Category Archives: RubyOnRails

Simple user preferences for your Rails app

In almost every project you need to save settings for the users of your application. One way to do that is simply adding more and more columns to your user table. If you did that for 10 or more settings you soon will realize that this doesn’t scale very well and the user model gets quickly a lot of attributes.
Another possibility would be to create another model, let’s call it UserPreferences, and create a User has many UserPreferences association. This is a fine solution, but I don’t like it much either because it’s not very flexible. In case you want to add another setting you need to create a migration and so on.

ActiveRecord has many cool features and one them is the ability to serialize a Ruby classes and save it away in an attribute of a model. Actually the example usage the documentation suggests is exactly about user preferences. Now, in the example they’re storing simply a hash, which is fine, but I decided I go ahead and create a class for user preferences:

class UserPreference
 
  attr_accessor :option1, :option2
 
  def initialize
    @option1 = nil
    @option2 = nil
  end
end

In order to save these user preferences in my user model, I added a column prefs:

class User < ActiveRecord::Base
  serialize :prefs, UserPreference
 
  def prefs
    # make sure we always return a UserPreference instance
    if read_attribute(:prefs).nil?
      write_attribute :prefs, UserPreference.new
      read_attribute :prefs
    else
      read_attribute :prefs
    end
  end
 
  def prefs=(val)
    write_attribute :prefs, val
  end
end

As you can see I overrode the getter and setter for the prefs attribute to be able to return always an instance of the UserPreference class. So far so good, by now you can do things like

user = User.find(1)
user.prefs.option1 = "hello world"
user.save!

If you need another setting, you simply can add an attribute to the UserPreference class and use it right away, without the need to create a migration for it.

After finishing that, I thought it would be handy to have these settings available in the user session. I'm using Authlogic, so I just added a method to the create method of the user_sessions controller:

  def create
    @user_session = UserSession.new(params[:user_session])
    @user_session.save do |result|
        [...]
        # load User Preferences into session
        load_user_preferences
        [...]
    end
  end

load_user_preferences looks like this:

  def load_user_preferences
    current_user.prefs.instance_variable_names.each do |v|
      session[v[1..-1].to_sym] = current_user.prefs.instance_variable_get v
    end
  end

Now you have super simple access to the current users settings through the session:

value = session[:option1]

In case some of the code which gets executed for a certain request changes e.g. session[:option1], I added an after_filter which looks like this:

  def save_user_prefs_if_changed
    changed_something = false
    current_user.prefs.instance_variable_names.each do |v|
      key = v[1..-1].to_sym
      unless current_user.prefs.instance_variable_get(v) == session[key]
        current_user.prefs.instance_variable_set v, session[key]
        changed_something = true
      end
    end unless current_user.nil?
 
    current_user.save! if changed_something && current_user
  end

This ensures that changed values in the session do get saved to the users preferences and are available when the user signs in again.

I like this approach because of its flexibility and usage of the session. It's not ideal when a preference contains a lot of data that you don't need often. You then wouldn't like to store that in the session. In that case I was thinking about introducing a naming convention to prevent the preference from being stored in the session. For ListKungFu.com however I didn't run into this situation yet.

DeliciousTwitterFacebookLinkedInRedditSlashdotTechnorati FavoritesDiggShare
Posted in RubyOnRails | 7 Comments

Nasty bug when using your Rails / jQuery powered app with Chrome

Today I ran into a nasty bug which is only reproducible in Google Chrome. In Firefox the very same code worked perfectly fine.

The code was supposed to work like this: After the user clicked the delete icon, jQuery would send an ajax request to the server, which then would delete the list. In the call back the UI gets updated by removing the list entry element. It’s as simple as that. The HTTP method was set to DELETE to be clean. In Firefox that worked great, in Google Chrome not at all. However you didn’t get a client side error, at least not at first, but a Rails error which would look like this:

Error occurred while parsing request parameters.
Contents:
 
[object Object]
/!\ FAILSAFE /!\  Sat Feb 20 18:56:39 +0100 2010
  Status: 500 Internal Server Error
  undefined method `name' for nil:NilClass
    /home/siebel/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/whiny_nil.rb:52:in `method_missing'
    /home/siebel/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/xml_mini/rexml.rb:29:in `merge_element!'
    /home/siebel/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/xml_mini/rexml.rb:18:in `parse'
    /home/siebel/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/xml_mini.rb:12:in `__send__'
    /home/siebel/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/xml_mini.rb:12:in `parse'
    /home/siebel/.gem/ruby/1.8/gems/activesupport-2.3.5/lib/active_support/core_ext/hash/conversions.rb:164:in `from_xml'
    /home/siebel/.gem/ruby/1.8/gems/actionpack-2.3.5/lib/action_controller/params_parser.rb:34:in `parse_formatted_parameters'
    /home/siebel/.gem/ruby/1.8/gems/actionpack-2.3.5/lib/action_controller/params_parser.rb:11:in `call'
    /home/siebel/.gem/ruby/1.8/gems/actionpack-2.3.5/lib/action_controller/session/cookie_store.rb:93:in `call'
    /home/siebel/.gem/ruby/1.8/gems/actionpack-2.3.5/lib/action_controller/failsafe.rb:26:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `synchronize'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/lock.rb:11:in `call'
    /home/siebel/.gem/ruby/1.8/gems/actionpack-2.3.5/lib/action_controller/dispatcher.rb:114:in `call'
    /home/siebel/.gem/ruby/1.8/gems/actionpack-2.3.5/lib/action_controller/reloader.rb:34:in `run'
    /home/siebel/.gem/ruby/1.8/gems/actionpack-2.3.5/lib/action_controller/dispatcher.rb:108:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rails-2.3.5/lib/rails/rack/static.rb:31:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:46:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:40:in `each'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/urlmap.rb:40:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rails-2.3.5/lib/rails/rack/log_tailer.rb:17:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/content_length.rb:13:in `call'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/handler/webrick.rb:50:in `service'
    /usr/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'
    /usr/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'
    /usr/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'
    /usr/lib/ruby/1.8/webrick/server.rb:162:in `start'
    /usr/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'
    /usr/lib/ruby/1.8/webrick/server.rb:95:in `start'
    /usr/lib/ruby/1.8/webrick/server.rb:92:in `each'
    /usr/lib/ruby/1.8/webrick/server.rb:92:in `start'
    /usr/lib/ruby/1.8/webrick/server.rb:23:in `start'
    /usr/lib/ruby/1.8/webrick/server.rb:82:in `start'
    /home/siebel/.gem/ruby/1.8/gems/rack-1.0.1/lib/rack/handler/webrick.rb:14:in `run'
    /home/siebel/.gem/ruby/1.8/gems/rails-2.3.5/lib/commands/server.rb:111
    /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
    /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require'
    /home/siebel/projects/Memento/script/server:3
    -e:1:in `load'
    -e:1

Since the server returned a HTTP 500 error, the client side displayed a message to the user that an error occurred. That certainly was ok, but why the hack did Rails throuw an error?

I had to google a while till I found this ticket: Bug in Chromium with GET parameters when doing a POST request

So it looked like something is going wrong when you send URL parameters alongside during a POST, PUT or DELETE. Since I’m using a URL parameter to specify the locale, I first thought that’s the issue: http://localhost:3000/lists/14?locale=en. Sadly removing it didn’t help.
While searching through the code I realized that for non-GET requests I was adding data to the PUT, DELETE, POST URL by this code:

$(document).ajaxSend(function(event, request, settings) {
        settings.data = { SomeParameter: "value" };
});

Once I commented out this one line things started working. I could finally delete the list using Chrome. The question now: How do I send this additional data. Fun thing: I didn’t have to … it was already added by other parts of the code. So the real issue here was that I had two parameters with the same name in my request. This was gracefully handled but Firefox but not by Google Chrome.

DeliciousTwitterFacebookLinkedInRedditSlashdotTechnorati FavoritesDiggShare
Posted in jQuery, RubyOnRails | 1 Comment

Rails: DEPRECATION WARNING: @var will no longer be implicitly assigned to local_var.

I just ran into an interesting warning and since it took me a few minutes to figure it out I thought I share it here.

I’m rendering a partial template like this:

render :partial => 'list_items/list_item', :locals => { :list => @list, :list_items => @list_item}

Now, when running this code I got this deprecation warning:

DEPRECATION WARNING: @list_item will no longer be implicitly assigned to list_item. 
(called from _run_erb_app47views47list_items47_list_item46html46erb_locals_list_list_item_list_items_object 
at /home/stefan/projects/Memento/app/views/list_items/_list_item.html.erb:1)

The problem here was that in the partial I’m using the local variables are named list and list_item, in the render call however I’m passing in list and list_items. Everything still works because rails implicitly assigns @list_item to the local variable list_item. This is what the warning says…

So the fix is simply removing the additional “s”.

render :partial => 'list_items/list_item', :locals => { :list => @list, :list_item => @list_item}

Not specifically hard but yet another prove that taking “a close look” at your own code is always difficult ;-) I bet a peer what have see the problem in a sec …

DeliciousTwitterFacebookLinkedInRedditSlashdotTechnorati FavoritesDiggShare
Posted in RubyOnRails | Leave a comment

Learning Rails: content_for

Learning Rails on a real project (ListKungFu.com) is great fun and you’ll find out everyday something else which makes Rails the awesome framework it is. In the beginning of cause, that are rather small but unbelievable convenient things, in this case that is: The content_for helper command.

In an application as simple as List Kung Fu most views are structured like that:

So we have the ListKungFu logo, a profile and a logout link in the header and some notes in the footer. All this you typically would find in a layout file (application.html.erb) because it’s likely you wanna display these elements on each page of your application. The more page specific data is rendered using a template, in this case show.html.erb.

List Kung Fu uses a fair amount of Javascript. My first approach was putting everything in a single .js file and loading it in application.html.erb. That’s ok for the beginning but becomes pretty soon pretty ugly. First, you end up with one big Javascript file which is not really nice for maintaining, second and that’s much worse: The user’s browser is loading the Javascript code for the complete application even though just a fraction of it is used per page.

Obviously the first thing you wanna do is splitting up the big Javascript file. My splitting strategy goes like this:

  • Code which is used all over the application and which might be useful even in different applications I put into application.js. This file in included in the layout file.
  • Second, I created a file called listkungfu.js containing Javascript which is also used over the whole application but is more specific and therefore unlikely to be reused in another application.
  • All Javascript code which is specific to just a small part of a application, e.g. a single view, I put into a separate file. Following above example there is for instance a file called lists_show.js for the show.html.erb view.

All we have to do now is including application.js and listkungfu.js in the layout file and lists_show.js in the view template.

< %=javascript_include_tag 'lists_show' %>

Done.

Hm… after loading the view in the browser and looking at the generated HTML you might not be satisfied a 100%. The Javascript files included in application.html.erb nicely line up at the bottom of the page, but lists_show.js is loaded somewhere in the middle of the HTML.

So, how do we move it down? You might have guessed it: content_for to the rescue!

Let’s replace above code with:

< % content_for :addtional_scripts do %>
    < %=javascript_include_tag 'lists_show' %>
< % end %>

And yield the content at the end of the layout file:

    < %= javascript_include_tag 'application', 'listkungfu' %>
    < %= yield(:addtional_scripts) %>

After reloading the view in the browser you’ll find the view specific Javascript file loaded after the two general files.

Very cool!

content_for can be used for a dozen other things, too. Think about having a side bar defined in the layout displaying view specific information, etc…

DeliciousTwitterFacebookLinkedInRedditSlashdotTechnorati FavoritesDiggShare
Posted in RubyOnRails | 1 Comment

Using Partials in Ajax Callbacks

When you start using Rails you pretty soon start using partials for saving forms in an extra template in order to re-use them in the different views of a CRUD interface.

If you go just a little bit further you’ll find out that partials are also very useful when building Ajax applications with Rails. Often when you do an Ajax call the one thing you want to do in the callback is updating the DOM of the page.

When the user clicks on the plus-button in above user interface, a form will popup which allows him to add another list. Once the form is filled in it will be send to the server using an Ajax call. Finally in the callback the new list will be added to the DOM.
So there are two situations when you want to render a list:

  1. When the page with all available lists is rendered.
  2. When the user adds a new list.

This sounds like the perfect use-case for a partial, so let’s create one. For above user interface the ERB template could look like this:

1
2
3
4
5
6
7
8
9
10
<li id="list_<%=list.id%>" class="list">
    <div>
        <h1>< %= list.title == '' ? '<em>enter a title' : list.title  %></h1>
        <div class="summary">
          < %= pluralize list.list_items.length, 'item' %>
          , < %= pluralize list.list_items_done, 'item' %> done.<br />
          Last updated < %= distance_of_time_in_words list.updated_at, Time.now %> ago
        </div>
    </div>
</li>

We save this code in a file named _list.html.erb.

In the view which generates the complete list of lists, we call the partial from within an each loop (index.html.erb):

1
2
3
< %- @lists.each do |list| -%>
  < %= render :partial => 'lists/list', :object => list %>
< %- end -%>

If you look back to the partial code in _list.html.erb, you’ll notice a local variable being used called list. By adding the parameter :object => list to the render call in index.html.erb we make this variable available in the partial.

Let’s finally have a look at the view for the Ajax callback. It’s saved in create.js.erb and can be as easy as this:

1
2
    var new_list = "< %=escape_javascript(render :partial => 'lists/list', :object => @list)%>";
    $("#all_lists").prepend(new_list);

All we do is assigning the list-HTML to a Javascript variable. For this we need to wrap the render call into an escape_javascript. Next we prepend this list to the ul-element using jQuery. That’s it.

Even though this is not a complete code example I hope it’ll point into the right direction. Please post questions as comments to this post.

DeliciousTwitterFacebookLinkedInRedditSlashdotTechnorati FavoritesDiggShare
Posted in RubyOnRails | Leave a comment