12 MarPolymorphic has_and_belongs_to_many

Friday, 12 March 2010 — 16:32

We faced today a problem where we needed a polymorphic has_and_belongs_to_many (habtm) implemented on Rails. Let me first explain the problem.

We want to let the user tag content in the site. This content can go from a post, to an image, a video,… sounds like a polymorphic relationship no? :-). One more requirement: we want to have Tags table as a unique list of tags. So a user can tag an image or a video with “cool”, but we will just have one entry in tags table for that word. And of course, same will happen if someone else uses same tag.

If we had just images, that would be a has_and_belongs_to_many case. As we have more than one taggeable object, that’s a polymorphic habtm.

This is how we implemented it, using a middle model class (TagsLinks) which is basically the polymorphic habtm table:

#Post
  has_many :tag_links, :as => :resource
#Image
  has_many :tag_links, :as => :resource
#TagLinks
  belongs_to :user
  belongs_to :resource, :polymorphic => true
  belongs_to :tag
#Tag:
  has_many :tag_links

Ger

12 Marattr_accessible vs attr_protected

Friday, 12 March 2010 — 09:01

I found recently some code where developers tend to have a never-ended list of model attributes like this:

# accessibles
  attr_accessible :category_type_id, :subcategory_type_id, 
                             :cause, :treatment, :prevention, :symptoms, 
                             :recovered, :finishes_at, :historical_id

or

# accessibles
  attr_accessible :category_type_id

This is usually an indicator that something is wrong, moreover when those are almost all the model attributes (first example). attr_accessibles allows mass assignment ONLY on those attributes in the list. That means we have to use write methods for attributes that are not on the list (second example). If you add a new column to that table/model, you need to remember to add that column to the attr_accessibles list.

Probably, it would have been more appropriate to protect JUST those critical attributes with this:

#attr_protected
attr_protected :user_id

In both cases the effect is the same:

record = Record.new(:category_type => "injury", : subcategory_type_id => "ankle", 
                 :treatment => "band", :user_id => '17823456S')
record.user_id # => nil

Ger

11 MarActiveRecord lifecycle

Thursday, 11 March 2010 — 16:24

Most rails developers know the language provides us with callbacks that allow us to run some logic before the ActiveRecord object is saved or updated. However, not everyone knows where to place this logic in the ActiveRecord cycle. This is the execution order and available callbacks, for a save/update instruction:

save
valid
(1) before_validation
(2) before_validation_on_create/ on_update
validate
validate_on_create/on_update
(3) after_validation
(4) after_validation_on_create/on_update
(5) before_save/before_update
(6) before_create

Very frequent bugs consist of making changes (that might break validations) on the model making use of before_save callback. If you do so, you might be filling your database with non valid records, as validations will not be run. Use before_validation in those cases

Ger

08 MarEnable logs in console

Monday, 08 March 2010 — 09:38

If you are one of those developers that like to test things on console before you add them to your code, i’m sure you’ll be missing some logs in your ruby console.

In order to see these logs you just need to run these simple commands:

ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Base.clear_active_connections!

Ger

06 MarActiveRecord.save without callbacks

Saturday, 06 March 2010 — 09:59

You already know there is a way to avoid running validations when you save an ActiveRecord

project.save(false)

But, what if you want to skip validations?. There are some ways to achieve this. There are some metaprogramming options, such as adding and removing methods, or calling private methods with send (or send!)

project = Project.new(:name => 'ggomeze.com')
project.send(:create_without_callbacks)

project = Project.find(1)
project.send(:update_without_callbacks)

If these methods are private, it just because there should be a different way to do the same, no? :-). So here is my favorite:

# accessors
  attr_accessor :skip_callbacks

# callbacks
  with_options :unless => :skip_callbacks do |project|
    project.after_update :do_something
    project.after_save :do_other_thing
  end

Ah, just a note, you might be tempted to use this, instead of attr_accessor:

attr_accessor_with_default :skip_callbacks, false

but seems like attr_accessor_with_default creates a class attribute which might be problematic under some circumstances

Ger