If you already know why C++ and Ruby on Rails are fundamentally different and just want to see the example, you can skip to The Example.
I’ve been developing software for many years but, for the most part, have stayed in the C++ world. I made the transition to web applications development with C# ASP.NET MVC applications, which I felt is a fairly easy transition. Adjusting to the MVC design pattern took a little change in thinking but it’s not a terribly difficult pattern to understand. It’s been a few years now and I am quite comfortable in that world.
Ruby on Rails is different. Very different
While the Ruby language itself is not particularly challenging to understand and work with, the magic of Rails is built on the notion that convention is a holy concept. You may know Ruby but if you don’t know the conventions of Rails, you won’t get too far past the basic scaffolding. Unlike C++, C#, ASP.NET, in Rails, where you put files and the names you give classes and objects all matter.
Luckily, the conventions usually aren’t to difficult to grasp. If you understand the MVC pattern, the file layout, naming conventions, and routing scheme of Rails is straight forward. Generators for models, controllers, and full scaffolding get you most of the way there and plenty of examples and tutorials online get you even further.
When the magic doesn’t work
It all seemed so easy. You created a simple application with a simple model, view, and controller scheme; nearly all of it effortless. Amazingly, it just worked! Now you want to add a simple AJAX call back into the controller from the view so you can dynamically update part of the page with the call results. You find an example online on how to do just that. It doesn’t work. From everything you can see, your code looks exactly like the example.
As a C++ programmer, your brain says "stick some breakpoints in there and figure out what’s broken." You could do that but remember, Rails uses magic. Your action might work perfectly but somewhere along the way you mis-named something and you will end up stepping into the actual source of Rails if you want to go that route.
Let’s create an example to debug. We’ll create a very basic blog type system that allows anyone to create an anonymous post (with a title and body) and anyone to add comments (with a name and body) to the post.
$ rails new MyBlog $ cd MyBlog $ rails g scaffold Post title:string body:text comments:references $ rails g scaffold Comment username:string body:text post:references $ rake db:migrate
It’s obviously not fully usable but that’s where we’ll start. Start a server and test it out:
$ rails server
Browse to http://localhost:3000/posts
Try to create a new post and you’ll notice immediately that the view isn’t quite right. Title and body are there but only a text field for comments. Go ahead and fill in some test values and, as expected, you’ll get an error:
The first part of the message is important. It tells us that the error has to do with creating a ‘new’ comment.
The rest of the error message tells us the parameters being passed into the action but we’ve got enough to go on here. In C++ something like this would not compile (being strongly typed and all) but with Rails we more or less must run tests to determine what’s going on. With that being the case the exceptions and error messages are usually fairly pointed. They tell you right where to look for your problems (unlike run-time C++ messages which can be a little on the cryptic side).
If you look in the file referenced (posts_controller.rb at line 43), you’ll see exactly what’s trying to happen; Post.new is failing. As the error message told us: comments can’t be written.
Of course, we really don’t care that it can’t be written since we don’t want to save any comments with the post anyway. Comments will come later, after the post exists. So, to fix this we just open the view, app/views/posts/new.html.rb and see that it’s actually just calling render ‘form’ (which magically gets resolved to _form.html.rb). Open up the _form.html.rb and remove the "comments" field.
<div> <%= f.label :comments %><br /> <%= f.text_field :comments %> </div>
Refresh http://localhost:3000/posts and create another new post. Great. We’ve created a post.
Click to go back to the posts index page and you should see the new post.
Click "show" to see the post. Everything we expect to be there is but now we want to be able to add comments.
Since we would like to dynamically update the comments in the post’s ‘show’ view, we’ll open app/views/posts/show.html.rb and remove the line:
<% @post.comments %>
Replace that with a div that we can edit with some jQuery later.
<div id='comments'> <% render @post.comments %> </div>
This should render all of the comments for the post but we don’t have any yet so we really can’t test. We could go ahead and add a form for adding comments but let’s take baby-steps and make sure that we can render comments first. So, how do we create comments without providing users the ability to create comments.
To interact with our model we’ll start the rails console by entering in a terminal:
$ rails console
This gives us a Ruby console that loads the framework for our Rails application. This is something that C++ (and even ASP.NET as far as I know) does not offer: the ability to interact with your back-end framework in a simple code-based way. Instead of having to debug a long chain of interaction between views, controllers, and models, we can use the console to focus solely on one area of the app: right now, the model. We’ll continue to use the console until we get our model working how we expect for our purposes.
To see the post you just made (or any other posts that you might have made):
Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => [#<Post id: 1, title: "Test", body: "This is a test post.", comments_id: nil, created_at: "2013-02-28 13:31:31", updated_at: "2013-02-28 13:31:31">]
So, if we would like to create a new Comment to go with our post, let’s just get the one post we care about:
post = Post.first
To create a new comment, we’ll use what we know about our comment model to create a comment:
comment = post.comments.create(:name => "Brian", :body => "Some comment on the post.")NoMethodError: undefined method `create' for nil:NilClass
Well, that didn’t go as expected so let’s examine what happened. It appears that the comments are undefined for the Post class. Since we are in the console, we are only working with our model so there is no need to look at anything but the Post model.
class Post < ActiveRecord::Base belongs_to :comments attr_accessible :body, :title end
Just looking at this shows something that doesn’t seem right. A Post does not belong to comments (it’s the other way around). Change "belongs_to" to "has_many", since a post "has many" comments.
Since our model files have changed, the Post we created earlier is not valid. Since we are only dealing with one post here, the easiest thing to do is just delete the post and recreate it:
post.destroy() > Post.all (yup, its gone) > post = Post.new(:title => "Another test post", :body => "This is a second test post") > post.save > post.comments.create(:username => "Brian", :body => "Test comment") (0.1ms) begin transaction SQL (1.2ms) INSERT INTO "comments" ("body", "created_at", "post_id", "updated_at", "username") VALUES (?, ?, ?, ?, ?) [["body", "Test comment"], ["created_at", Thu, 28 Feb 2013 14:23:42 UTC +00:00], ["post_id", 3], ["updated_at", Thu, 28 Feb 2013 14:23:42 UTC +00:00], ["username", "Brian"]] #<Comment id: 1, username: "Brian", body: "Test comment", post_id: 3, created_at: "2013-02-28 14:23:42", updated_at: "2013-02-28 14:23:42">
That looks more like it. If you get the post again and examine the comments for it you’ll see the new comment:
post = Post.first Post Load (0.2ms) SELECT "posts".* FROM "posts" LIMIT 1 > <Post id: 3, title: "Another test post", body: "This is a second test post.", comments_id: nil, created_at: "2013-02-28 14:23:19", updated_at: "2013-02-28 14:23:19"> <blockquote> post.comments Comment Load (0.3ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 3 => [#<Comment id: 1, username: "Brian", body: "Test comment", post_id: 3, created_at: "2013-02-28 14:23:42", updated_at: "2013-02-28 14:23:42">] </blockquote>
We’ve got our model. It’s not perfect. For example, there is still an empty comments_id column in the Post class and database table as a result of our initial migration. That certainly could be cleaned up but it is outside of the scope of this article.
So, from the perspective of a C++ developer, this is a lot different. We are debugging by interacting with the console, not tracing through the code.
In Part II we’ll change gears and start looking at debugging on the view and controller side of things.
(image of Rails logo via http://en.wikipedia.org/wiki/Ruby_on_Rails)
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.