Ruby Conference 2007 Advanced Ruby Class Design by Jim Weirich

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
thank you I'm glad to be here how many people are excited to be here and the number seven really before I start my talk I want to say Marcel talked a lot about beauty so you might want to know that is a flaming maple back guitar made by Breedlove which I consider to be one of the most beautiful instruments around so I've got lots of other pictures of guitars if you want to see them sometime I can tell there's going to be a problem right away this stage is way too small and I pasted when I talked so if I suddenly disappear down there that's what's happened and I'm going to have to stand back probably using that one two-step okay and can you hear me now is that better is that okay okay good it's not quite as long to my ears okay great my name is Jim Wyrick if you haven't picked up on that yet I work for edge case which is a rails development company I've been working them for about six months before that I was pretty involved in the Java world as well doing Ruby and Java kind of at the same time so that's kind of my background we'll talk a little bit about that but first I want to mention the word advanced here you might want to think wow he's really going to go deep into some really complicated Ruby code here and that's really not the case what I want to really talk about is not so much the advanced part but the Ruby part of it I have found that language particularly programming languages tend to shape and form the way you think so let me give you a little bit of my background so you understand where I am coming from I the very first programming language I ever learned was Fortran and that was in my physics class and we learned enough Fortran to drive some Cal Comp plotters so we could plot the data from our physics lab experiments and that was great I really loved it and it turned out I kind of followed the whole computer science thing rather than the physics things after I got out of college my first few jobs involve Fortran from there I did us some C programming I got very involved in modular 2 does anybody remember modular 2 at the time it was a I'm gonna say a beautiful language it had really great concepts and the things I was worried about her time was modularity cohesion and coupling and modulo - I felt got a lot of those things exactly right from that point then I started getting involved in object-oriented languages which took the ideas of coupling and cohesion and modularity even father so C++ was my first entry into object-oriented design from there I got into Eiffel and Eiffel taught me about contracts and how to write reliable software great language beautiful in its own way beautiful in a very different way than Ruby is but still a beautiful language in its own right I think and then I also involved into Java a lot now alongside of that the second programming language ever learned was Lisp yes these are your father's parentheses and it was quite by accident you see I signed up when I discovered hey this programming thing is kind of fun in my junior year in college I signed up for an introduction to Fortran programming so I could learn Fortran just to look better I got into class and the instructor started drawing core code on the blackboard we didn't even have whiteboards back then this is this is how old it is okay you start writing code on the blackboard and I noticed something right away it had way too many parentheses to be Fortran and I sat in that class for three days totally confused I had no idea why he was doing why all those parentheses because I knew a little bit of Fortran I knew this wasn't right and all of a sudden it dawned on me while he was doing with the parentheses in the cons and the recursions and the whole thing and it turns out this introduction introduction to Fortran course had Daniel Freedman as the instructor Daniel Freedman those of you who know lists the author of the little list for the little schemer and all that he had just come out with a little lisper book at that time so we were getting a lot of the little Lisp er techniques thrown at us in class and we learned lists for about the first three quarters of the course and then we switched over to Fortran so we did get our introduction to Fortran eventually but by then I was hooked so I learned less after at college I got involved with fourth fourth was great because it's a it's an interactive language but it's designed for systems with really small resources and I didn't have a lot of money just out of college I didn't have enough money to buy the it was either an apple 2e or Apple 2 or trs-80 those were the big computers back then I didn't even have enough money for them right out of college so I bought a little single board computer with 2k of memory on it and you know what forth runs great in 2k of memory I had an editor an assembler a compiler and enough for my application code all in 2 K of memory which is really cool fourth is very interactive not dynamically typed in any way ruby is but it was interactive in the same way that Ruby and in the same way that Lisp is in a lot of ways from there I kind of graduated up to tickle tool tickle tickle Google command language thank you it's been a while since I've done that pickle was also cool in a lot of ways and finally landed into Perl as my standard language for developing tools to help my programming of C++ and Java so Perl was kind of my tool language Java and C++ were kind of my professional languages that I used to get real coat real code done right and about that time I was really looking for an alternative Perl because I really like Perl I could really I really love the way I can get things done in Perl but you know what it was unreadable and when you grew your Perl code to a certain size it just didn't scale up to larger problems in my view I I couldn't handle Perl of a large code base so I was looking for alternatives and about that time Dave Thomas I think it was in the extreme programming mailing list I mentioned about this little language called Ruby so I grabbed Ruby downloaded it compiled it on my machine and tried it and in one day I had completely converted over to using Ruby rather than Perl so that was my introduction and what was great about Ruby it was an object-oriented scripting language and I was already really involved in the whole Olo movement and working with that from the C's C++ in the Java world in the Eifel world but I want to point out that my OO experience up to that time is entirely what we call static languages so my view of OO was very much shaped by the languages that I had used to do oh and I knew a little bit of small talk I did enough small talk that I could read small talk code but I'd never written any of it in anger so the concepts in Smalltalk really weren't a part of me at that time Ruby was eye-opening the things that you can do in Ruby as opposed to like a static language like Java was was incredible and I really loved that whole Ruby experience there's a saying you can write Fortran code in any language perhaps the updated version of that should be you can write Java code in any language and I think a lot of us how many people have come to Ruby from Java there's a lot of us here I think when you ruby is your first introduction to a dynamic oo language you're going to bring concepts from Java and C++ that a lot of your concepts are going to be great and they're going to work the whole thing about modularity and cohesion and coupling that's going to come right with and that's going to work as well but there are techniques that you can do in Ruby that are inconceivable in Java code and C++ code we are going to talk about some of those techniques today so what I really want to do is if you are boxed by the language that you're thinking and now I really want to get you to thinking outside of that box so what are you going to expect from this talk well first of all I have three examples of more or less real-life code that I'm going to show you right here these are examples of libraries that I've written and are using in production and some there's one example of one that's just kind of a cool idea the reason I'm included that one because these examples have to be fun they have to be interesting and they have to I have to be excited about talking about that I will make a confession I was working on this presentation and I got to the third example and I was writing it up and I was about halfway done with writing up and I made this discovery I was bored I didn't like the example it was interesting code but the process of explaining it wasn't very interesting at all to me so I so I was trying to decide whether to use that example or switch to something else I happened to forget to save my presentation file so the few days later when I came back to revisit it the whole set of slides for the third example is gone so it says good that's a sign from God we're changing that example so the abstract you might have read in the program is a little bit different than what we're doing here that's okay the other thing I want to illustrate our techniques that you're not going to find in your standard Java /c C++ type of programming so these are things that you're not going to really run across so that's what makes this advanced Ruby talk okay so let's go ahead and move on to the examples the first one I want to talk box number one that you want to jump out of I call this the master of disguise and this is an example that actually comes from rank I'm sure most of you have perhaps heard of rake build tool for Ruby Thank You Marcel I think this is a new piece of code Beauty is the fact that people use find it useful and that really gives me a thrill more than anything else that people find code useful so thank you for that this is actually a very small piece of rake that I want to talk about it in rake rake is all about building things and manipulating lists of files so we have a special class in a rake that handles the collection of file names and finding files in your project and managing them that's called the file list object and use it a lot like this as you see on the screen you give it a glob a pattern and it will act then as an array of those file names like an array almost there are about four differences between file lists then and a real array number one it's initialized by a glob it is knowledgeable about knowledgeable about your file system so it can find those file names in your system you can include files you can exclude files you can do it by patterns and all kinds of interesting things so it's very knowledgeable about that it also has two specialized 2's method and this is the big reason I wrote a specialized class to handle it it's because if you 2's an array all the things get in there they are jammed together without spaces and I really wanted a list of file name of spaces each one so file list has a specialized 2's it also has some extra methods that don't exist or a normal array you can manipulate the extensions of all the file names in a file list you can omit their paths even these other interesting things on file lists that are not available right finally fire lists are lazy the reason is if you have a rake file and you have a bunch of different tasks to find in your rake file you might define a whole bunch of file lists up front at the beginning of your rake file that won't be used but you're just defining them just in case you might they might be used by one of the texts that might be called later so if you initialize a file us by globbing through your file directories of searching for files building up these lists of file list could be quite expensive so rake file lists are lazy and that they don't actually go out and look at the file system until the first time you need to use the values in that file list at that point would go out we get the values you need and we bring them in so if you define 1020 file lists beginning your rake file none of them actually do anything time-consuming until you actually start using it so that's a good feature a file list actually the feature we're going to kind of concentrate on today now my first cut at writing file lists looked a lot like this I said well a file list is an array and so we'll just inherit from array and get all the array behavior automatically if you come from a static language like Java or C++ one of the first rules that you learn in class design is do not inherit from concrete classes that carries a whole bunch of baggage with it truth is that in Ruby the reasons there are reasons for not inheriting from concrete classes but they are entirely different than the reasons that you would find in Java or C++ and we'll get into what these reasons are so I thought okay let's let's go ahead and inherit from this concrete class and let's just see what happens and it actually worked out quite well begin with initialize for file list essentially now this is a simplified version I've simplified the code for the talk here the real file list is a little more complicated than this but we initialize a file list by loading a pattern we save the pattern we set a resolved flag to false because we have not yet gone out and gotten the filenames that exist on the file system yet and also we call super that's just good practice when you inherit from a class and your initializer make sure you allow the initializers in your parent classes to run as well so lazy loading set just remember the pattern for use later and set the resolved the false so that you know you have resolved your file yet then you have to write a resolved method and it's pretty straightforward when you call resolve you want to make sure your arrays empty go through the file system get all the patterns and insert them into South which is an array and then set resolve to true so when you're done your file list now contains all the file names out on your file system and we're resolved so the next time you won't have to call resolve and you check that later now does this work at this point no helped by the title I gave that one away did we haven't resolved it yet so when we create a file list and we try to access the first member because we have not resolved anything yet that array is empty so this will fail what we have to do is actually explicitly resolve the file is first ouch that's painful I don't want to do that so the way to fix that is to automatically resolve the file list the first time you need it so we will take the indexing operation and we will replace a raised version with the version that Cole's resolved if we haven't been resolved yet and then invokes the super version of index which calls the array code it actually returns the indexing mmm now that's okay for indexing remember there's a lot of other array methods that need to be handled as well so you've got our wash rinse repeat this over and over again now if um I would strongly recommend at this point investigating a little meta programming make this easier so you defined this code in one place and we'll do that in a little bit and I'll leave that as is for right now okay I'll resolve great actually worked pretty good and I was really happy and a couple versions of rate work exactly like this and I had very few problems then of sudden someone reported a bug this code works this is the using the plus operator that takes two arrays and concatenates the contents together pluses of method on a file list I resolve the file list and I concatenate the then I call the super version of that which concatenates the contents and the result is a list of everything in the file list plus everything in the array that's the argument to the bus operator so that works but what happens if I change the order we've got a small problem arrey doesn't know anything about resolving file lists so in this case the plus is an operator on a regular array the regular array takes an argument that's a file list looks at it and says oh I have an array and it does because file listed Barrett's from an array it is an array so it says oh let's concatenate the contents oh we have no contents so the result is just the first array you totally never resolve the contents of file list which is a bug that's a problem what are we going to do about that okay talk about the whys and we've covered that okay if only I love that phrase if only there was a way for an arbitrary object indicate to the Ruby system hope treat me as if I were an array wouldn't that be a nice thing to have as then I could put that into file s and Ruby would would work with that excuse me can someone get me a great water thank you I appreciate that because the technology turns out there's a method called to array to ary that is designed to indicate exactly this behavior it says I am an object thank you it says I am an object and I want to be treated as if I were an array when Ruby senses you have a too Airy method on your class it will call that method use the result method should return an array as a result and then it will use that result in all its operation so it treats your object as if it were an array well that's fine except file list to cleverly mimics the fact visit array in fact it is an array when Ruby looks at pilots it says oh you're an array I don't need to call to Airy on you I'm just going to use your context so the fact I inherit from array and file list was what was killing it so to make the to Airy method work I had to modify file us to not inherit from array after all instead it's a regular object inheriting from just your base class object again the initialize is much the same except now we delegate to an actual array object that we contain inside a file list the app items item there we have to change our resolving code so that it used to look like this we now delegate two items instead of calling super and now everything is cool we can take an array and a file s that when we concatenate them Ruby says oh look I've got an object that's not an array can you act as an array file list we'll say yes I have a two area Airy method on me call that and we'll be happy Ruby does that and we get the result back so that's cool remember this we need to auto resolve all those so I'm going to go ahead and show you what that would look like let's draw out that code instead of implementing all those methods all over again you can do something like this create a list of methods you need to auto resolve and do a little meta programming using class eval to define all those so it becomes actually quite easy to inherit from array and r2 not inherit from array but yet simplement all of arrays behavior in fact you can instead of explicitly setting up resolve I'm pointing at my screen you see that instead of explicitly listing those you can actually go and ask an array off to quarter your instance methods and then iterate and just auto resolve all those with a little metaprogramming your class the code you actually write is actually quite small and so that's what we're doing today with vilest things to remember from this okay sometimes when you want to be something else is better not to inherit but to use the built-in Ruby to Airy in the case of arrays is also if you do something similar for string there's a two STR method that does something very similar for that so use those rather than inheritance when you want to mimic built-in classes okay box number two doing nothing people have used builder I really don't ask that to get applause but it makes me feel good so applause Lord you want builder was a fun library to write I wrote it just for the pure joy of writing it I saw an example actually in groovy there was a some code in groovy that inspired this and I thought oh we could do that movie quite easily and it turned out to be quite easy however in the writing of this it turned out to be some interesting little corner cases that we had to deal with what I'm going to talk about those corner cases right now so if you're not familiar with builder it's very simple builder is a way of building XML very easily in in Ruby you create a builder object you then send messages such as stup such as name such as phone number to be builder object nest them in blocks appropriately and builder figures out how to translate all that good stuff into XML using the names of the methods uses the tags in XML and using your block structure to figure out how to nest it properly so this is a simple example of using builder and the names of the tags and the names of the messages match up to the magic of method missing I didn't write builder knowing anything about student about student names about student phone numbers it's just that we trap those methods in a builder with method missing and take the name and create the XML using yes so you can send anything to ACTU builder and it recognizes it and it's a really cool I will claim it's actually easier to write XML with builder than it is to write XML on XML and it's great especially if you're dynamically generating stuff you can create loops and generate a list of students very easily pulling stuff out of a database and doing a builder is a really handy little library to use what happens though in this example we have name phone number and we added a new call or a builder class or two our builder object and that's class so you want to wreck record what class the student is taking and he's taking an intro to Ruby obviously a very astute student in computer science he what happens when we execute this little piece of demo code right here yeah class is a predefined method Ruby has a method called class that every object acts in class returns the class of the object there's a number actually there's quite a number of predefined methods in object this class is ID there's two two as to string you know the list goes on and on and on if you use any of these predefined methods in your XML builder has a problem with it because it says oh this is predefined it doesn't get into the whole method missing thing at all and we've got problems so class is predefined the fix for this would be nice there's that phrase again wouldn't be nice yet how can we inherit from object without actually inheriting from object we would like to be an object but not yet all the I'm not going to take garbage but all the other extra stuff that comes with object we don't want it for our builder class so how are we going to handle that well in builder we're going to change it so instead of inheriting from object it's going to inherit from something called blank slate and it is blank slate that I want to talk about today here we go blank slate it turns out is really easy to write well almost what happens when we run this do you see what I'm doing here I'm garrovick all the instance methods that are available in the blank slate class I'm iterating through each of them and I'm under finding that method when you undefined the method that makes it disappear from that class a blank slate will not have class it will not have ID it will not have to s defined in it so we just iterate through them and remove them what happens when I run this demo well we get the right XML but we also get a little cup of warnings because we were a little overzealous removing methods there's a couple methods such as underscore ID and underscore sin that the system kind of uses in if you remove them it may cause serious problems that's easy to fix so we'll just undef it unless the name begins with double underscores and actually I think the real version of blank slate we also avoid removing instance about because it turns out instance of Al is a interesting method to have around and probably not too many XML files that have instance of a Lhasa tag name so that was kind of a safe wouldn't be better so still that's pretty cool and if we run this we get exactly the right code great I love that simple direct easy to run I love ruby is this good enough have to covered all the corner cases again blank slate was released pretty much like I just showed you into the wild and people began writing in and say hey there's a problem with this XML builder that these methods are not appearing in my XML even though they should Oh what are we doing here we require blank slate so blank slate loads it removes all the methods from it internally so now it is a blank slate and then in our application we decide wouldn't it be nice to have a globally available method called name we've put it into kernel kernel is module that he gets included into objects anything you define the kernel is available on every object looting blank slate now if we could arrange our code so blank slate is always the very last thing ever loaded so I did this addition to kernel before I loaded blank slate blank slate would handle it and remove it when it was loaded unfortunately it's too late at this point blank slate is already loaded it's already removed all his methods and now I go adding something to kernel and blank slate picks it up automatically that is not a good thing so again this will result in the wrong number of arguments error for the same reason that classed it because now name is predefined so we're going to deal with this turns out this is also pretty easy to deal with in Ruby I'm going to refactor blank slate just a little bit undef method is a private method I want to be able to call it somewhere else so in preparation for that we're going to wrap it up in a method called hide will use method hide in that loop that undefined zall our methods and now hide is available globally and then we're going to add something to curl if people have used method added know what method added is it's a hook into the Ruby code itself Ruby when it defines methods in classes and modules will look for a method a class method called method Adam and if it finds it it will call it and parents in the name of the method that was just added to that module so I want to direct your eyes to the third join in method added it says blank slate bhai when we add a method I want to hide it inside a blank slate if the module is being added to itself is Colonel so if Colonel gets a method added to it we remove it from blank slate so blank slate gets kind of late notification of any methods being added if the module were adding a method to is not the colonel and that's okay because I don't care because it's only Colonel that's going to bother a blank slate so I check to make sure we're only hiding it if it's colonel the first and last line of method added is just a good way use book methods because someone else might have cooked into method added and you want to make sure that you call their code as well otherwise you will blitter ate their use of method added so that's that's just good I think rails has a way of doing that automatic method chain or something alias method change the rail rails has a little way of doing that very automatically but that's essentially what we're doing here is just being courteous and using method books and anytime you use a method book please use something similar to this so oh and note at the bottom of the screen says we need something similar for object you have to repeat this code in object because you can add methods directly to object as well as kernel so very similar little set of code will go in there as well okay now are we good enough have we covered all the corner cases I added that the builder released it again and I thought we've sold everything everything's good cool and then all of a sudden bug reports start coming again system I'm using this method and it's still not showing up and builder like it should it's still not showing up in my XML so I scratch my hat as I said into your code because I've got everything covered I can't see how awesomely we could be missing anything and so they sent me the code and I looked it over discovered this very similar the problem before subtly different in this case we are creating a new module called capital name inside the module name we define a method called name and then we include the module name into the object class this is actually a very common way of extending core classes you put all the extension things in a module that contains the extensions and then in one place in the core class just include the whole thing all at once so this is actually good practice and recommended problem is it totally bypasses the method adding hook because we're not adding methods now the object we're including an entire module lots of methods in it so fortunately for us ok that fails this example there fortunately for us there's a hook feature called append features and I'm not actually going to walk through this code because if you saw the method added thing it's almost exactly identical back we have to make sure that whenever we include a module but append teachers is called whenever a module is included so we could find those events we check to see that were where the object class and then we remove all the methods go ahead and hide the methods that are in that module in blank slate so blank slate captured that behavior as well and that has turned out to be pretty good that's what we're using currently in builder and I don't think I've got any reports from anyone saying they're having problems now and look carefully over the audience no one's waving hands good good good but if you want to see the exact details I encourage you to go grab the code for builder and peruse it it was a really fun library to write and I really enjoyed doing that ok this is the example that's not in a real library that I use but I think the techniques in here are interesting and I'm really hoping someone will find a good use for my example probably not end up being a good use and we'll talk about the whys of that but the technique is fun and that's good enough for me because I like to talk about fun code you've seen this right this is just rails code if you've done any rails programming at all you've seen this all the time user is a rails active read active record module a bottle and has a find method on it we're saying find all the users whose name is Jim and you see N and if you look at that same that's interesting we have name equals question mark and in quotes that is actually sequel code so we're mixing a little bit of sequel code with a little bit of Ruby code all kind of in one thing and it's it's a whole cemetry clarity beauty thing is bothering me at this point because one thing I know I can do is if I have a list of users I can call select on it pass it a block and in that block I say okay it does this use his name equal Jim and that should be a double equals there and if it equals Jim then that is true and select for me all the users whose name is Jim from this list of users that is all Ruby code and that's the way we write Ruby code every day of our lives none of this sequel stuff that we have to match with so so so I'm really drawn to the way of doing things but I know that the database is there for models and I have to do that somehow but wouldn't it if it's raised again wouldn't it be nice if there was a way we could you select an active record model that would be so cool so we could say this users not select and give it the block that has all our criteria we code everything in Ruby and everything would be beautiful and we're all happy we never have to deal with sequel again so let's write let's write select and here's my first attempt I hear some coughs and food from the audience do you detect some problems with this code a performance issue if a pre exactly premature optimization is the hobgoblin vixie quotes okay it's hugely inefficient look what I'm doing I'm grabbing all the users from the database and then I'm putting them in LS so if I have a million users I just have a an array with a million entries in it then I sort through them for the handful of users whose name is actually Jim not really efficient not efficient at all it's slow large tables are going to absolutely kill this code you know databases are actually as much as I rail against databases they are a good thing they do things very well they have really efficient algorithms for searching data and the tables indices set up to track this stuff and we're not using the database at all for this just for the sake of being able to write everything in Ruby so I've got a I got this problem I want to do everything in Ruby but I also want to take advantage of the database as well so what can we do well I'm going to hypothesize a magic implementation of select and the magic is okay we have this little magic method called translate block to sequel we pass it the block and it will come up and the result of this method will be a condition that we can pass into rails and let rails and sequel and the database do all the good work that it likes it will translate the form at once and then the database does this job but we are still writing everything that we do in Ruby that would be really cool except how would we do that well I could think of handful of ways we might accomplish this we could actually parse that quote how many people have written a parser great was it a lot of fun actually yeah probably it was but it was also a lot of work and just to solve the problem of writing code and writing a route up let's ask us how many people have written a ruby parser max you can raise in here that's a hard thing to do ruby is an interesting language to parse and actually I think there's some libraries out there that that will handle some of that for you but that's a little too much work for me because I'm looking for this simple solution I can write in an evening well oh yeah this is this is what ah this is a small snippet of code from the Ruby parser built into Ruby itself so I didn't want to get into there there's a library called parse tree yes right question is a wonderful library and it bypasses the whole oh how do i parse ruby question by saying look there's one piece of code that really really really knows how to parse Ruby and that's Ruby so let's let Ruby parse it and we'll go into its guts and rip out the abstract are the the parse tree that's built into Ruby itself and we'll expose it into Ruby code and it's a really cool idea and I really recommend doing that in fact that's such a good idea that someone else had that idea and if you look up the ambition project on air the blog at this URL he does exactly that in fact he writes a version of select using parse tree to get at the data so if you want to do this this is a good way to go and look at that library and I think he's just kind of getting started I'm not sure how far along he is with it there's some really cool ideas but we're not going to do it that way mainly because someone else is doing it that's not how my car why don't we just execute the code execute the code that's going to happen keep that thought in mind for just a second and this is a IRB session for your illumination we start IRB we load a library called node actually node one I create an object called table node so we're going to be dealing with nodes table node is a tell you in a little bit what a node is but a table node is a node that remembers a database table so we tell it to remember the name of users which is the name of a database table that facts our active record model we store that in a variable called user and then we call a method on user the method is dot name and we store the result of that method in a variable called result and then we print out the result I would convert result to a string and we get user's name what just happened that's really interesting I created an object I called a method on it and the result of calling that method if I convert the result of calling the method to a string it tells me what method I called and what object I call doc actually it actually it does something a little bit different than that actually it converts that method call on user to a sequel fragment that references the table dot field name cool I can now get field references from within Ruby code just by using this table note and just to illustrate it further I also asked for the age field in user and when I convert the result of that to a string I get users age so I can reference I can build up sequel for very simple method calls and the implementation of table node is true feel it's one it's two classes the first one is table node which we're using directly and it just remembers the name of the table that we're referencing and when you convert that to a string there at the bottom of the screen you see that it just returns the table name trivial code the only minor tricky stuff in it at all is a method missing call and when we get a method on our table node it says I am going to remember the method that was just called by creating a new object called a method node the method node will recall me the table node plus the method that was just called on me and I'll return that as a result of the method call itself and so the method node is the result of calling a method on a ruby object here's method node again really trivial it just records the object in the method name and when asked to convert it to a string does exactly that that is all the code you need to implement that IRB session that's kind of cool we're hardway converting Ruby code the sequel because we can convert method calls and objects to sequel fragments at reference fields on tables but got to be a little bit more just convert field references how do we handle this how do we say username equals Jim how do we record that and translate that into sequel well you know what double equals is just a method and I can define double equals on node I can see somebody's smiling because you're seeing where this is going and I can create a new kind of node with it you're going to see a lot of different node classes in this implementation hold a binary up node the binary opto says ok 4 double equals the sequel equivalent of that would be a single equal and I have to remember the left-hand side and the right-hand side of the equals and I'll record that and we initialize now let's go ahead and show the implementation of binary output we record the operator the left and right hand side and then when we to ask the whole thing we just convert it to a sequel fragment that has parentheses around it so we don't have to worry about precedence and throwing an extra parentheses of effort and we have the left hand side converted to a string the operator converted to a string and the right hand side converted to a string Wow binary node binary opt node is pretty easy as well and not very complicated so reiterating capture the method in the node class create a node object that represents it initialize that node object with the things that it needs to know and just return that implementing the node is pretty trivial and running this now create our table node we execute the code user age equals 50 and store that as 1 then when we convert res 1 to a string it says users got age equal 50 it's the sequel where clause for that Ruby code right there let's try it again let's do the same thing with the string now let's say username equals gym double equal jump and when we convert that the result of that to a string we get user's name single equal Jim whoo I got a problem yeah we have no quotes around Jim ah it's easily fixed small matter of programming what we've done is that we've not distinguished what we're taking Ruby objects and just dumping them out of strings we really need to differentiate the conversion we need to capture the conversion of Ruby objects into sequel objects numbers just 2's on a number that's fine however string we can invert a ruby string to a sequel fragment we really want to put boats around them so we need to surprise create a node class for those so let's create a literal node class and that's just handles simple things like integers that convert directly into sequel and then we'll create a string node that handles the conversion of strings and the sequel strings and it does that when you call to us it just adds single quotes to either side of the string and probably you want to make that a little bit fancier and handle so the scaping in that string as well just be aware of that but we're keeping it simple here now we have a problem I've got a bunch of different well I got two classes literal and strength that I have to make sure get applied to the right kind of Ruby operator come in so we have to look at a ruby object and we have to make a decision how do I apply that ruby object and come up with the right kind of node for that Ruby object well you could do this I could create a method called wrap and node it takes an object just run through a case statement in in that and this will work fine I don't like this in particular the reason is I've got a wonderful oo language and objects know how they themselves should do things why don't I ask instead of centralizing all this conversion into one place why don't I distribute it among the objects so every object knows how to convert itself into a sequel node so what I would rather do with something like this open up the object class create aid as a sequel node method and it just wraps any arbitrary object as a literal node and then string which is a special case that I want to handle I just wrap strings up in a string node and return that as well they're both called as a sequel look the key is we're opening up core classes here so we want to be careful with the names of the method this is perfectly fine Ruby but you want to be careful because you're opening up core classes and there could be named collisions with other libraries so that's why I named it as a sequel node I felt that name was unique and not that I probably would collide with any other arbitrary Ruby library adding things to string and adding things object so just be aware of that issue and now converting any object to eight node is really really easy you just ask that object please give me give me yourself as the proper node now we need a tweak node just a little bit it needs its own as a sequel node method and since it's already a node just return itself that works actually perfectly fine then we need to use it so when we create a binary up node we need to make sure that the right-hand side of the operation is converted to a node now the left hand side is a node object already that's why we're here in this code so we don't need to convert self it's already a note but other may or may not be a node we just convert it to a node now when we run this single quotes that's really cool we can convert arbitrary Ruby code to sequel just by executing the Ruby code okay what's left to do in this example well there's lots of operators we need to touch yet double equals not the only one we need to hit all the other greater than less than greater than equal to less than or equal to all the plus minus star and divide operators and a bunch of other operators we might want to consider things like milk question mark milk question mark is the Ruby idiom for doing that perhaps no take nil question mark and translate that dude is no okay so there's perhaps pattern matching should be converted to the sequel like operator maybe maybe not I don't know there's all kinds of things that we could add to that and then if we want to write select it's pretty much just this except I noticed I forgot to do a 2's call on our table of the result of the block but we take the block we execute the block passing in a table node object as the as the argument so when we evaluate our condition the condition gets evaluated against the table node object builds up the sequel code as we built write the query and when we're done that result that con and we take it converted to a string which I forgot to put into the slide and pass in to fund all and we've got our selector we've got our magic convert to convert block to sequel cool so why don't we do this lot of problems with this method well there's a number of them most operators are commutative so we can say user name is Jim or we can say Jim double equals user name the first one works the second one has a small problem and that double equals is being sent to the string class string doesn't know about nodes so it's going to return true or false it's not going to return the proper node object to capture that so that is a problem we can play with that we could override double equals and capture that but then we're playing with really fundamental definitions in Ruby and I'm just a wee bit uncomfortable but that so that's that's a small problem we can either solve it or decide to live with it actually if you have mathematical operators like + - those actually handle the whole coercion problem by a protocol called or works so in Ruby if you have a fixed number and a floating point number you have to add them together you have to figure our blocks with proper result for those things they participate in a little method called force it figures out the proper common class to do that you can actually tie into coerce and use in mode and make sure that if the node is on the right-hand or left-hand side it still works it doesn't matter so mathematical operators are okay but some of them such as double equal will not quite work right because they don't participate in force okay but here's a bigger problem and I think this is really the killer that really stops suspect from being useful for sequel and that's the double ampersand and double vertical bar operators are well they're not methods you cannot override them as methods and the reason is that double LAN and double vertical bar and or operators short-circuit their evaluation if they can determine the truth true or false of an expression just by looking at the first operand they won't ever evaluate the second operand so it's difficult to define that kind of semantics in a method and therefore Ruby doesn't give us a way of hooking into those operators as over aidable so you cannot override double and double vertical bar you could use single ampersand single vertical bar instead then we have to write special Ruby code inside those select blocks rather than regular Ruby code and that's a little bit uncomfortable for me a bigger problem also is that the not the bang operator and the bang equal operator also are not override abode so you never say not condition it will always say bang in that expression or return true or false it will never take that invert it would not know there's just no way of doing that likewise for the equals can't change them can't override so that's a problem and so I wouldn't really recommend this technique for sequel it's a great example to kind of motivate the problem but I'm kind of throwing this out because I'm really hoping someone will have a great idea for using this thing I think it's a beautiful technique it's a solution we're looking for a problem perhaps maybe evaluating expressions in graphing or some kind of mathematical analysis this could be useful for maybe you have a problem domain where oh hey this would be really useful to use but the technique is essentially capturing Ruby code execution tracing Hall gets called and building up a structure of that so you can tell what has happened in the past actually we use this a very small limited way in flex Mach flex Mach has the ability to record transactions as they happen on a Mach so later you can playback you can have a verified good way of doing something and a new way of doing something and you can verify that they interact with the mop the same way a very limited fashion we're doing with that but nothing nothing like this I got this idea actually from a small talk library drawer which does this in small talk very similar there's also a library in Ruby called criteria that does this as well but it suffers from the problems that we you've mentioned before but you can look at those so advanced Ruby class design what did we learn well we saw a lot of fun code that was actually my goal for today but I think we really show now that that that language shapes the way you think and when you're in Ruby you need to look for Ruba fide solutions to your problem and in doing so you really kind of have to understand the corners of your language you can come into Ruby from Java from C++ or from a different language and use Ruby very quickly but until you kind of learn those corner cases learn what hook methods are learn about meta programming learn about redefining operators you're going to miss a lot of good solutions to your problems until you kind of get that core language under your belt so I really encourage you to go into research the language and understand those kind of corners I also want to encourage you to kind of go ahead and think outside the box look for those unusual solutions you don't need them all the time in fact you don't need them most of the time most of your code works like this I would be very very worried but there are occasions when you need a special solution and Ruby key step in and find that special solution for you so go ahead and think outside the box you know because after all about three years ago someone had not stepped out of the box with regard to web frameworks I'd still be writing this kind of code so thank you very much you you
Info
Channel: Confreaks
Views: 10,956
Rating: undefined out of 5
Keywords: rubyconf2007, Ruby Conference 2007
Id: vwBpTgdZBDk
Channel Id: undefined
Length: 61min 16sec (3676 seconds)
Published: Fri Apr 12 2013
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.