Skip to content

Has many :through association

asmolero edited this page Mar 12, 2012 · 23 revisions

Synopsys:

class Grid < ActiveRecord::Base
    
  has_many :block_grid_associations, :dependent => :delete_all, :autosave => true, :include => :block
  has_many :blocks, :through => :block_grid_associations
  
  # for a multiselect widget: (natural choice for n-n associations)

    attr_accessible :block_ids
    # if you need ordered blocks inside each grid (assuming a position column in block_grid_associations table)
    def block_ids=(ids)
      unless (ids = ids.map(&:to_i).select{|i|i>0}) == (current_ids = block_grid_associations.map(&:block_id))
        (current_ids - ids).each { |id| block_grid_associations.select{|b|b.block_id == id}.first.mark_for_destruction }
        self.blocks = ids.each_with_index.map do |id, index|
          if current_ids.include?(id)
            (block_association = block_grid_associations.select{|b|b.block_id == id}.first).position = (index+1)
            block_association
          else
            block_grid_associations.build({:block_id => id, :position => (index+1)})
          end
        end.map(&:block)
      end
    end

  # for a nested form: (no reordering)
   
    accepts_nested_attributes_for :blocks, :allow_destroy => true
    attr_accessible :blocks_attributes
end

# for info

class Block < ActiveRecord::Base
  has_many :block_grid_associations, :dependent => :delete_all
  has_many :grids, :through => :block_grid_associations
end

class BlockGridAssociation < ActiveRecord::Base
  belongs_to :block
  belongs_to :grid
  default_scope order(:position) # if you need ordered blocks
end

RailsAdmin.config do |config|
  config.model Grid do
    configure :block_grid_associations do
      visible(false)
    end

    configure :blocks do
      orderable(true) # only for multiselect widget currently. Will add the possibility to order blocks
      # configuration here
    end
  end
end

Note: has_many :through associations are not considered any differently from vanilla has_many association. In particular, no special help is provided to edit join table attributes; you can edit indifferently the join-table or the target table.

Alternative

The code above didn't work for me. There were some join models created with the position attribute set, and then the self.blocks assignment created new join models with a nil position. So, why not just do the assignment first and then update the positions? I don't know if the code is equivalent to the former, but it works for me and it's a lot simpler.

def block_ids=(ids)
  unless (ids = ids.map(&:to_i).select { |i| i>0 }) == block_grid_associations.map(&:block_id)
    self.blocks = Block.find(ids)

    ids.each_with_index do |id, index|
      block_grid_associations.select { |b| b.block_id == id }.first.position = (index+1)
    end
  end
end

More here (has_many)

Clone this wiki locally