1
\$\begingroup\$

I needed to add existing models to a has-many relation using nested attributes so I overwrote thumbnails_attributes=:

class Gallery < ActiveRecord::Base
  has_many :thumbnails, dependent: :nullify

  accepts_nested_attributes_for :thumbnails

  def thumbnails_attributes=(thumbnails_attributes)
    ids = thumbnails_attributes.map { |t| t['id'] }
    (ids - thumbnail_ids).each do |id|
      thumbnail = Thumbnail.find id
      thumbnails << thumbnail
    end
    super(thumbnails_attributes)
  end
end



class Thumbnail < ActiveRecord::Base
  belongs_to :gallery
end

Given the following data (as fixture):

thumbnails.yml

one:
  gallery: one

two:
  gallery:

galleries.yml

one:
  name: MyString

I can now run:

galleries(:one).update(thumbnails_attributes: ['id' => thumbnails(:two).id])

and galleries(:one).thumbnails will return both thumbnails.

While it works pretty well, I wanted to ask for reviews and ways to improve/clean this code.

The only issue I have is with include? (which is used by assert_includes in my tests):

galleries(:one).update(thumbnails_attributes: ['id' => thumbnails(:two).id])
galleries(:one).thumbnails.include? thumbnails(:two) # false
galleries(:one).thumbnails                           # [#<Thumbnail one>, #<Thumbnail two>]
galleries(:one).thumbnails.include? thumbnails(:two) # true

It looks like I have to reload the relation for include? to work, but I haven't found a fix nor a way to properly test it.

galleries(:one).thumbnails.to_a.include? thumbnails(:two) works, include? doesn't appear to load the association.

\$\endgroup\$
3
\$\begingroup\$

Couple of things to improve here:

Firstly - do not use fixtures, use factories. There is a neat library allowing you to do this with ease called FactoryGirl (for rails you will need FactoryGirlRails). Having this in place you can define factories like:

FactoryGirl.define do
  factory :thumbnail do
    name 'Thumbnail name if you have such a field'
    other_field :other_value
  end
end

And then use it as:

thumbnail = FactoryGirl.create :thumbnail

One advantage of factories is that it does not load all the data into the database for each test, but rather allows you tell it what is needed instead.

There are quite a lot of options you can add to your factories, like dynamic field values (useful when you have uniqueness validations), building parameters, building associated objects etc. All are well documented on gem github wiki.

Secondly, accepts_nested_attributes_for already provides you with a method to assign objects using their ids:

@model.children_ids << [1,2,3]

or even modify the whole association in one go

@model.children_ids = [1,2,3]

This is usually more useful in terms of web forms. If you have a form where client can select multiple objects from a list from db, all you need to do is to name those fields children_ids and rails will take care of persisting the association.

\$\endgroup\$
  • \$\begingroup\$ The children_ids array is exactly what I was looking for, thank you. FactoryGirl was quite a burden to setup (I had to use database_cleaner and minitest-around) but might prove useful in the future. \$\endgroup\$ – ivann Nov 27 '14 at 14:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.