So I am wondering if there is a better way of doing this. I have a parent class that has three 1 to 1 relationships, children.
class Parent
has_one :child_one
has_one :child_two
has_one :child_three
...
end
Then within each child object I state belongs_to :parent, now ChildOne has attribute1, attribute2, ..., attribute30 etc.
I am using a gem that uses yaml to build calculations, but it can only access the table or model of the Parent class. It means that all the attributes i need from the ChildOne class I'll have to pull like this
def calculation_one
cal_one = self.child_one.attribute1
end
and onward. This would mean that I would have a model that's freakin fat just linking children attributes. Is there a better way of doing this?
Update
What i am looking for is a way to basically attr_accessor a subclass?
class Parent
attr_accessor :child_one, :attribute1
end
person = Parent.new
person.attribute1 = "awesome"
person.attribute1 # => "awesome"
I think what you're looking for is the Delegate module in rails : you can call delegate to let a related model respond to a method call like that :
class Parent < ActiveRecord::Base
has_one :child_one
delegate :attribute1, to: :child_one
end
Full documentation : http://apidock.com/rails/Module/delegate
Related
Background
I have some polymorphic relationships - one in particular assetable where I have a parent-child Asset relationship and the parent has various classes.
I also have a global Tracker model that creates a global id type scheme across my various models which I also use the FriendlyID gem for.
My Goal
Something like this:
parent = Tracker.find_by(tracker: '4Q73XEGK').trackable
Asset.find(1).update(parent_tracker: parent.tracker_id)
thinking I could do something like this - set the new polymorphic relationship by the tracker_id:
class Asset < ActiveRecord::Base
def parent_tracker
assetable.tracker_id
end
def parent_tracker=(val)
a = Tracker.find_by(tracker: val).trackable
assetable_id = a.id
assetable_type = a.class.name
end
end
Question?
Am I on the right path here (with tweaks) OR show that I am on the completely wrong path here.
I know that there are before_save filters etc. but the setter approach seems more elegant and clear as I can apply this across many other models.
You should not have to set both the type and id - use the setter created by the association instead:
class Asset < ActiveRecord::Base
belongs_to :assetable, polymorphic: true
def parent_tracker
assetable.tracker_id
end
def parent_tracker=(val)
self.assetable = Tracker.find_by(tracker: val).trackable
end
end
The setter for polymorphic associations will set both the id and type attributes. Also note that you need to use self explicitly when calling setters.
assetable_id = a.id
assetable_type = a.class.name
Will just set local variables that are garbage collected when the method ends.
Tracker.find_by(tracker: val) feels really smelly too. If your Tracker class just keeps track of global ids shouldn't it provide a method that takes such an id and returns the trackable?
class Tracker < ActiveRecord::Base
def self.lookup(global_id)
find_by(tracker: global_id).trackable
end
end
class Asset < ActiveRecord::Base
belongs_to :assetable, polymorphic: true
# ...
def parent_tracker=(val)
self.assetable = Tracker.lookup(val)
end
end
I'm trying to create a mixin that allows an ActiveRecord model to act as a delegate for another model. So, doing it the typical way:
class Apple < ActiveRecord::Base
def foo_species
"Red delicious"
end
end
class AppleWrapper < ActiveRecord::Base
belongs_to :apple
# some meta delegation code here so that AppleWrapper
# has all the same interface as Apple
end
a = Apple.create
w = AppleWrapper.create
w.apple = a
w.foo_species
# => 'Red delicious'
What I want is to abstract this behavior into a Mixin, so that given a bunch of data models, I can create "Wrapper" classes that are also ActiveRecords, but that each wrapper corresponds to a specific class. Why? Each of the data models have calculations, aggregations with other models, and I want the "Wrapper" classes to contain fields (in the schema) that correspond to these calculations...so in effect. the Wrapper acts as a cached version of the original data model, with the same interface.
I will have to write out each Wrapper...so for Apple, Orange, Pear, there is a different Wrapper model for each of them. However, I just want to abstract out the wrapper behavior...so that there's a class level method that sets what the Wrapper points to, a la:
module WrapperMixin
extend ActiveSupport::Concern
module ClassMethods
def set_wrapped_class(klass)
# this sets the relation to another data model and does the meta delegation
end
end
end
class AppleWrapper < ActiveRecord::Base
include WrapperMixin
set_wrapped_class Apple
end
class OrangeWrapper < ActiveRecord::Base
include WrapperMixin
set_wrapped_class Orange
end
How would I set this up? And would this have to be a STI type relation? That is, does the Wrapper class have to have a wrapped_record_id and a wrapped_record_type?
You can use belongs_to in your set_wrapped_class method.
def set_wrapped_class(klass)
belongs_to klass.to_s.downcase.to_sym
end
is there any trick to create default child when a parent doesn't have children and not showing it when parent has children ? an after_create callback doesn't solve the problem by itself alone.now if user creates 3 children and you call "parent.children" the result not only contains those 3 children, but also the auto-created one.
UPDATE: I need the auto-created child only when there are no other children and when there are, I don't want the auto-created one to be among the result of "parent.children"
Example:
i have a product model having many variants.a customer can order a variant and so we need a variant. but sometimes the admin don't want to add variant and thinks that a product without variant it enough.so we need a default variant to let customers to order.maybe 2 month later, the admin comes and add a variant and from this time, we don't want a default one anymore. i was looking for a nice and clean solution for this problem
after_create should work just fine, assuming you associated any children before saving the parent:
class ParentModel < ActiveRecord::Base
has_many :child_models
after_create :ensure_child_exists
def ensure_child_exists
child_models.create(default: true) unless child_models.exists?
end
end
If you then need to remove this child if new ones are added at some later point, you'll need to have a 'default' flag on the child to identify for removal, then add a callback to trigger the default child removal.
class ChildModel < ActiveRecord::Base
attr_accessible :default
belongs_to :parent
after_create :remove_default_if_unneeded
scope :default, -> { where(default: true) }
def remove_default_if_unneeded
parent.child_models.default.destroy_all unless self.default?
end
end
Just call a method like this in the after_create callback.
def create_child_if_needed
Child.create(:parent_id => self.id) unless self.children.any?
end
This will only create the Child object if there isnt any child in the children association.
I have two applications, App1 and App2. App1 posts a JSON payload to App2 that includes data for a parent and child object. If the parent object already exists in App2, then we update the parent record if anything has changed and create the child record in App2. If the parent object does not exist in App2, we need to first create it, then create the child object and associate the two. Right now I'm doing it like this:
class ChildController
def create
#child = Child.find_or_initialize_by_some_id(params[:child][:some_id])
#child.parent = Parent.create_or_update(params[:parent])
if #child.update_attributes(params[:child])
do_something
else
render :json => #child.errors, :status => 500
end
end
end
Something feels dirty about creating/updating the parent like that. Is there a better way to go about this? Thanks!
As a starting point, you'll want to create the association in your model, then include accepts_nested_attributes_for on your Parent.
With the associations created in your model, you should be able to manipulate the relationship pretty easily, because you automatically get a host of methods intended to manage the relationship. For example, your Parent/Child model might look something like this:
In your Parent model:
class Parent < ActiveRecord::Base
has_many :children
accepts_nested_attributes_for :children
In your Child model:
class Child < ActiveRecord::Base
belongs_to :parent
Then, you should be able to build associations in your controller like this:
def new
#parent = Parent.children.build
end
def create
#parent = Parent.children.build(params[:parent])
end
The nested_attributes property will then allow you to update attributes of the Child by manipulating the Parent.
Here is the Rails API on the topic: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Use accept_nested_attributes_for to handle parent children relationship .here's a blog post to help you out http://currentricity.wordpress.com/2011/09/04/the-definitive-guide-to-accepts_nested_attributes_for-a-model-in-rails-3/
I have the following (simplified) class hierarchy:
def Parent < ActiveRecord::Base end
def Child < Parent
belongs_to :other
end
def Other < ActiveRecord::Base end
I want to get all Parent objects and -if they are Child objects- have them eager load the :other association. So I had hoped I could do:
Parent.find(:all, :include => [:other])
But as I feared, I get the message: "Association named 'other' was not found; perhaps you misspelled it?"
What is the best way to establish eager loading in this scenario?
[Edit]
As requested, here's the more concrete example:
Parent = Event
Child = PostEvent
Other = Post
I want to log different types of events, all with their own properties (some of which are references to other objects), like the above example. At the same time I want to be able to list all Events that occurred, hence the parent class.
Can you define the belongs_to :other association in the Parent model? It won't be relevant to every Parent object, but that's the nature of STI: you will almost always have some column that's not used by every child.
If you really can't move the association to the parent, you may have to load in two steps, for example:
Parent.find(:all, :conditions => "type != 'Child'") +
Child.find(:all, :include => [:other])
Since Child inherits from Parent (and not the other way around), Parent has no knowledge of the belongs_to :other association.
I think you need to reconsider how you're modeling your app. Perhaps some specifics on your actual models would raise some answers on alternative methods of what you're trying to accomplish.