I'm having issues with the referencing of a specific model when being cloned. I've tried using multiple approaches but none have gotten the correct result, and I believe it has to do with me not understanding how the logic of cloning works.
This involves multiple models, but the error raises in an interdependency between the last 2 models in the hierarchy. For simplicity, I'll try to explain this with 3 models to get an idea of how everything's working: Page, PageElement and PageElementDependency
A Page has many PageElements
A PageElement has many dependent_page_element_dependencies and has_many dependee_page_element_dependencies, where both of these relations are PageElementDependency. Through both of these dependencies I've setup the following has_many through relationships: has_many :dependees, through: :dependent_page_element_dependencies and has_many :dependents, through: :dependee_page_element_dependencies. Summarizing everything looks like this:
#models/page.rb
has_many :page_elements, -> { order(position: :asc) }, inverse_of: :page, dependent: :destroy
amoeba do
enable
clone [:page_elements]
end
#models/page_element.rb
has_many :dependent_page_element_dependencies, foreign_key: :dependent_id, inverse_of: :dependent, class_name: "PageElementDependency", dependent: :destroy
has_many :dependees, through: :dependent_page_element_dependencies
has_many :dependee_page_element_dependencies, foreign_key: :dependee_id, inverse_of: :dependee, class_name: "PageElementDependency", dependent: :destroy
has_many :dependents, through: :dependee_page_element_dependencies
amoeba do
enable
clone [:dependee_page_element_dependencies, :dependent_page_element_dependencies]
end
#models/page_element_dependency.rb
belongs_to :dependent, class_name: "PageElement", inverse_of: :dependent_page_element_dependencies, foreign_key: :dependent_id
belongs_to :dependee, class_name: "PageElement", inverse_of: :dependee_page_element_dependencies, foreign_key: :dependee_id
Let's say I'm trying to clone Page#3, with PageElement#4 and PageElement#5, where there's a PageElementDependency, where the dependent is PageElement#4 and the dependee (who is depended upon) is PageElement#5.
So basically here we'd be able to access PageElement.find(5).dependees => PageElement#6, and if I do PageElement.find(6).dependents => PageElement#5.
So, when trying to clone Page#3, I'd expected to get a new Page#4 with PageElement#6 and PageElement#7, and here's the important part, a PageElementDependency where the dependent is PageElement#6 and the dependee is PageElement#7.
However, when I try cloning the PageElementDependencies, only one of the columns is getting a new value, but the other column is keeping the old PageElement's value. So in the example given, I'd get a PageElementDependency where the dependee is correct: PageElement#6, but the dependent is not update and stays as PageElement#5.
I'm not sure how to use a remapper in order to get the related "new" and "old" object of the dependencies, so I'm not really sure how to proceed.
I've also posted this as an issue in github: https://github.com/amoeba-rb/amoeba/issues/84
Related
I have a problem when setting up associations in my rails project and wonder if anyone can help me with that??
I have three models: user, comment and event. And there are two kinds of users: organization and volunteer. I have problem when I tried to make event.volunteers and volunteer.joined_events work...
Here are how the models set up:
class Comment < ApplicationRecord
belongs_to :organization, class_name: "User"
belongs_to :volunteer, class_name: "User"
belongs_to :event
end
class User < ApplicationRecord
has_many :organized_events, foreign_key: "organization_id", class_name: "Event"
has_many :joined_events, through: :being_commented_comments, :source => :event
has_many :commented_comments, foreign_key: "organization_id", class_name: "Comment"
has_many :being_commented_comments, foreign_key: "volunteer_id", class_name: "Comment"
end
class Event < ApplicationRecord
belongs_to :organization, class_name: "User"
has_many :volunteers, through: :comments, source: "Volunteer"
has_many :comments
end
And I keep getting errors like:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError (Could not find the source association(s) "Event" in model Comment. Try 'has_many :joined_events, :through => :being_commented_comments, :source => <name>'. Is it one of organization, volunteer, or event?)
or
ActiveRecord::HasManyThroughOrderError (Cannot have a has_many :through association 'User#joined_events' which goes through 'User#being_commented_comments' before the through association is defined.)
and I think the problem happens because I am not familiar enough with :source...Any suggestions would be super appreciated! Thanks!
When using has_many through: you must declare the association you are going through before the indirect association:
class User < ApplicationRecord
has_many :organized_events, foreign_key: "organization_id", class_name: "Event"
has_many :being_commented_comments, foreign_key: "volunteer_id", class_name: "Comment"
has_many :joined_events, through: :being_commented_comments, source: :event
has_many :commented_comments, foreign_key: "organization_id", class_name: "Comment"
end
I have a polymorphic, has_many through association between Service and context through ServiceUsage. The context models (currently Scenario and Mapping) include a ServiceConsumer mixin that declares the following associations:
has_many :service_usages, as: :context, dependent: :destroy
has_many :services, through: :service_usages, dependent: :destroy
ServiceUsage, the join table, defines the following associations:
belongs_to :service, inverse_of: :service_usages
belongs_to :context, inverse_of: :service_usages, polymorphic: true
Service currently has:
has_many :service_usages, inverse_of: :service
With the current setup, there is no way to get from service to the to the associated objects (either Scenario or Mapping).
One solution is to add explicit associations to Service for each Scenario and Mapping as follows:
has_many :scenarios, through: :service_usages, source: :subject, source_type: Scenario
I think there is a better way to do this to avoid explicitly defining the associations on Service.
I've been thinking of something along these lines:
module ServiceConsumer
extend ActiveSupport::Concern
included do
has_many :service_usages, as: :context, dependent: :destroy
has_many :services, through: :service_usages, dependent: :destroy
Service.class_eval <<-EOF
has_many #{self.to_s.underscore.pluralize.to_sym}, through: :service_usages,
source: :context,
source_type: ::#{self}
EOF
end
end
The idea is that when a model includes the ServiceConsumer concern, it defines on Service a has_many association for that particular model.
This sounds great in principal but I haven't been successful in getting it to work yet.
Any thoughts/comments/suggestions would be greatly appreciated. Do you think this is too obscure? Would it be better just to explicitly define the has_many associations on Service for each polymorphic associated model?
Thanks!
has_many #{self.to_s.underscore.pluralize.to_sym} this line in your concern is fundamentally wrong. Like if Scenario includes it, it'll translate to has_many :scenario inside Scenario model, which makes no sense.
On the other hand, you must maintain a balance between dry and meta programming. Unnecessary Metaprogram may overkill the benefit.
This worked for me.
Tag.class_eval %Q"
has_many :#{model_name.plural}, through: : service_usages, source: :context, source_type: #{model_name.name}
"
and I don't think metaprogramming is an overkill in this case.
I'm trying to make a list of commissions to add to applicant_commissions.
ApplicantCommission.rb
belongs_to :applicant
belongs_to :commission
Applicant.rb
has_many :applicant_commissions
Commission.rb
has_many :applicant_commissions
The problem is, if I use has_many through: when I delete an Applicant, the Commission is removed too. (and visa versa)
Any suggestions how I can create and destroy this without losing the associated record?
You should have following relations in your file
ApplicantCommission.rb
belongs_to :applicant
belongs_to :commission
Applicant.rb
has_many :applicant_commissions, dependent: :destroy
has_many :commissions, through: :applicant_commissions
Commission.rb
has_many :applicant_commissions, dependent: :destroy
has_many :applications, through: :applicant_commissions
This way your assosiation will not be deleted.
Secondly i would recommend you to have a look at this 'mark_for_destruction' for deletion.
This will help you deleting things easily.
http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
A miniature may have many contents.
class Miniature < ActiveRecord::Base
has_many :contents, foreign_key: "setmini_id", dependent: :destroy
has_many :minisets, :through => :contents, source: :miniset
has_many :reverse_contents, foreign_key: "miniset_id", class_name: "Content", dependent: :destroy
has_many :setminis, :through => :reverse_contents, source: :set mini
On a miniature's show view I currently list it's contents. What I want to do is add a default sort scope to my Contents model so that it sorts by name.
class Content < ActiveRecord::Base
default_scope { order('name ASC') }
belongs_to :miniset, class_name: "Miniature"
belongs_to :setmini, class_name: "Miniature"
My attempt here fails and complains "No such column name:".
With a normal has_many_through relationship this would work but I'm guessing because I'm using the join table in two directions and declaring class_name: "Miniature" this doesn't work here.
Is there a way I can get this list to default sort by name?
I've tried "content.name" and "setmini.name" to no avail.
You'll need the name of the database table in your order clause (not the model or association name). If you're following Rails' conventions for naming, this would be:
order('contents.name ASC')
After looking at some other answers to similar questions I eventually got it working with the following:
default_scope joins(:setmini).order('miniatures.name ASC')
I have this has_many association
has_many :devices, through: :vehicles, foreign_key: :meid
I need last_device to be implemented as a has_one relation to use it with ransack gem and it can be implemented using a method like this
def last_device
devices.last
end
I've tried a few variants but I haven't managed to get it working
has_one :last_device, class_name: 'Device'
Actually I've done it by splitting it into two pieces
has_one :last_vehicle, -> { order('vehicles.created_at DESC') }, class_name: 'Vehicle'
has_one :last_device, through: :last_vehicle, source: :device, class_name: 'Device'
Don't think it's possible to do it the way you propose, 'cause you wouldn't have a correct setting method on that association.
The only thing I can think of (if you insist on working with ransack), is adding a last_device_id column to your model and then adding a relation like this:
has_one :last_device, class_name: Device
Though you'll need to update that column when a new device is added to a user's vehicle or the last one is removed.