I have been using this nested attributes with has_many :through
class Check < ActiveRecord::Base
has_many :checks_tags, dependent: :destroy
has_many :tags, through: :checks_tags
attr_accessible :tags_attributes, :checks_tags_attributes
accepts_nested_attributes_for :tags, :checks_tags
end
class Tag < ActiveRecord::Base
has_many :checks_tags, dependent: :destroy
has_many :checks, through: :checks_tags
end
class CheckTag < ActiveRecord::Base
belongs_to :check
belongs_to :tag
end
so here the issue is when i create with this hash
"tags_attributes"=>[{"id"=>"", "name"=>"test12", "company_id"=>"1"}, {"id"=>"", "name"=>"test12", "company_id"=>"1"}]
actually here have two tags with same name, so its creating Tag twice and after putting twice on CheckTag, so is there any way to avoid this creation as twice in Tag?
If you want it forbidden in the database, you could create a unique index on the combination of the two columns on the check_tag table. If you want to handle it in rails, you can do it with a before_save callback on the Check model (if that's the only way you create these), but that may make you vulnerable to a race condition.
See this answer:
Index on multiple columns in RoR
Related
I have two models: Orders and Items. This is a many to many relationship, but I'm unsure if HMT is correct
Order model:
-user_id
-transaction_cost
-delivery_time
Item model:
-price
-name
Orders should be able to get all items in the order
Items do not need to be able to get all orders
The convention on this is to use the names of both models. A good name might be ItemOrders. Has many through is almost certainly a correct choice here.
class Order < ActiveRecord::Base
has_many :item_orders, dependent: :destroy
has_many :items, through: :item_orders
end
class Item < ActiveRecord::Base
has_many :item_orders, dependent: :destroy
has_many :orders, through: :item_orders
end
class ItemOrder < ActiveRecord::Base
belongs_to :item
belongs_to :order
end
Now you just have another ActiveRecord model, and you can add to it as you'd like. It will also be helpful for debugging. You can even use a model/scaffold generator to generate these:
rails g model item_order order:references item:references
This way you get the migrations correct right away. Nothing needs to be altered on your other models except for the above code.
I have two AR models and a third has_many :through join model like this:
class User < ActiveRecord::Base
has_many :ratings
has_many :movies, through: :ratings
end
class Movie < ActiveRecord::Base
has_many :ratings
has_many :users, through: :ratings
end
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :movie
after_destroy do
puts 'destroyed'
end
end
Occasionally, a user will want to drop a movie directly (without directly destroying the rating). However, when I do:
# puts user.movie_ids
# => [1,2,3]
user.movie_ids = [1, 2]
the rating's after_destroy callback isn't called, although the join record is deleted appropriately. If I modify my user model like this:
class User < ActiveRecord::Base
has_many :ratings
has_many :movies,
through: :ratings,
before_remove: proc { |u, m| Rating.where(movie: m, user: u).destroy_all }
end
Everything works fine, but this is really ugly, and Rails then tries to delete the join model a second time.
How can I use a dependent: :destroy strategy for this association, rather than dependent: :delete?
Answering my own question, since this was difficult to Google, and the answer is super counter-intuitive (although I don't know what the ideal interface would be).
First, the situation is described thoroughly here: https://github.com/rails/rails/issues/7618. However, the specific answer is buried about halfway down the page, and the issue was closed (even though it is still an issue in current Rails versions).
You can specify dependent: :destroy for these types of join model destructions, by adding the option to the has_many :through command, like this:
class User < ActiveRecord::Base
has_many :ratings
has_many :movies,
through: :ratings,
dependent: :destroy
end
This is counter-intuitive because in normal cases, dependent: :destroy will destroy that specific association's object(s).
For example, if we had has_many :ratings, dependent: :destroy here, all of a user's ratings would be destroyed when that user was destroyed.
We certainly don't want to destroy the specific movie objects here, because they may be in use by other users/ratings. However, Rails magically knows that we want to destroy the join record, not the association record, in this case.
I have this model
class XmlImport < ActiveRecord::Base
belongs_to :video
belongs_to :user
has_many :events, through: :event_import_records, dependent: :destroy
has_many :event_import_records, dependent: :destroy
has_attached_file :xml
validates_attachment_content_type :xml, :content_type => ["text/xml"]
end
The :event_import_records entries are being destroyed. But the :events are not.
Is the dependent:destroy on the has_many through association valid?
Is there another way of writing it? If that is not correct
How can I destroy all the events associated to the XmlImport through the event_import_records?
You can find at the Rails API that: "If using with the :through option, the association on the join model must be a belongs_to, and the records which get deleted are the join records, rather than the associated records." I understand that it delete the joins records but not the associated by through.
If I were you, I try:
class EventImportRecord < ActiveRecord::Base
has_many :events, dependent: :destroy
end
If not work I swap the order of the has_many relations on the XmlImport model, because of "Note that :dependent is implemented using Rails' callback system, which works by processing callbacks in order. Therefore, other callbacks declared either before or after the :dependent option can affect what it does." Also find at the same page of the Rails API.
A posts model:
class Post < ActiveRecord::Base
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
and a tags model:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, through: :taggings
and the associated join table:
class Tagging < ActiveRecord::Base
belongs_to :tag, dependent: :destroy
belongs_to :post
so, the dependent: :destroy in Post makes the db entries in Tagging to be destroyed when doing for instance Post.last.destroy, and the dependent: :destroy in Tagging makes the associated Tag object to be destroyed as well. Fine.
My problem is, 2 posts may share the same Tag, and I would like to destroy that tag -only- if the post being deleted is the last one referencing it. I don't allow duplicate entries in the Tag table. The tags for a post are submitted as a a string with the tags separated by commas, and upon creation of a post I do the following:
def tag_names=(names)
self.tags = names.split(",").map{ |tag| Tag.where(name: tag.squish).first_or_create! }
end
Any idea on how I could achieve this? Thanks!
Are you using a plugin? act_as_taggable or something like that?
If so, take a look to the docs because probably is already implemented.
If not, you can always implement the after_destroy callback to count the elements associated to a tag and delete the tag in the case that you have "empty" tags.
For instance, in your Post model:
class Post
before_destroy :clean_up_tags
protected
def clean_up_tags
tags_to_delete = Tagging.where(id: self.tag_ids).group(:tag_id).having("count(distinct taggable_id) = 1").pluck(:id)
Tag.find(tags_to_delete).map(&:destroy)
end
end
This method is assuming that you have a method tag_ids that returns the tags associated to an specific Post and that your taggings model is polymorphic).
As you probably have more than one model with the tags feature, a good approach would be packing this method into a module and include it in all the models, in this way you keep the things DRY.
I have these models:
class Project < ActiveRecord::Base
has_many :task_links, -> { includes(:task).order("tasks.name") }, dependent: :destroy
has_many :tasks, through: :task_links
class TaskLink < ActiveRecord::Base
belongs_to :project
belongs_to :task
class Task < ActiveRecord::Base
has_many :task_links, dependent: :destroy
has_many :projects, through: :task_links
I want to create an after_create callback that will automatically create all task_links for all active projects and the newly created task. I can do this by looping over the active projects and creating a task_link for each, but I'm wondering if there is a nicer way to do this? Preferably with one big insert command instead of xxx.
I'm not sure why you'd need to use an after_create to do this, because I think a before_create would work quite well. With a before_create, you can save all of your task_links when your new task is saved.
class Task < ActiveRecord::Base
has_many :task_links, dependent: :destroy
has_many :projects, through: :task_links
before_create :create_task_links
def create_task_links
# I'm assuming that the where below satisfies "all active projects"
Project.where(active: true).find_each do |proj|
# build only creates objects but does not save them to your database
self.task_links.build(project: proj)
end
end
end