module SourceIds::ClassMethods

Public Instance Methods

accepts_source_ids_for(source_association_name, options = {}) click to toggle source
# File lib/source_ids.rb, line 9
def accepts_source_ids_for(source_association_name, options = {})
  source_association = reflect_on_association(source_association_name)
  raise "Could not find association #{source_association_name}" unless source_association
  relation = source_association.through_reflection
  raise "#{source_association_name} does not have :through" unless relation
  source_name = source_association.source_reflection.name
  fk_in_relation = source_association.source_reflection.foreign_key
  source_ids_name = options[:as] || "#{source_association_name.to_s.singularize}_ids"

  # _source_ids=
  define_method "_#{source_ids_name}=" do |ids|
    ids = ids.find_all(&:present?).map(&:to_s) # care for hidden field of collection_select

    # removing
    removed = send(relation.name).find_all{|r| !ids.include?(r.send(fk_in_relation).to_s) }
    removed.each do |r|
      if r.new_record?
        send(relation.name).delete(r)
      else
        r.mark_for_destruction
      end
    end

    ids.each do |source_id|
      unless send(relation.name).detect{|r| r.send(fk_in_relation).to_s == source_id}
        send(relation.name).build(fk_in_relation => source_id)
      end
    end

    send(relation.name).target.sort! do |a, b|
      if a.marked_for_destruction? && b.marked_for_destruction?
        0
      elsif a.marked_for_destruction?
        -1
      elsif b.marked_for_destruction?
        1
      else
        ids.index(a.send(fk_in_relation).to_s) <=> ids.index(b.send(fk_in_relation).to_s)
      end
    end
  end

  # _source_ids
  define_method "_#{source_ids_name}" do
    send(relation.name).find_all{|r| !r.marked_for_destruction?}.map(&fk_in_relation.to_sym)
  end

  # _sources
  define_method "_#{source_association_name}" do
    send(relation.name).find_all{|r| !r.marked_for_destruction?}.map(&source_name)
  end
end