Exploring Ruby on Rails
LJ: What's the first step with Rails? What are the first steps to turning some concept into code?
DF: That's a good question, because it brings up an important point about Rails that might be foreign to you if you're coming to Rails from a different framework or a different programming environment. There's a lot of code generation and a lot of the boilerplate code is generated or already exists. So, the first step with Rails is to run the rails command with the name of your application in the directory where you'd like to serve the application. For example, enter
~ > cd /var/www/htdocs/www.mysite.com && rails mycoolwebapp
and boom, this whole hierarchy is created for you.
LJ: Then what?
DF: There are a few main directories in this hierarchy that you will be spending most of your time in as a Rails application developer. The main directory is called app, and it's where your Web application will go.
LJ: So, I can imagine you might have a table representing posts to your blog, something like
create table blog_posts ( id serial, post text, created_on timestamp, primary key (id) );
How do you go about getting a handle on that table in Rails?
DF: After you create your application, you would generate a model called BlogPost.
LJ: Rails is pretty big on MVC, huh?
DF: Definitely; the whole framework (see Figure 1) is based on this paradigm.
LJ: So how do you generate this model?
DF: First, and this is really the only configuration step in Rails, you'd edit the ./config/database.yml file to reflect your database of choice--MySQL, PostgreSQL, SQLite, whatever. Basically this is username and password stuff. Next, there's a script called generate in the script directory. You give this script one argument for what type of object you want to generate, for example model or controller, and a second argument for the name of the object you are generating. In our example it would be:
~ > ./script/generate model BlogPost
LJ: Hmmm. Let me get this straight: you ran the rails command, which is itself a code generator, initially to populate a directory hierarchy with a bunch of stuff in it. And then one of those files, the generate" script, is used to generate more code for this specific application?
LJ: How did you configure the table to model-class mapping?
DF: You don't. Basically the class that rails generates is totally empty. It will look something like this
class BlogPost < ActiveRecord::Base end
Because this class inherits from ActiveRecord::Base, it knows how to find its table in the database at runtime.
LJ: But we were talking about a table named "blog_posts"?
DF: Right, and it's named--this another important point that you bring up--it's named after what it contains: blog_posts, plural. And the model that you generate for that table is called BlogPost, singular. Rails has a smart Inflector class that does all this translation, but you can override it if you want.
LJ: I see Rails knows English better than I do. Your class is empty, so how do you configure the database to instance-method mapping?
DF: Again, you don't; ActiveRecord::Base handles that for you. It dynamically responds to methods at runtime, so if you call the method category on an instance of BlogPost, which, of course, represents a single row of the blog_posts table, it knows how to get that column. It knows how to look up the field "category" from that row.
LJ: What if you altered your blog_posts table, say you added a column, something such as the time the post was entered?
DF: If you followed convention and added a field named created_on to the blog_posts table, you'd get a bunch of stuff for free. First, any field in the table automatically has both getter and setter methods dynamically determined at runtime, even if the column is added later. Second, certain column names have special meaning to Rails, and created_on is one of those. A column with that name is handled automatically by inserting the creation time of the row as the transaction time at which it was created.
LJ: How do you know which column names are special?
LJ: And these automatic methods to access the table columns, do they return strings because they are so generic?
DF: Nope. Rails inspects the database schema and maps the columns to the appropriate types. So an int comes back as a Ruby Fixnum, a timestamp comes back as a Ruby Time object and so on.
LJ: You still haven't talked about writing any code.
DF: True. Nothing so far requires any [code] to be written.
LJ: Well, your database is pretty boring at this point; you've got only one table. How does Rails handle relationships between tables?
DF: In my opinion, this is where the real magic of ActiveRecord starts. Reflecting on the database to give a model-class getters and setters is pretty cool, but handling inter-table relationships intelligently goes to another level. Comments in a blog are a great example of this. Normally, a blog post has several comments associated with it, assuming your readers aren't too lazy to post them. Likewise, a comment always belongs to a blog post. In the database, then, a common way to implement this is to use a foreign key in one of the tables. For instance, my blog_comments table has a column labeled blog_post_id, because ActiveRecord understands this naming convention. The only other thing I had to do to connect my BlogPost model with my BlogComment model was add two lines of code, one to each of the models:
class BlogComment < ActiveRecord::Base belongs_to :blog_post end
class BlogPost < ActiveRecord::Base has_many :blog_comments, :order => "date" end
This, of course, assumes a some schema such as this one:
create table blog_comments ( id serial, comment text, blog_posts_id int, primary key (id), foreign key (blog_posts_id) references blog_posts (id) );
After I did this and played with the code a little bit, I realized how cool it was. Automatically an instance of BlogPost had a new method called comments that would yield an array of the comments whose foreign key, blog_post_id, matched the ID of the BlogPost instance. The same was true of a BlogComment instance; a method named post provided the BlogPost instance associated with that comment via the foreign key.
LJ: So Rails dynamically generates join selects if it has been told two tables are related?
LJ: That's almost like configuration.
DF: Well, true, but because it's done in a fully fledged programming language such as Ruby instead of a data language such as XML, it can do so much more. For instance, if we're working with a database that didn't support foreign keys, such as SQLite, and you defined the relationship like this
class BlogPost < ActiveRecord::Base has_many :blog_comments, :order => "date", :dependent => true end
Rails would handle cascading delete for you. That is, deleting a BlogPost would result in all its associated BlogComments being deleted too, even though the underlying database may not enforce foreign keys or cascading deletes.
LJ: But surely well-designed classes could grok XML that somehow dictated cascading deletes?
DF: Yeah, that's true, but it's really nice for the programmer to be able to keep everything--model, view, controller--all in one language. In Rails you don't have to split out state and behavior into separate files; you can describe it all in one file using the same language. Plus, in practice, only a few of these configuration steps even need to be taken, because Rails model-objects are designed to reflect on the database at runtime. It's not an exaggeration to say that your entire controller-class could end up being shorter than a single XML configuration file.
|Designing Electronics with Linux||May 22, 2013|
|Dynamic DNS—an Object Lesson in Problem Solving||May 21, 2013|
|Using Salt Stack and Vagrant for Drupal Development||May 20, 2013|
|Making Linux and Android Get Along (It's Not as Hard as It Sounds)||May 16, 2013|
|Drupal Is a Framework: Why Everyone Needs to Understand This||May 15, 2013|
|Home, My Backup Data Center||May 13, 2013|
- Designing Electronics with Linux
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- Dynamic DNS—an Object Lesson in Problem Solving
- New Products
- Using Salt Stack and Vagrant for Drupal Development
- Validate an E-Mail Address with PHP, the Right Way
- Build a Skype Server for Your Home Phone System
- Why Python?
- Tech Tip: Really Simple HTTP Server with Python
- A Topic for Discussion - Open Source Feature-Richness?
- Not free anymore
1 hour 6 min ago
4 hours 53 min ago
- Reply to comment | Linux Journal
5 hours 1 min ago
- Understanding the Linux Kernel
7 hours 16 min ago
9 hours 45 min ago
- Kernel Problem
19 hours 48 min ago
- BASH script to log IPs on public web server
1 day 15 min ago
1 day 3 hours ago
- Reply to comment | Linux Journal
1 day 4 hours ago
- All the articles you talked
1 day 6 hours ago
Enter to Win an Adafruit Pi Cobbler Breakout Kit for Raspberry Pi
It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Pi Cobbler Breakout Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- 5-21-13, Prototyping Pi Plate Kit: Philip Kirby
- Next winner announced on 5-27-13!
Free Webinar: Hadoop
How to Build an Optimal Hadoop Cluster to Store and Maintain Unlimited Amounts of Data Using Microservers
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Some of key questions to be discussed are:
- What is the “typical” Hadoop cluster and what should be installed on the different machine types?
- Why should you consider the typical workload patterns when making your hardware decisions?
- Are all microservers created equal for Hadoop deployments?
- How do I plan for expansion if I require more compute, memory, storage or networking?