Blog
what did i learn today

One recurring problem when doing Ruby on Rails development is a nested model form.

Nested Model Form: a single form that contains multiple, nested models.

For example a project with its tasks. A nested model form will allow you to create or edit, in 1 form, the project and each of its tasks. With the use of cocoon, this post will describe how to create a nested model form for some (all?) of the possible relations. All these examples start from the project, so we will just start with a dummy project as follows: [ruby] rails g scaffold Project name:string [/ruby] That's all we need to get started. In these examples i am using simple_form and slim (which looks a lot like haml, but is much faster).

The simple :has_many

The simple :has_many is the most occuring relation (unfortunately no scientific data to back that up). The simple :has_many is a relation where the child cannot exist without the parent, and where each child can have only one parent. A typical example is the project and his tasks. Each task is unique, each task only exists because of the project. If the project is gone, so are the tasks. First the models: [ruby] class Project < ActiveRecord::Base has_many :tasks accepts_nested_attributes_for :tasks, :reject_if => :all_blank, :allow_destroy => true end class Task < ActiveRecord::Base end [/ruby] The accepts_nested_attributes_for method makes sure that when posting the project-data, it will accept the attributes for the nested model. Secondly we add the views. Inside the project _form.html.slim we add [ruby] #tasks = f.simple_fields_for :tasks do |task| = render 'task_fields', :f => task .links = link_to_add_association 'add task', f, :tasks [/ruby] Create a partial called projects/_task_fields.html.slim which looks like: [ruby] .nested-fields = f.input :name = f.input :description = f.input :done, :as => :boolean = link_to_remove_association "remove task", f [/ruby]

The nested :has_many

Actually this is a simple extension of the previous example. For instance, a project with tasks, and each tasks has sub-tasks. Our models: [ruby] class Task < ActiveRecord::Base has_many :sub_tasks accepts_nested_attributes_for :sub_tasks, :reject_if => :all_blank, :allow_destroy => true end class SubTask < ActiveRecord::Base end [/ruby] Edit the projects\_task_fields.html.slim : [ruby] .nested-fields = f.input :name = f.input :description = f.input :done, :as => :boolean .sub_tasks = f.simple_fields_for :sub_tasks do |sub_task| = render 'sub_task_fields', :f => sub_task .links = link_to_add_association 'add sub-task', f, :sub_tasks = link_to_remove_association "remove task", f [/ruby] and add the view projects\_sub_tasks.html.slim (which bears a lot of resemblance to the previous tasks-partial): [ruby] .nested-fields = f.input :name = f.input :description = link_to_remove_association "remove sub-task", f [/ruby]

The look-up or create :belongs_to

A simple :belongs_to would mean that the parent would already exist, and a simple look-up (dropdown list) would suffice. If the parent is always unique, it can be solved in the same way as the :has_many. So, let's describe a solution for the case where we either select the item from a list, or create a new one. Our project has an owner (which is a person): [ruby] class Project < ActiveRecord::Base belongs_to :owner, :class_name => 'Person' accepts_nested_attributes_for :owner, :reject_if => :all_blank end class Person < ActiveRecord::Base end [/ruby] inside the _projects/_form.html.slim we add the following: [ruby] #owner #owner_from_list = f.association :owner, :collection => Person.all(:order => 'name'), :prompt => 'Choose an existing owner' = link_to_add_association 'add a new person as owner', f, :owner [/ruby] Here we use the built-in way from simple_form to select an association from a look-up list: f.association. But secondly, we place the link_to_add_association that will render the partial projects/_owner_fields.html.slim and create a new Person and link to him. The partial itself is pretty straightforward: [ruby] .nested-fields = f.input :name = f.input :role = f.input :description = link_to_remove_association "remove owner", f [/ruby] Now if you implement this, the form is functioning but not really user-friendly. What we want is that when clicking the add a new person as owner-link, that the drop-down box and the link itself disappear. When we choose to remove the to-be created owner, that the drop-down and add-link reappear. So we need to add a bit of extra javascript. Open up app\javascripts\projects.js and add: [javascript] $(document).ready(function() { $("#owner a.add_fields"). data("association-insertion-position", 'before'). data("association-insertion-node", 'this'); $('#owner').bind('insertion-callback', function() { $("#owner_from_list").hide(); $("#owner a.add_fields").hide(); }); $('#owner').bind("removal-callback", function() { $("#owner_from_list").show(); $("#owner a.add_fields").show(); }); }); [/javascript] The first two lines control where the new partial (the new owner) will appear. Upon insertion or removal, cocoon will trigger callbacks that are defined on the parent-container of the add-link. The last lines add those callbacks, and these will make sure that the link and dropdownbox will be hidden or shown again.

The :has_many :through relation

A classic example of this relation are tags. Something has tags, those can be chosen from an exisiting list of tags, one or more, or one could create new tags. This relation is, again, an extension of the previous solution. This solution is a bit more complicated, because there is a bit more javascript involved here. The models: [ruby] class Project < ActiveRecord::Base has_many :project_tags has_many :tags, :through => :project_tags, :class_name => 'Tag' accepts_nested_attributes_for :tags accepts_nested_attributes_for :project_tags end class ProjectTag < ActiveRecord::Base belongs_to :tag belongs_to :project accepts_nested_attributes_for :tag, :reject_if => :all_blank end class Tag < ActiveRecord::Base end [/ruby] inside the projects/_form.html.slim add: [ruby] #tags = f.simple_fields_for :project_tags do |project_tag| = render 'project_tag_fields', :f => project_tag = link_to_add_association 'add a tag', f, :project_tags [/ruby] Create a file projects/_project_tag_fields.html.slim containing: [ruby] .nested-fields.project-tag-fields #tag_from_list = f.association :tag, :collection => Tag.all(:order => 'name'), :prompt => 'Choose an existing tag' = link_to_add_association 'or create a new tag', f, :tag = link_to_remove_association "remove tag", f [/ruby] And create another file projects/_tag_fields.html.slim: [ruby] .nested-fields = f.input :name, :hint => 'how should it be tagged' [/ruby] When entering all this, it should be working, but it looks a bit confusing. The dropdown box does not disappear (as before). So we need the following bit of javascript to projects.js: [javascript] $("#tags a.add_fields"). data("association-insertion-position", 'before'). data("association-insertion-node", 'this'); $('#tags').on('cocoon:after-insert', function() { $(".project-tag-fields a.add_fields"). data("association-insertion-position", 'before'). data("association-insertion-node", 'this'); $('.project-tag-fields').on('cocoon:after-insert', function() { $(this).children("#tag_from_list").remove(); $(this).children("a.add_fields").hide(); }); }); [/javascript] While this javascript is doing almost the same as with the :belongs_to it is a bit more complicated, because the tag-partial is not yet inside in the page, so neither is the parent-div. Once that is clear, it is actually quite obvious what it does. I hope. Upon insertion of a new (project)tag, it also adds the callbacks for the insertion and removal of a new tag.

Example Code

All these examples are demonstrated in a working project: cocoon-simple-form-demo. If you can think of more relations you would like to see solved, or see an example of, please let me know. Hope this helps.

testing image_tag using rspec2 and rails3

When testing your helpers, you might want to test if your call to image_tag generates the correct output. Unfortunately, in Rails, for caching purposes, the asset tag is added which makes testing it somewhat unreliable. Luckily there is an easy way around that. If you define the environment variable RAILS_ASSET_ID, that is used instead of some permutation of the file-creation-date/time. You could set RAILS_ASSET_ID to a specific number, simplifying the tests, because the number will always be the same, on all machines executing the tests. But if you set RAILS_ASSET_ID to an empty string, it effectively removes the tag from the link. For testing this is exactly what we want. So an easy way to test is: [ruby] describe OperatorsHelper do context "get_operator_icon" do before(:each) do ENV['RAILS_ASSET_ID'] = '' end it "returns the image for any operator (not current)" do helper.stub(:current_operator?).and_return(false) @operator = Factory(:operator) helper.get_operator_icon(@operator).should == "<img alt="User-business-gray" src="/images/icons/user-business-gray.png" title="interactive user" />" end it "returns the image for the current operator" do helper.stub(:current_operator?).and_return(true) @operator = Factory(:operator) helper.get_operator_icon(@operator).should == "<img alt="User-business" src="/images/icons/user-business.png" title="interactive user (yourself)" />" end end end [/ruby] Hope this might help someone with the same problem :)

allowing double render in rails3

I migrated the actionwebservice gem to rails3. Inside actionwebservice (server-side) there is the option to test your SOAP interface. What this does, under the cover, is first handle the SOAP-request (and render the result), and then wrap those results inside a HTML-view. Very nice. To circumvent the double render exception in rails2 you had to call a function erase_render_results. Unfortunately this function is no longer available in rails3. Luckily the fix is quite easy (but it took me a while to find). Inside actionwebservice the following function was called inside a controller to allow a second render: [ruby] def reset_invocation_response erase_render_results response.instance_variable_set :@header, Rack::Utils::HeaderHash.new(::ActionController::Response::DEFAULT_HEADERS.merge("cookie" => [])) end [/ruby] To make this work in rails3, you just have to write: [ruby] def reset_invocation_response self.instance_variable_set(:@_response_body, nil) response.instance_variable_set :@header, Rack::Utils::HeaderHash.new("cookie" => [], 'Content-Type' => 'text/html') end [/ruby] So in short: to allow a second render, you have to

  • set the controller's instance variable @_response_body to nil
  • reset the content-header Hope this helps.

During the migration of a huge application from rails2 to rails3 I encountered a seriously baflling error. In almost every controller spec, i got the the following error: [ruby] Caught ActionController::RoutingError: No route matches {:controller=>"home"} [/ruby] This got me puzzling. If i ran a single controller-spec it worked, running the whole bunch failed (using rake spec or rspec spec/controllers/**/*_spec.rb). So I posted a question on stackoverflow and on the rspec-mailing-list. When googling, all related issues seemed to be caused by actual errors in config/routes.rb, but my application was working, it was generating all links correctly. It only failed when testing, and only when running the full suite. What i did to solve this, was actually compare the log-files of a single group of working controllers with the total set of controllers. And there it was: the routing table was erased by the first spec i ran. We have a base controller class where the shared behaviour for our API-controllers is placed. To test this controller we create, inside our spec, a new controller-class that derives from that baseclass and add a new route. In rails2 and rspec1 this spec looked as follows (simplified): [ruby] require 'spec_helper' class WsTestController < Ws::BaseController def index end end ActionController::Routing::Routes.draw do |map| map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format' end describe WsTestController do context "without authentication" do it("asks for Basic authentication") { get :index response.status.should contain("401") } end context "with authentication" do # .. snipped the rest .. end end [/ruby] Now apparently what happens now, in rails3, is that instead of adding routes, the entire routing table is replaced by these routes. Whoops. So in effect, this was almost the only controller-spec that actually worked. When i disabled this spec, or more specifically the part where the routes are manhandled, all my controller and view specs worked again. So how do you fix this? How can you dynamically add routes in rails3 at runtime? Luckily google found the solution here. So my working spec for rails3/rspec2 looks as follows (and that doesn't interfere with my other specs) : [ruby] require 'spec_helper' class WsTestController < Ws::BaseController def index render :text => 'this worked' end end describe WsTestController do before(:each) do begin _routes = Dpplus::Application.routes _routes.disable_clear_and_finalize = true _routes.clear! Dpplus::Application.routes_reloader.paths.each{ |path| load(path) } _routes.draw do # here you can add any route you want get "ws_test/index" end ActiveSupport.on_load(:action_controller) { _routes.finalize! } ensure _routes.disable_clear_and_finalize = false end end context "without authentication" do it("asks for Basic authentication") { get :index response.status.should contain("401") response.header["WWW-Authenticate"].should contain('Basic') } end context "with authentication" do # ..snipped ... end end [/ruby] Hope this helps :)

I have created a few rails 3 sites, but i have never before ported an existing rails2 application to rails3. I will describe the problems i encountered. First off there were some very good resources (step-by-step descriptions) to guide me through it:

ActiveSupport::Callbacks

Before we wrote: [ruby] module Workflow class Base define_callbacks :before_something, :after_something def some_method run_callbacks :before_something puts "some method" run_callbacks :after_something end end class SpecificWorkflow < Base before_something :do_something def do_something puts "something" end end [/ruby] Now, this has changed. Into the following: [ruby] module Workflow class Base define_callbacks :something def some_method run_callbacks :something do puts "some method" end end end class SpecificWorkflow < Base set_callback :something, :before, :do_something def do_something puts "something" end end [/ruby] Which is pretty nice. It has the disadvantage we can only use :before and :after callbacks anymore, but that was easily solved.

ActionController::ParamsParser

We hooked into the Rack middleware before the ActionController::ParamsParser to make sure we return Bad Request when the parsing fails. This middleware has been renamed to ActionDispatch::ParamsParser. Easy fix :)

safe_helper removed?

We prepared our Rails 2 project a long time ago, and used the rails_xss plugin. We used the safe_helper all over our helper-methods, but apparently this is not supported in Rails 3. Bummer. So, before [ruby] def some_helper "<strong>something</strong>" end safe_helper :some_helper [/ruby] After: [ruby] def some_helper "<strong>something</strong>".html_safe end [/ruby] A bit annoying to do, a bit unfortunate, because we thought we were preparing ourselves for a smoother upgrade. Not entirely so.

The routes!

Only after those steps did my rails get far enough to discover my routes. I did not change anything about the routes, just placed inside the correct block. I deleted my index.html and put back my original application.html.haml and the first view was working.

The missing to_key

The to_key was missing inside the user_session model. I described that solution already in this blogpost. So i just needed to fix that.

Replacing ActionController::RecordIdentifier.singular_class_name

We used ActionController::RecordIdentifier.singular_class_name, in rails3 this is replaced by ActionController::RecordIdentifier.dom_class.

Missing translations

A feature we used a lot was I18n.t('.filter') and this would look for operators.filter.filter because the line was inside a partial _shared/_filter.html.haml and called from the operators/index.html.haml. Now this does not work anymore, and now instead I18n looks for _shared.filter.filter. I did not find an easy way to solve this, just moved some translations. Luckily we did not override the translations of a partial based on the context where it was rendered yet. Does anybody has any tips on that?

Upgrading javascript helpers

In rails3 unobtrusive is the way to go. So, a remote_form_for like [ruby] - form_remote_tag :url => update_url do [/ruby] becomes [ruby] - form_tag :url => update_url, :remote => true do [/ruby] Which seems easy enough. It does get more complicated when the :update tag is used. For example: [ruby] - remote_form_for post, :update => {:success => "result_#{id}"} do |form| [/ruby] becomes [ruby] - remote_form_for post, :remote => true, :html => {:class => 'remote-post-form', :'data-update' => "#result_#{id}"} do |form| [/ruby] The :update is no longer supported as before, and we have to perform some glueing ourselves. So i replaced the :update by data-update. And then inside javascript (e.g. application.js) we can do the following: [javascript] jQuery(function($) { $(".remote-post-form") .bind("ajax:success", function(data, status, xhr) { var update_selector = $(this).attr('data-update'); $(update_selector).html(status); }); }); [/javascript] If you need more elaborate example, check this question on stackoverflow.

master_helper_module

This has been replaced by _helpers.

active_layout

The active_layout method, of a controller, no longer exists inside rails3. To replace it you need to use two methods:

  • action_has_layout? returns true if a layout exists.
  • To get the layout-name, i have no better solution then calling the private method _layout. This can be done, as known, by doing controller.send(:_layout).

Upgrade rspec

I had to upgrade Rspec 1 to v2. First off in the Gemfile i added the correct version. I removed the rspec.rake file. I ran [ruby] rails g rspec:install [/ruby] But still all rake spec commands were missing. The fix was simple. In the Gemfile make sure the rspec-rails is also usable from development mode: [ruby] group :development, :test do gem 'rspec-rails' end [/ruby] Now on to fixing the tests!

Fixing the tests

In the upgrade from rspec1 to rspec2 a lot of things have changed. What i encountered:

  • replace all Spec:: by RSpec::
  • controller_name 'users' no longer exists, write a surrounding describe UsersController do instead (that is also clearer imho)
  • ...

Extending Rspec::Rails::ExampleGroup::ControllerExampleGroup

Before, in rspec1, ControllerExampleGroup was a class and you could add code to it. So for instance, we had a file like this in our support folder: [ruby] module RSpec module Rails module Example class ControllerExampleGroup let(:the_account ) { Factory(:account) } let(:the_user ) { Factory(:user)} end end end end [/ruby] A bit larger, but you get the picture: this allowed us to define a set of shared let definitions. In rspec-2 this is no longer possible this way, because ControllerExampleGroup is now a module. I handled that like this: [ruby] module Lets def self.included(base) base.let(:the_account ) { Factory(:account) } base.let(:the_user ) { Factory(:user)} end end [/ruby] and inside my spec_helper this file is automatically required (since it is stored in the support folder) and i just add [ruby] config.include(Lets) [/ruby]

Rspec: replace config.extend

I also noticed that using config.extend(ModuleName) dit not work anymore. Instead i had to write (on the same place, inside the configure block) : [ruby] Rspec.configure do |config| .. include ModuleName .. end [/ruby] and that worked for me.

More?

Do you have more hints to share? What bumps did you find and how did you overcome them?

handling migrations in rails 3 engines

In the process of converting a rails 2.3.* plugin to a rails 3 gem, i bumped into the problem of converting the migrations too. There was some documentation in the rails-guides, but it did not quite do what i wanted. I also found out that in the next version of rails (3.1) there will be support for rake tasks for migrations from engines. What i wanted was that upon invocation of my InstallGenerator, also the migrations would be added, and the user could just do rake db:migrate. So, presume my gem is called MyGem, i want that the following line could be run: [ruby] rails g my_gem:install [/ruby] To achieve this goal, you need to define an InstallGenerator that will add the migrations to the Rails application itself.

create the generator

Create the folder lib\generators\my_gem\install and inside that folder create a file called install_generator.rb with the following code: [ruby] require 'rails/generators/migration' module YourGemName module Generators class InstallGenerator < ::Rails::Generators::Base include Rails::Generators::Migration source_root File.expand_path('../templates', __FILE__) desc "add the migrations" def self.next_migration_number(path) unless @prev_migration_nr @prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i else @prev_migration_nr += 1 end @prev_migration_nr.to_s end def copy_migrations migration_template "create_something.rb", "db/migrate/create_something.rb" migration_template "create_something_else.rb", "db/migrate/create_something_else.rb" end end end end [/ruby] The function next_migration_number makes sure each migration gets a unique number (even if they are all added in the same second).

add the migration templates

Inside the lib/generators/my_gem/install/templates add your two files containing the migrations. Let us define the one named create_something.rb : [ruby] class CreateSomething < ActiveRecord::Migration def self.up create_table :abilities do |t| t.string :name t.string :description t.boolean :needs_extent t.timestamps end end def self.down drop_table :abilities end end [/ruby] You can define the other examples as you wish.

run the migrations

When your gem is added to some app, you can just do : [ruby] rails g my_gem:install rake db:migrate [/ruby] Hope this helps.

I am developing several rails projects. Some are still rails 2.3.9, and some are rails3. But using those at the same time, on the same machine can cause some trouble. I use bundles for all my projects, so the gem dependencies are managed. But still, somehow, when running my tests, i get into trouble. If i run spec, it complains that it is deprecated. Rubymine doesn't support running tests in my rails 2 project. The solution? Use gemsets! Gemsets are a feature from rvm, the Ruby Version Manager. But with gemsets rvm does more than just manage your ruby version! A gemset is a set of gems, which you can switch at will. So i am using a gemset per project. Creating a gemset is easy: [ruby] rvm gemset create some_name [/ruby] Using a gemset is equally simple: [ruby] rvm gemset use some_name [/ruby] Showing which gemset is in use: [ruby] rvm gemset name [/ruby] Showing the list of possible gemsets [ruby] rvm gemset list [/ruby] By default, you are using an unnamed gemset, which contains all the gems for your ruby-version. Per project you can create a new gemset. Per gemset you have to re-install your gems, but luckily, using bundler, that is as simple as doing bundle install. Now comes the good part:

  • Rubymine supports using gemsets, so you can select your gemset to work with, and each time your project is opened, it uses the correct gemset and the correct corresponding scripts (e.g. spec)
  • If you place a .rvmrc file in your project-folder containing rvm default@projecta, every time you cd into that folder it will select the default ruby and the projecta gemset. Awesome!
getting rcov working with rspec 2.0.0.rc

I had troubles to get rcov working with the latest rspec2 release (since beta23 and now 2.0.0.rc). I got the same error every time: [bash] require': no such file to load -- spec\_helper (LoadError) [/bash] But luckily, somebody [found the problem](http://github.com/rspec/rspec-core/issues/issue/172) and it is extremely easy to fix. Just add -Ispecto your rcov task. Therake spec:rcovdoes not work for me (as it needs to be fixed). So i added my own task (add this code to the end ofRakefileor add in a seperate filercov.rakeinsidelib/tasks`) : [ruby] desc "Run all specs with rcov" RSpec::Core::RakeTask.new("test_cov") do |t| t.rcov = true t.rcov_opts = %w{--rails --include views -Ispec --exclude gems/,spec/,features/,seeds/} end [/ruby] and then you can run the task by typing [bash] > rake test_cov [/bash] in your rails root folder.

rails3 doing cross-browser json

I have two rails applications that communicate together. In the first application i have the following method in my controller: [ruby] def delivery_status envelope = Envelope.find(params[: id]) render : json => envelope.to_json end [/ruby] which, if go to the url in my browser, nicely shows my the JSON. Seems ok. However, if i call this through jQuery, from my second application, using the following: [javascript] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); $.ajax({ url: data_url, dataType: 'text', success: function(data, status, req) { alert('received with status' + status) alert('received json ' + data); } }); }); }); [/javascript] then the right url is hit with the correct parameters but data is always empty. I first tried using $.getJSON, then $.get to end at $.ajax. I am not sure but it seemed i was doing something wrong at server-side. The request looked fine inside firebug, but the response was always empty. Yet, i did not understand, if let the browser hit the same url, i got my json object. So how do you solve this? Well, i was reading the documentation of $.ajax, and there i found:

When data is retrieved from remote servers (which is only possible using the script or jsonp data types), the operation is performed using a script tag rather than an XMLHttpRequest object.

So, jsonp was the way, but how? First, i changed my jQuery code: [sourcecode language="javascript"] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); data_url = data_url $.ajax({ url: data_url, dataType: 'jsonp', success: function(data) { envelope = data.envelope; alert('received envelope ' + data.envelope.id); } }); }); }); [/sourcecode] but then my server-side needed to be able to handle the jsonp. I handled that using the following code: [ruby] def delivery_status envelope = Envelope.find(params[:id]) render_json envelope.to_json(:include => [: deliveries, : log_lines]) end private # render json, but also allow JSONP and handle that correctly def render_json(json, options={}) callback, variable = params[:callback], params[:variable] logger.debug("render json or jsonp? Callback = #{callback}, Variable=#{variable}") response = begin if callback &amp;&amp; variable "var #{variable} = #{json};\n#{callback}(#{variable});" elsif variable "var #{variable} = #{json}" elsif callback "#{callback}(#{json});" else json end end render({:content_type => :js, :text => response}.merge(options)) end [/ruby] Where the render_json does all the dirty work for me :) I was somewhat expecting this to be standard inside rails3, and as Kevin Chiu pointed out in the comments, it is and much simpler at that: [ruby] def delivery_status envelope = Envelope.find(params[:id]) render :json => envelope.to_json(:include => [: deliveries, : log_lines]), :callback => params[:callback] end [/ruby] Awesome :)I have two rails applications that communicate together. In the first application i have the following method in my controller: [ruby] def delivery_status envelope = Envelope.find(params[: id]) render : json => envelope.to_json end [/ruby] which, if go to the url in my browser, nicely shows my the JSON. Seems ok. However, if i call this through jQuery, from my second application, using the following: [javascript] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); $.ajax({ url: data_url, dataType: 'text', success: function(data, status, req) { alert('received with status' + status) alert('received json ' + data); } }); }); }); [/javascript] then the right url is hit with the correct parameters but data is always empty. I first tried using $.getJSON, then $.get to end at $.ajax. I am not sure but it seemed i was doing something wrong at server-side. The request looked fine inside firebug, but the response was always empty. Yet, i did not understand, if let the browser hit the same url, i got my json object. So how do you solve this? Well, i was reading the documentation of $.ajax, and there i found:

When data is retrieved from remote servers (which is only possible using the script or jsonp data types), the operation is performed using a script tag rather than an XMLHttpRequest object.

So, jsonp was the way, but how? First, i changed my jQuery code: [sourcecode language="javascript"] $(document).ready(function(){ $('.get_sms_details').live('click', function(event) { var el = $(this), data_url = el.attr('data-url'); data_url = data_url $.ajax({ url: data_url, dataType: 'jsonp', success: function(data) { envelope = data.envelope; alert('received envelope ' + data.envelope.id); } }); }); }); [/sourcecode] but then my server-side needed to be able to handle the jsonp. I handled that using the following code: [ruby] def delivery_status envelope = Envelope.find(params[:id]) render_json envelope.to_json(:include => [: deliveries, : log_lines]) end private # render json, but also allow JSONP and handle that correctly def render_json(json, options={}) callback, variable = params[:callback], params[:variable] logger.debug("render json or jsonp? Callback = #{callback}, Variable=#{variable}") response = begin if callback && variable "var #{variable} = #{json};\n#{callback}(#{variable});" elsif variable "var #{variable} = #{json};" elsif callback "#{callback}(#{json});" else json end end render({:content_type => :js, :text => response}.merge(options)) end [/ruby] Where the render_json does all the dirty work for me :) I was somewhat expecting this to be standard inside rails3, but apparently it isn't. Are there any better ways to handle this?

rspec2 using watchr instead of autotest

I was having troubles with autotest, and not really finding a good solution. But i noticed that inside rspec2 they were using watchr. Watchr is a very generic gem that will allow you to watch a set of files and take action when something changes. That sounds great, but maybe a bit too general :) So, it appears you need a script to run your rspec2 continuously using watchr. Luckily i did find some examples, which allowed my to brew my own (save in your rails-root as specs.watchr): [ruby wraplines="false"] # adapted from http://github.com/rspec/rspec-rails/blob/master/specs.watchr # Run me with: # # $ watchr specs.watchr # -------------------------------------------------- # Convenience Methods # -------------------------------------------------- def all_spec_files Dir['spec/**/*_spec.rb'] end def run_spec_matching(thing_to_match) matches = all_spec_files.grep(/#{thing_to_match}/i) if matches.empty? puts "Sorry, thanks for playing, but there were no matches for #{thing_to_match}" else run matches.join(' ') end end def run(files_to_run) puts("Running: #{files_to_run}") system("rspec -c #{files_to_run}") no_int_for_you end def run_all_specs run(all_spec_files.join(' ')) end # -------------------------------------------------- # Watchr Rules # -------------------------------------------------- watch('^spec/(.*)_spec.rb') { |m| run_spec_matching(m[1]) } watch('^app/(.*).rb') { |m| run_spec_matching(m[1]) } watch('^app/(.*).haml') { |m| run_spec_matching(m[1]) } watch('^lib/(.*).rb') { |m| run_spec_matching(m[1]) } watch('^spec/spec_helper.rb') { run_all_specs } watch('^spec/support/.*.rb') { run_all_specs } # -------------------------------------------------- # Signal Handling # -------------------------------------------------- def no_int_for_you @sent_an_int = nil end Signal.trap 'INT' do if @sent_an_int then puts " A second INT? Ok, I get the message. Shutting down now." exit else puts " Did you just send me an INT? Ugh. I'll quit for real if you do it again." @sent_an_int = true Kernel.sleep 1.5 run_all_specs end end [/ruby] It even seems to be quicker than autotest, but not sure if that is just wishful thinking.

authlogic on rails3

If you want to get authlogic working in a fresh rails 3 project, it will take a bit more steps than devise. This has everything to do with vision: authlogic only claims to deliver you the backend, allowing itself to remain more stable and to easily replace other authentication libraries (even your home-brewn). That seems a great philosophy, but starting from scratch involves more steps. First you need to setup a clean rails3 project, as i described before.

Install the gem

As simple as adding the following line to your Gemfile : [ruby] gem "authlogic" gem "rails3-generators" [/ruby] (note: you need the rails3-generators to include the needed generators). Then run bundle install or bundle update.

Create the UserSession

[ruby] rails g authlogic:session UserSession [/ruby] This would be all, but in my installation it was not enough. Something inside rails3 broke the authlogic session. But the fix, luckily, is pretty easy: you have to add the to_key function. So your complete UserSession model will look as follows: [ruby] class UserSession < Authlogic::Session::Base def to_key new_record? ? nil : [self.send(self.class.primary_key)] end end [/ruby]

Create the User

If you do not yet have a User model, and i am assuming you don't, you need to [ruby] rails g model User [/ruby] For now, this is an empty model. You will need to fill in your migration to create the model correctly. [ruby wraplines="false"] class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :login, :null => false t.string :email, :null => false t.string :crypted_password, :null => false t.string :password_salt, :null => false t.string :persistence_token, :null => false #t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params #t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability # magic fields (all optional, see Authlogic::Session::MagicColumns) t.integer :login_count, :null => false, :default => 0 t.integer :failed_login_count, :null => false, :default => 0 t.datetime :last_request_at t.datetime :current_login_at t.datetime :last_login_at t.string :current_login_ip t.string :last_login_ip t.timestamps end add_index :users, ["login"], :name => "index_users_on_login", :unique => true add_index :users, ["email"], :name => "index_users_on_email", :unique => true add_index :users, ["persistence_token"], :name => "index_users_on_persistence_token", :unique => true end def self.down drop_table :users end end [/ruby] Now we still have to add some code to the User model : [ruby] class User < ActiveRecord::Base acts_as_authentic end [/ruby]

ApplicationController

We need to add some generic code to our applicationcontroller to persist the sessions, to check whether a user is required and do the correct redirects when needed. [ruby] class ApplicationController < ActionController::Base protect_from_forgery helper_method :current_user_session, :current_user private def current_user_session logger.debug "ApplicationController::current_user_session" return @current_user_session if defined?(@current_user_session) @current_user_session = UserSession.find end def current_user logger.debug "ApplicationController::current_user" return @current_user if defined?(@current_user) @current_user = current_user_session && current_user_session.user end def require_user logger.debug "ApplicationController::require_user" unless current_user store_location flash[:notice] = "You must be logged in to access this page" redirect_to new_user_session_url return false end end def require_no_user logger.debug "ApplicationController::require_no_user" if current_user store_location flash[:notice] = "You must be logged out to access this page" redirect_to account_url return false end end def store_location session[:return_to] = request.request_uri end def redirect_back_or_default(default) redirect_to(session[:return_to] || default) session[:return_to] = nil end end [/ruby]

UserSessionsController

[ruby] rails g controller UserSessions new [/ruby] and fill the controller with the correct code: [ruby] class UserSessionsController < ApplicationController before_filter :require_no_user, :only => [:new, :create] before_filter :require_user, :only => :destroy def new @user_session = UserSession.new end def create @user_session = UserSession.new(params[:user_session]) if @user_session.save flash[:notice] = "Login successful!" redirect_back_or_default users_url else render :action => :new end end def destroy current_user_session.destroy flash[:notice] = "Logout successful!" redirect_back_or_default new_user_session_url end end [/ruby] and of course that also needs a view, in new.html.haml[ruby] %h1 Login = form_for @user_session, :url => {:action => "create"} do |f| = f.error_messages %div = f.label :login = f.text_field :login %div = f.label :password = f.password_field :password %div = f.check_box :remember_me = f.label :remember_me %div = f.submit "Login" [/ruby] We want to use the f.error_message, but that is now removed from Rails3 and we need to install a plugin instead: [ruby] rails plugin install git://github.com/rails/dynamic_form.git [/ruby] We also need to define, inside config/routes.rb. You will see that the generator will have added the route get 'user_sessions/new', but that is not enough. You will have to add: [ruby] resources :user_sessions match 'login' => "user_sessions#new", :as => :login match 'logout' => "user_sessions#destroy", :as => :logout [/ruby]

Restrict access

Suppose you now have some other controller, e.g. HomeController, then restricting acces is straightforward: [ruby] rails g controller home index [/ruby] [ruby] class HomeController < ApplicationController before_filter :require_user def index end end [/ruby] The method require_user, defined in ApplicationController, will check if there is a user logged on, and if not redirect to the login-page. If we now delete the index.html inside your public folder, and we add the following at the bottom of config/routes.rb[ruby] root :to => 'home#index' [/ruby] Now we can start our application, and it should redirect to our login-page. But of course, since we have no users for now, we can't login just yet. To allow testing, you fire up your console: [bash] $ rails c Loading development environment (Rails 3.0.0.rc) ruby-1.9.2-p0 > User.create(:login => 'test', :email => 'test@tester.com', :password => 'test123', :password_confirmation => 'test123') => #<User id: 1, login: "test", email: "test@tester.com", crypted_password: "0129d9733b7912017e37a50263901488da90e127e6fd1ae6081...", password_salt: "ygufdflWkJQZGhbsGyia", persistence_token: "957788609b2067092dd3852b01613d53f96d0692ce5131174ca...", login_count: 0, failed_login_count: 0, last_request_at: nil, current_login_at: nil, last_login_at: nil, current_login_ip: nil, last_login_ip: nil, created_at: "2010-08-29 14:11:04", updated_at: "2010-08-29 14:11:04"> ruby-1.9.2-p0 > [/bash] Now you should be able to login using this user. For completeness, you should add some links to your application-view to allow logging in and out: [ruby] #user_nav - if current_user = "Signed in as #{current_user.email}. Not you?" = link_to "Sign out", logout_path - else = link_to "Sign in", new_user_session_path [/ruby] This would get your rails3 project started. The next steps would be to add some user management, or allowing users to sign up themselves, and maybe add some roles to limit certain users access if needed. Now, to contrast this with devise: i now have a bunch of code in my application that actually is not specific to my code, but is also completely not tested. I will provide example rspec tests for this later. Actually, the problem with this scenario as outlined, is it is one big bang. You should start little, declaring things you need to be able to do, and work in little steps to get there. But this scenario is intended as a draft to see how you could get there. Now, in your real application, you should start with the tests, and then from this example you now how you can implement it.If you want to get authlogic working in a fresh rails 3 project, it will take a bit more steps than devise. This has everything to do with vision: authlogic only claims to deliver you the backend, allowing itself to remain more stable and to easily replace other authentication libraries (even your home-brewn). That seems a great philosophy, but starting from scratch involves more steps. First you need to setup a clean rails3 project, as i described before.

Install the gem

As simple as adding the following line to your Gemfile : [ruby] gem "authlogic" [/ruby] and then run bundle install or bundle update.

Create the UserSession

[ruby] rails g authlogic:session UserSession [/ruby] This would be all, but in my installation it was not enough. Something inside rails3 broke the authlogic session. But the fix, luckily, is pretty easy: you have to add the to_key function. So your complete UserSession model will look as follows: [ruby] class UserSession < Authlogic::Session::Base def to_key new_record? ? nil : [self.send(self.class.primary_key)] end end [/ruby]

Create the User

If you do not yet have a User model, and i am assuming you don't, you need to [ruby] rails g model User [/ruby] For now, this is an empty model. You will need to fill in your migration to create the model correctly. [ruby wraplines="false"] class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :login, :null => false t.string :email, :null => false t.string :crypted_password, :null => false t.string :password_salt, :null => false t.string :persistence_token, :null => false #t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params #t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability # magic fields (all optional, see Authlogic::Session::MagicColumns) t.integer :login_count, :null => false, :default => 0 t.integer :failed_login_count, :null => false, :default => 0 t.datetime :last_request_at t.datetime :current_login_at t.datetime :last_login_at t.string :current_login_ip t.string :last_login_ip t.timestamps end add_index :users, ["login"], :name => "index_users_on_login", :unique => true add_index :users, ["email"], :name => "index_users_on_email", :unique => true add_index :users, ["persistence_token"], :name => "index_users_on_persistence_token", :unique => true end def self.down drop_table :users end end [/ruby] Now we still have to add some code to the User model : [ruby] class User < ActiveRecord::Base acts_as_authentic end [/ruby]

ApplicationController

We need to add some generic code to our applicationcontroller to persist the sessions, to check whether a user is required and do the correct redirects when needed. [ruby] class ApplicationController < ActionController::Base protect_from_forgery helper_method :current_user_session, :current_user private def current_user_session logger.debug "ApplicationController::current_user_session" return @current_user_session if defined?(@current_user_session) @current_user_session = UserSession.find end def current_user logger.debug "ApplicationController::current_user" return @current_user if defined?(@current_user) @current_user = current_user_session && current_user_session.user end def require_user logger.debug "ApplicationController::require_user" unless current_user store_location flash[:notice] = "You must be logged in to access this page" redirect_to new_user_session_url return false end end def require_no_user logger.debug "ApplicationController::require_no_user" if current_user store_location flash[:notice] = "You must be logged out to access this page" redirect_to account_url return false end end def store_location session[:return_to] = request.request_uri end def redirect_back_or_default(default) redirect_to(session[:return_to] || default) session[:return_to] = nil end end [/ruby]

UserSessionsController

[ruby] rails g controller UserSessions new [/ruby] and fill the controller with the correct code: [ruby] class UserSessionsController < ApplicationController before_filter :require_no_user, :only => [:new, :create] before_filter :require_user, :only => :destroy def new @user_session = UserSession.new end def create @user_session = UserSession.new(params[:user_session]) if @user_session.save flash[:notice] = "Login successful!" redirect_back_or_default users_url else render :action => :new end end def destroy current_user_session.destroy flash[:notice] = "Logout successful!" redirect_back_or_default new_user_session_url end end [/ruby] and of course that also needs a view, in new.html.haml[ruby] %h1 Login = form_for @user_session, :url => {:action => "create"} do |f| = f.error_messages %div = f.label :login = f.text_field :login %div = f.label :password = f.password_field :password %div = f.check_box :remember_me = f.label :remember_me %div = f.submit "Login" [/ruby] We want to use the f.error_message, but that is now removed from Rails3 and we need to install a plugin instead: [ruby] rails plugin install git://github.com/rails/dynamic_form.git [/ruby] We also need to define, inside config/routes.rb. You will see that the generator will have added the route get 'user_sessions/new', but that is not enough. You will have to add: [ruby] resources :user_sessions match 'login' => "user_sessions#new", :as => :login match 'logout' => "user_sessions#destroy", :as => :logout [/ruby]

Restrict access

Suppose you now have some other controller, e.g. HomeController, then restricting acces is straightforward: [ruby] rails g controller home index [/ruby] [ruby] class HomeController < ApplicationController before_filter :require_user def index end end [/ruby] The method require_user, defined in ApplicationController, will check if there is a user logged on, and if not redirect to the login-page. If we now delete the index.html inside your public folder, and we add the following at the bottom of config/routes.rb[ruby] root :to => 'home#index' [/ruby] Now we can start our application, and it should redirect to our login-page. But of course, since we have no users for now, we can't login just yet. To allow testing, you fire up your console: [bash] $ rails c Loading development environment (Rails 3.0.0.rc) ruby-1.9.2-p0 > User.create(:login => 'test', :email => 'test@tester.com', :password => 'test123', :password_confirmation => 'test123') => #<User id: 1, login: "test", email: "test@tester.com", crypted_password: "0129d9733b7912017e37a50263901488da90e127e6fd1ae6081...", password_salt: "ygufdflWkJQZGhbsGyia", persistence_token: "957788609b2067092dd3852b01613d53f96d0692ce5131174ca...", login_count: 0, failed_login_count: 0, last_request_at: nil, current_login_at: nil, last_login_at: nil, current_login_ip: nil, last_login_ip: nil, created_at: "2010-08-29 14:11:04", updated_at: "2010-08-29 14:11:04"> ruby-1.9.2-p0 > [/bash] Now you should be able to login using this user. For completeness, you should add some links to your application-view to allow logging in and out: [ruby] #user_nav - if current_user = "Signed in as #{current_user.email}. Not you?" = link_to "Sign out", logout_path - else = link_to "Sign in", new_user_session_path [/ruby] This would get your rails3 project started. The next steps would be to add some user management, or allowing users to sign up themselves, and maybe add some roles to limit certain users access if needed. Now, to contrast this with devise: i now have a bunch of code in my application that actually is not specific to my code, but is also completely not tested. I will provide example rspec tests for this later. Actually, the problem with this scenario as outlined, is it is one big bang. You should start little, declaring things you need to be able to do, and work in little steps to get there. But this scenario is intended as a draft to see how you could get there. Now, in your real application, you should start with the tests, and then from this example you now how you can implement it.

setting up delayed_job in rails3

If you want to run longrunning jobs in the background, one very easy solution is using delayed_job. One other very interesting alternative is resque, but it seemed harder to setup (it uses redis), and delayed_job seemed to be just right for my needs. In short (from the redis documentation) : Choose Resque if:

  • You need multiple queues
  • You don't care / dislike numeric priorities
  • You don't need to persist every Ruby object ever
  • You have potentially huge queues
  • You want to see what's going on
  • You expect a lot of failure / chaos
  • You can setup Redis
  • You're not running short on RAM Choose DelayedJob if:
  • You like numeric priorities
  • You're not doing a gigantic amount of jobs each day
  • Your queue stays small and nimble
  • There is not a lot failure / chaos
  • You want to easily throw anything on the queue
  • You don't want to setup Redis And in my case: a short list of outstanding jobs, i am using a database already, not sure about redis, performance is not that important, i do not expect a lot of failure, i do not want to setup redis (yet :). So to use the delayed_job gem in a rails3 you will need to use the code from github (as the gem version 2.0.3 is not yet rails3 compatible). Add the following line to your Gemfile : [ruby] gem "delayed_job", :git => 'git://github.com/collectiveidea/delayed_job.git' [/ruby] and then run [bash] bundle install [/bash] Because i am using ActiveRecord, i can just use the generator, that will create the table and add the script to run a worker: [ruby] rails generate delayed_job rake db:migrate [/ruby] Include the rake tasks from delayed-jobs into your Rakefile: [ruby] begin require 'delayed/tasks' rescue LoadError STDERR.puts "Run bundle:install to install delayed_job" end [/ruby] Create an initializer file delayed_jobs_config.rb and write [ruby] # config/initializers/delayed_job_config.rb Delayed::Worker.destroy_failed_jobs = false #Delayed::Worker.sleep_delay = 60 #Delayed::Worker.max_attempts = 3 Delayed::Worker.max_run_time = 5.minutes [/ruby] If you are happy with the defaults, you can leave it. For me it was important to keep a record of the jobs that failed. Then you can just delay any function-call like this: [ruby] envelope.delay.query_status(delivery) [/ruby] Just adding the .delay does all the magic for you! And starting a worker is as simple as [ruby] rake jobs:work [/ruby]

Previously, in rails 2.3.8 i used the prototype-helpers link_to_remote and form_remote_for (amongst others). These had the option to add callbacks as follows: [ruby] link_to_remote "Add to cart", :url => { :action => "add", :id => product.id }, :update => { :success => "cart", :failure => "error" } [/ruby] (an example from the documentation). This example would, upon success update the html-element with class "cart", and upon failure the class "error". The possible callbacks were:

  • :loading: Called when the remote document is being loaded with data by the browser.
  • :loaded: Called when the browser has finished loading the remote document.
  • :interactive: Called when the user can interact with the remote document, even though it has not finished loading.
  • :success: Called when the XMLHttpRequest is completed, and the HTTP status code is in the 2XX range.
  • :failure: Called when the XMLHttpRequest is completed, and the HTTP status code is not in the 2XX range.
  • :complete : Called when the XMLHttpRequest is complete (fires after success/failure if they are present). Now the modus operandi has changed, instead we write: [ruby] link_to "Add to cart", :url => {:action => "add", :id => product.id}, :remote => true [/ruby] and it seems there is no option to set the callbacks anymore. Instead of a normal html, we now render javascript, like this (in jquery) : [ruby] $('.cart').replaceWith(<%= escape_javascript(render :partial => 'cart') %>) [/ruby] But how do you handle an error situation? Do i handle it in my controller, and use seperate views? It would seem useful to me to somehow be able to mimic the behaviour we had before. Luckily in this article I was able to find the solution. I had already found that in rails.js the following callbacks were checked:
  • ajax:beforeSend : triggered before executing the AJAX request
  • ajax:success : triggered after a successful AJAX request
  • ajax:complete : triggered after the AJAX request is complete, regardless the status of the response
  • ajax:error : triggered after a failed AJAX request, as opposite to ajax:success But i had no idea how to provide these callbacks. The javascript should be unobtrusive, so this coupling is not done straight in the HTML anymore. From the same article i found a very clear example how to solve this. Take the following Rails 2.3.8 code : [ruby] <% form_remote_tag :url => { :action => 'run' }, :id => "tool-form", :update => { :success => "response", :failure => "error" }, :loading => "$('#loading').toggle()", :complete => "$('#loading').toggle()" %> [/ruby] That translates to this in Rails3 : [ruby] <% form_tag url_for(:action => "run"), :id => "tool-form", :remote => true do %> [/ruby] and inside some javascript (application.js), you bind the events [javascript] jQuery(function($) { // create a convenient toggleLoading function var toggleLoading = function() { $("#loading").toggle() }; $("#tool-form") .bind("ajax:beforeSend", toggleLoading) .bind("ajax:complete", toggleLoading) .bind("ajax:success", function(data, status, xhr) { $("#response").html(status); }); }); [/javascript] For completeness, here is a list of the events and their expected parameters: [javascript] .bind('ajax:beforeSend', function(xhr, settings) {}) .bind('ajax:success', function(data, status, xhr) {}) .bind('ajax:complete', function(xhr, status) {}) .bind('ajax:error', function(xhr, status, error) {}) [/javascript] [UPDATED 7/2/2012] Updated to reflect the new event-names. :loading was renamed to :beforeSend, and :failure was renamed to :error.

I have created a Rails3 application, started with Haml/Sass and finding it awesome. I am also trying to do unobtrusive javascript. Before, in Rails 2.3, I would have expected a remote-form to have an :update attribute, where you could specify a selector where the response of the remote method would be rendered. Now it needs to be done differently: my controller function will render a ".js" view, which will do the necessary actions itself. This makes it more library agnostic, and i prefer jquery. So for instance i have a controller action "search", and i have a corresponding "search.js.erb" as follows: [ruby] $('.grid').replaceWith('<%= escape_javascript(render :partial => 'list') %>') [/ruby] This works. It will replace the html of the element with a class grid with the html from my rendered partial. I started out trying to achieve that in HAML, and failed at first. So first i created the above ERB code which worked. Now my task seemed simpler: translate this seemingly simple line to HAML. My first naive approach was to do the following: [ruby] = "$('.grid').replaceWith('#{escape_javascript(render :partial => 'list')}')" [/ruby] But this just places the html as a readable string inside my page. Even i do something simple like [ruby] = "$('.grid').replaceWith('<h3>Text</h3>') [/ruby] i see the actual characters [ruby] <h3>Text</h3> [/ruby] and not the markup. But i need to contain the javascript inside the double-quotes or haml will not recognise it, and can not interpolate my ruby there. After looking through the HAML reference, actually the solution was incredible simple. Once more. [ruby] != "$('.grid').replaceWith('#{escape_javascript(render :partial => 'list')}')" [/ruby] The != unescapes HTML (as opposed to the standard =). This is exactly what we need of course.

With Rails3 i can create a project fully cut to my needs. I will write it down here, just so i remember it well and hopefully it will help some of you too. In my projects, i want to use haml, rspec2, factory-girl, jquery, ... We need a few steps to complete this.

Create the project

For starters, create the folder, without Test::Unit (-T) and without prototype (-J). [ruby] rails new test-project -T -J [/ruby] You could also specify the database, using option -d, [--database=DATABASE], with the following options: `

mysql oracle postgresql sqlite3 (default) frontbase ibm_db`

Specify needed gems

We also have to specify the gems we need. To do that, we need to edit the Gemfile which is located in the root of your project. My file looks as follows: [ruby] source 'http://rubygems.org' gem 'rails', '3.0.0.rc' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' #gem 'pg' gem 'sqlite3-ruby', :require => 'sqlite3' # Use unicorn as the web server # gem 'unicorn' # Deploy with Capistrano # gem 'capistrano' # To use debugger # gem 'ruby-debug' gem 'rails3-generators' gem "bson_ext" gem "haml" gem "haml-rails" gem "jquery-rails" gem "rcov" # we need this here, see http://blog.davidchelimsky.net/2010/07/11/rspec-rails-2-generators-and-rake-tasks/ group :development, :test do gem "rspec-rails", ">= 2.0.0.beta.18" end # test-environment gems group :test, :spec, :cucumber do gem "factory_girl_rails" gem "rspec", ">= 2.0.0.beta.18" gem "remarkable", ">=4.0.0.alpha2" gem "remarkable_activemodel", ">=4.0.0.alpha2" gem "remarkable_activerecord", ">=4.0.0.alpha2" gem "capybara" gem "cucumber" gem "database_cleaner" gem "cucumber-rails" end [/ruby] Run bundle install to install all needed gems.

Use wanted generators

Then now edit the file application.rb, located in the config folder, and add the following lines: [ruby] # Configure generators values config.generators do |g| g.test_framework :rspec, :fixture => true g.fixture_replacement :factory_girl, :dir=>"spec/factories" end [/ruby] This will make sure that the generators use our own defaults. So now when you type [ruby] rails generate model TestModel name:string rails generate controller TestModel [/ruby] and it will create haml views, rspec tests, and factories. Awesome :)

Prepare to use jquery!

To start using jQuery in Rails3, you have to first get the rails.js specifically for jQuery (stored in the jquery-ujs project). Save it in your public\javascripts directory. Download jquery, and make sure to include both in your application layout or view. In HAML it would look something like. [ruby] = javascript_include_tag 'jquery.min.js' = javascript_include_tag 'rails' [/ruby] BUT: there is a quicker route! We also installed the gem jqeury-rails, which has the following generator if we want to install jquery in one go! [ruby] rails g jquery:install #--ui to enable jQuery UI [/ruby] Rails requires an authenticity token to do form posts back to the server. This helps protect your site against CSRF attacks. In order to handle this requirement the driver looks for two meta tags that must be defined in your page's head. Luckily rails makes it easier for us, again, and we just need to include csrf_meta_tag somewhere inside your page's head (rails3 does this default in your application.html.erb). Be sure to include it in your haml code too. An example application.html.haml would look like this: [ruby] !!! Strict %html{ "xml:lang" => "en", :lang => "en", :xmlns => "http://www.w3.org/1999/xhtml" } %head %title== Test-project #{@page_title} %meta{'http-equiv' => "Content-Type", :content => "text/html; charset=utf-8"} %link{ :href => "/favicon.ico", :rel => "shortcut icon" } = stylesheet_link_tag :all = javascript_include_tag 'jquery-1.4.2.min.js', 'rails.js', 'application.js', :cache => 'all_1' = csrf_meta_tag = yield :local_javascript = yield :head %body #banner =image_tag 'your-logo.png' .the-title %h1 %p Your application #navigation #container #sidebar #contents =yield #footer == © 2010 your company [/ruby]

Done! :)

rails3 and rspec2 view testing

Testing the view in Rspec is just great. It allows you to test the view in total isolation. It just does the render. Make sure you set up some data that is needed, and then check if all areas are available for instance. Now for Rspec2 a lot of things have changed:

  • have_tag is no longer supported: use have_selector from webrat instead
  • response is deprecated, use rendered instead
  • the assignment has changed, write assign(:events, [stub_model(Event)]) instead of using the old assigns[:events]=[...] notation Let me show you a full example, because that always makes things clearer: [ruby wraplines="false"] require 'spec_helper' DUMMY_NUMBER="dummy number" DUMMY_MESSAGE="dummy message" describe "envelopes/index.html.haml" do before (:each) do envelope = mock_model(Envelope) envelope.should_receive(:destination).and_return(DUMMY_NUMBER) envelope.should_receive(:message).and_return(DUMMY_MESSAGE) assign(:envelopes, [envelope]) render end it ("has a h1") { rendered.should have_selector('h1')} it ("shows the correct title") { rendered.should contain(t('envelopes.list.title')) } it ("have a table-grid") { rendered.should have_selector('.grid') } it ("shows the destination") { rendered.should contain(DUMMY_NUMBER) } it ("shows the message") { rendered.should contain(DUMMY_MESSAGE) } end [/ruby]
rewriting find_by_sql for rails 3

I am starting in Rails 3. I have a simple situation where i would select a parent based on a condition of one of its children. Normally i would write something like: [ruby] Distributor.find_by_sql("select d.*, c.rate from distributors d, coverages c where c.country_code_id = 25 and c.distributor_id=d.id order by rate asc") [/ruby] Although this works, it is not the best solution to take. For one, it is vulnerable to sql-injection and secondly it shows a lot of the underlying database structure, and on top of that the sql-implementation could change between databases. In Rails 3 i can now write [ruby] Distributor.includes(:coverages).where("coverages.country_code_id=? and distributors.id =coverages.distributor_id", 25).order(:rate) [/ruby] This is so readable! Awesome :)