[Be el o ge]

I ♥ The Web

[Be el o ge]

Entries Tagged as 'RubyOnRails'

Serialize / Deserialize nested objects in ActiveRecord

June 26th, 2010 · No Comments

Using ActiveRecord’s serialize method you can save Ruby objects in a single field of your model. The serialization is done through YAML.

Today I tried to save a nested object structure with this method. These are the classes:

class PhotoStyle
  attr_accessor :image
 
  def initialize
    @image = StyleElement.new
  end
end
 
class StyleElement
  attr_accessor :selector, :style
 
  def initialize
    @selector = '*'
    @style = {}
  end
end

The model which saves a PhotoStyle object:

class Photo < ActiveRecord::Base
  serialize :style, PhotoStyle
end

This first approach didn’t quite work out. Saving the model worked fine, but when I loaded it only PhotoStyle got deserialized to the original type. The nested StyleElement didn’t:

p = Photo.last
p.style
=> #<photostyle :0xb70a906c @image=#<YAML::Object:0xb72079a4 @ivars={"selector"=>"#theimage", "style"=>{"border"=>"0", "box-shadow"=>"1px 3px 15px #555"}}, @class="StyleElement">}, @class="PhotoStyle">>
</photostyle>

Note the YAML object assigned to the @image instance variable.

I'm not sure if that's a bug in Rails3 or if that is by design. You can however "fix" it by running this code snippet when your application starts up:

YAML::Syck::Resolver.class_eval do
  def transfer_with_autoload(type, val)
    match = type.match(/object:(\w+(?:::\w+)*)/)
    match && match[1].constantize
    transfer_without_autoload(type, val)
  end
  alias_method_chain :transfer, :autoload
end

Save it e.g. in yaml_autoloader.rb in the config/initializers folder of your app.

Now all types will autoload:

#<photostyle :0xb73dcb08 @image=#<StyleElement:0xb73dd058 @selector="#theimage", @style={"border"=>"0", "box-shadow"=>"1px 3px 15px #555"}>> 
</photostyle>

I found above code snippet here. Thanks!

Read more about ActiveRecord's serialize method here: Simple user preferences for your Rails app.

Update:
Still not sure if that's a bug, but I submitted a lighthouse ticket.

Update 2:
Apparently this is not a bug in Rails, but a problem in my code :-) Please see Aaron's comment in above ticket.

  • Delicious
  • Twitter
  • Facebook
  • LinkedIn
  • Reddit
  • Slashdot
  • Technorati Favorites
  • Digg
  • Share/Bookmark

[Read more →]

Tags: Ruby · RubyOnRails

Heroku, Rails 3, RVM and Ubuntu

June 15th, 2010 · No Comments

Today I tried to use Heroku on Ubuntu / RVM for a Rails 3 test app. I ran into a similar problem like I had yesterday with OpenSSL.

After installing the Heroku gem:

gem install heroku

and running the command for create a new app

heroku create

I got this error

/home/siebel/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require': no such file to load -- readline (LoadError)
	from /home/siebel/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
	from /home/siebel/.rvm/gems/ruby-1.8.7-p249/gems/heroku-1.9.10/lib/heroku/commands/app.rb:1
	from /home/siebel/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
	from /home/siebel/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
	from /home/siebel/.rvm/gems/ruby-1.8.7-p249/gems/heroku-1.9.10/lib/heroku/command.rb:5
	from /home/siebel/.rvm/gems/ruby-1.8.7-p249/gems/heroku-1.9.10/lib/heroku/command.rb:5:in `each'
	from /home/siebel/.rvm/gems/ruby-1.8.7-p249/gems/heroku-1.9.10/lib/heroku/command.rb:5
	from /home/siebel/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require'
	from /home/siebel/.rvm/rubies/ruby-1.8.7-p249/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require'
	from /home/siebel/.rvm/gems/ruby-1.8.7-p249/gems/heroku-1.9.10/bin/heroku:7
	from /home/siebel/.rvm/gems/ruby-1.8.7-p249/bin/heroku:19:in `load'
	from /home/siebel/.rvm/gems/ruby-1.8.7-p249/bin/heroku:19

Now, the first line looks darn familiar and so does the solution:

First install libreadline-dev:

sudo apt-get install libreadline-dev

Then cd into

~/.rvm/src/ruby-1.8.7-p249/ext/readline

and build the extension:

ruby extconf.rb
make
make install

That’s it… heroku should run after that without problems.

  • Delicious
  • Twitter
  • Facebook
  • LinkedIn
  • Reddit
  • Slashdot
  • Technorati Favorites
  • Digg
  • Share/Bookmark

[Read more →]

Tags: RubyOnRails

OpenSSL, Rails 3 and Ubuntu

June 14th, 2010 · 1 Comment

I’m using RVM on my Ubuntu box. This way I can nicely install several Ruby and Rails versions next to each other. With Rails 3 however I had a little bit of trouble. When starting the server using

rails server

the server started fine, but as soon as I clicked “About your application’s environment”, I got this error on the console:

LoadError: no such file to load -- openssl

If you’re getting this too the first thing to check is whether these packages are installed:

  • openssl
  • libssl-dev
  • libssl0.9.8

If they are and it anyway doesn’t work you want to go to the source code of your Ruby installation. In my case the source is in this path:

~/.rvm/src/ruby-1.8.7-p249

Now you cd to ext/openssl and run the following commands:

ruby extconf.rb
make
make install

After restarting WEBrick, everything was good:

For a more complete description on how to install Rails 3 on Ubuntu using RVM, checkout this blog post by Rohit Arondekar: Installing Rails 3.0 Beta 3 on Ubuntu using RVM

  • Delicious
  • Twitter
  • Facebook
  • LinkedIn
  • Reddit
  • Slashdot
  • Technorati Favorites
  • Digg
  • Share/Bookmark

[Read more →]

Tags: Linux · RubyOnRails

Mobile_Fu, Internet Explorer and respond_to

June 1st, 2010 · No Comments

Today I noticed that List Kung Fu displays the mobile version of the list page when I browse there with regular desktop Internet Explorer. Surfing to the same page with Firefox or Opera on the same machine worked fine and just returned the regular HTML page.

So what’s up?

Simple… Internet Explorer sends a really funny accepts header, none of the formats actually is text/html. Thus HTML is covered with an implicit */*. That’s not awesome (in good IE manner), but not really a problem. A problem it just becomes with something like this in your controller action:

      respond_to do |format|
        format.mobile
        format.html
      end

Since Internet Explorer doesn’t send an explicit accept for text/html, Rails would just default to the first format, in above case format.mobile and then correctly return the mobile version of the view. The fix is easy. Just move format.html to the first position:

      respond_to do |format|
        format.html
        format.mobile
      end

Now */* would default to format.html.

  • Delicious
  • Twitter
  • Facebook
  • LinkedIn
  • Reddit
  • Slashdot
  • Technorati Favorites
  • Digg
  • Share/Bookmark

[Read more →]

Tags: RubyOnRails

Simple user preferences for your Rails app

May 9th, 2010 · 2 Comments

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.

  • Delicious
  • Twitter
  • Facebook
  • LinkedIn
  • Reddit
  • Slashdot
  • Technorati Favorites
  • Digg
  • Share/Bookmark

[Read more →]

Tags: RubyOnRails