Sasha
Sasha

Reputation: 3281

rails model association using has_many: through

i have a lessons table and a tags table. i associate both of the them using a has_many :through relationship and my middle table is tags_relationship.rb

class Lesson < ActiveRecord::Base
  attr_accessible :title, :desc, :content, :tag_name
  belongs_to :user

  has_many  :tag_relationships
  has_many  :tags, :through => :tag_relationships
end

class Tag < ActiveRecord::Base
  attr_accessible :name

  has_many :tag_relationships
  has_many :lessons, :through => :tag_relationships
end

in one of my views, im trying to create a a virtual attribute. i have...

    <div class="tags">
        <%= f.label :tag_name, "Tags" %>
        <%= f.text_field :tag_name, data: { autocomplete_source: tags_path} %>
    </div>

but my lessons table doesn't have that attribute, tag_name, so it calls my method instead

    def tag_name
       ????????
    end

    def tag_name=(name)
        self.tag = Tag.find_or_initialize_by_name(name) if name.present?
    end

however im not sure what to put inside the ????????. im trying to refer the :name attribute inside my tags table.

back then i used a has_many and belongs_to relationship. my lesson belonged to a tag (which was wrong) but i was able to write...

tag.name

and it worked. but since its a has_many :through now, im not sure. i tried using tags.name, Lessons.tags.name, etc but i cant seem to get it to work. how can i refer to the tags table name attribute? thank you

Upvotes: 0

Views: 858

Answers (1)

George Vinogradov
George Vinogradov

Reputation: 106

Apologize for my bad english.

When your Lesson was belonged to Tag lesson had only one tag, so your code was right. But now Lesson has many Tags, and it is collection (array in simple words). So, your setter must be more complex:

def tag_names=(names)
  names = if names.kind_of? String
    names.split(',').map{|name| name.strip!; name.length > 0 ? name : nil}.compact
  else
    names
  end

  current_names = self.tags.map(&:name) # names of current tags
  not_added = names - current_names # names of new tags
  for_remove = current_names - names # names of tags that well be removed

  # remove tags
  self.tags.delete(self.tags.where(:name => for_remove))
  # adding new
  not_added.each do |name|
    self.tags << Tag.where(:name => name).first || Tag.new(:name => name)
  end
end

And getter method should be like this:

def tag_names
  self.tags.map(&:name)
end

BTW, finders like find_by_name are deprecated. You must use where.

Upvotes: 2

Related Questions