In a form, I have a multiselect dropdown that I can manually sort. When I submit the form on edit, I get the following in the params:
"article" =>{"tag_ids"=>["", "4", "1", "2"]}
Which it is what I want, rather than 1, 2, 4.
Then, in the model I update the ordinal. I am unable to get the new order of tag_id from the parameter. When I call the article_tags I get the order of the table, and not the one in the parameters. I am assuming this is because there was no change in tag_ids (The same entries, different order). Is there a way I can access that order in model itself?
I can do some workarounds with hidden inputs, or attr_accessors, but I want to know if there is a way to do it from the model.
Controller:
def update
if @article.update(article_params)
redirect_to articles_path
end
end
def article_params
params.require(:article).permit(:title, tag_ids: [])
end
Models:
class Article
has_many: :article_tags
has_many: :tags, through: :article_tags
before_save: :order
def order
self.article_tags.each_with_index do |i, idx|
i.update_attribute(:ordinal, idx)
end
end
end
class ArticleTag
belongs_to: :article
belongs_to: :tag
end
Tables
articles
id | title
------------------
1 | Testing
tags
id | name
------------------
1 | Business
2 | Education
3 | Health
4 | Social
article_tags
id | article_id | tag_id | ordinal
------------------------------------------
1 | 1 | 1 | 2
2 | 1 | 2 | 3
3 | 1 | 4 | 1
You could do this by overriding the tag_ids=
setter method created by has_many: :tags, through: :article_tags
.
class Article
# ...
def tags_ids=(ids)
ids.reject(&:blank?).each_with_index do |id, index|
article_tag = self.article_tags.find_or_intialize_by(tag_id: id)
article_tag.update(
ordinal: index
)
article_tag.where.not(tag_id: ids).destroy_all
end
end
end