Independent observation of model events in Rails - ruby-on-rails

What is the best way in rails to implement the situation where when Model A is created, Model B can observe this and change.
One solution is some kind of filter such as an after_create in Model A. However having some Model B code in Model A seems like a bad practise.
Another solution is to have some code in Model B's controller to handle this which also seems like a bad idea.
Ideally Model B or some kind of independent observer class should be able to observe the creation of all Model A's and then act as required.

Update:
Thanks to OP for pointing out that this was for Rails4 as the question was originally tagged which I had missed to notice. Rails 4 alternative to Observers question here at SO has several great answers.
Original Answer:
This can be done using Observer. Say you want a ModelAObserver, where you'd define the operations required on ModelB, you can create a new file app/models/model_a_observer.rb manually or use the generator rails g observer ModelA.
Then define the required callbacks in the ModelAObserver:
# app/models/model_a_observer.rb
class ModelAObserver < ActiveRecord::Observer
observe :model_a
def after_create(new_model_a_record)
...
# Operations on ModelB
...
end
end

Related

Default creation of has_one dependency in rails

I'd like to understand best practices for creating a dependency of a model in rails. The scenario is simple. The two models are:
class Main < ActiveRecord::Base
has_one :dependent
validates :dependent, presence: true
end
class Dependent < ActiveRecord::Base
end
(Note that I want to validate to ensure the dependent always exists)
Whenever a Main object is created I want a Dependent object to be created and "default initialized". I come from a background of C++ hence I view this problem as one of constructing a member variable whose type is some class which has a default constructor.
There are a bunch of ways I can solve this.
Put logic in before_validation to create a Dependent.
This feels very "un-railsy". I wanted to do this in before_create but validations are done before that callback. Doing it in before_validation is ugly as this callback is called both on create and on update, which makes the logic tricky/messy.
Put logic in .new
This feels very very "un-railsy" and is probably conceptually wrong. I'd see new as performing ActiveRecord construction which happens before the model is built.
Make the caller do the work
Whenever a Main object is created it must be done via new-save rather than create. The calling code then has to create Dependent itself, albeit with default values, e.g.
Main.new do |m|
m.dependent = Dependent.create
end
This is annoyingly burdensome on callers and causes a lot of duplicate code. I could pack this into a factory type method but the same problem exists that calling code needs to do some legwork.
Is there a canonical solution to this?
You should try using after_create callback and create Dependent instance in this method.
To answer my own question, I found a callback I hadn't see listed before: after_initialize. I can do what I want here.
A note for others:
My particular case is quite straightforward as I always want my dependent class to be default initialized and the user doesn't ever need to set anything. However, in a more complex situation this wouldn't work and initializing dependents may require:
Explicit initialization at the site of creation
UI for user to initialize the dependent using #accepts_nested_attributes_for

Rails STI: transfer records from one model to another

I have a model called Coupons
Then I have two child models CouponApplicationsand ApprovedCoupons.
The last two inherit from Couponsvia an STI architecture.
Now I want to realise the following:
A user sees CouponApplications
He clicks on an Approve button which makes the CouponApplications ApprovedCoupons
I realise that I could simply update the typecolumn of the Couponsrecord to change types. However, there are several Concerns, hooks etc in the ApprovedCoupons model which are happening after creation so this is not so easy. In fact, I want to create a complete new record to trigger those Concerns, hooks etc.
So I wrote this which I consider really bad:
#coupon_application = CouponApplication.find(params[:id])
#approved_coupon = ApprovedCoupon.new
# copy/paste attributes except the ID as this would be considered a duplication
#approved_coupon.attributes = #coupon_application.attributes.except("id")
# set the new type
#approved_coupon.update_attributes(type: "Advertisement")
#approved_coupon.save
I hope you understand what I want to achieve. It works this way but I doubt this is clean code.
To summarize:
I want to change the Coupontype from CouponApplication to
ApprovedCoupon
I still want to trigger the Concerns, hooks etc. in my ApprovedCoupon model so I decided to create a new ApprovedCoupon
record instead of just changing the type.
Is there any better approach?
You could add an approve method to your CouponApplication model like this:
class CouponApplication < Coupon
...
def approve
data = attributes.except('id', 'created_at', 'updated_at')
ApprovedCoupon.create(data.merge(type: 'Advertisement'))
end
end
Now your code can be simplified to this:
#coupon_application = CouponApplication.find(params[:id])
#approved_coupon = #coupon_application.approve

Which MVC layer should handle a child claiming a toy?

I have two models in my app: a child that has_many :toys and a toy that belongs_to :child. I made the db migration I needed for this to work (added child_id to toys table).
At first, children exist on their own, and toys exist on their own (with no association). At the start of every day in kindergarten, no child owns any toys. To play with a toy, a child must claim it first, and become its owner. So, now I need to somehow implement a child.claim(toy) method, and here I get stuck. Specifically:
Should this go into the child controller or model? Or maybe it should be somehow split between both?
If it should go into the controller, should it correspond to one of the CRUD actions or be its own thing as def claim(toy)?
Edit 1: The child is the user and is logged on via the browser. (Today's kids can do amazing things)
Actually you don't need a claim method if child is the user. You can have a claim_toy method in your controller. In you toys index view for each toy you can give a link as follows.
<%= link_to "claim", claim_toy_path(:toy_id => toy.id) %>
Your controller method will look something like this.
def claim_toy
toy = Toy.find(params[:toy_id])
current_child.toys << toy
end
simple enough. This is not a restful solution by the way.
A Child is a Foreign Key on Toy
There are certainly other ways to do this, but based on your original question, the simplest solution is to make your behavior consistent with the fact that a child is associated with a toy in the Toy table.
Simplest Solution
Setting aside arguments about what one should do in a perfect OOP/MVC design, the sensible place to make the change is in a controller since when a child claims a toy, the claim is processed by a an #update (or perhaps even a #claim) action on the Toy controller.
The child is the user and is logged on via the browser.
In your case, the session already knows who the child is, so adding the foreign key to the Toy model is trivial. The controller is the entity that receives the params for the associated model, so it's the correct place to tell the Toy model what attributes to update for a given toy.
There are certainly more complicated solutions, but they are of dubious merit based on the information provided in your original post. As always, your mileage may vary.
I'd build a separate class handling all the logic regarding a child and a toy. Call it context, call it concern, but do it.
class ToyChild # or ToyInteraction or ChildContext::Toys...
attr_reader :toy, :child
def initialize(toy, chid)
#toy = toy
#child = child
end
def associate
toy.child = child
# could be more difficult: you should check if the child has not enough toys, if the toy is not already assigned to another child etc...
#I'd avoid saving here since you may want to perform other operations
end
def foo
#code here
end
end
And in controller:
assoc = ToyChild.new(toy, child).associate
assoc.save
This style of coding:
is easier to test
splits responsibilities clearly
keeps things dry (no specific code in controllers)

Rails: Indicating that a model has an observer

A model I've implemented needs to escape/format one of the fields into an html-friendly version of the entered text (for the sake of argument, lets say it's a blue/redcloth stlye thing).
I implemented this in an observer with the before_save callback, all working, no complaints. I am learning rails as I code and it struck me that from just looking at the model there is no indication that it has an observer.
Is there a neat way of indicating (for other programmers who may view/edit my code) that a model has an observer or should I just do this in a comment (or not at all).
As far as I know, you do not need to specify on the target class/model that it has an observer all you need to do is make sure that the observer lists which models it is observing.
class ContentObserver < ActiveRecord::Observer
observe :answer, :audio_clip, :document
#This right here ^
def after_update(record)
record.recent_activity.save!
end
end
Have a spec/test file for the observer. If, in the future, someone changes the model in a way that upsets the observer then the spec/test will fail which will alert them.

ActiveRecord Inheritance with Different Database Tables

I have just started investigating using more advanced models in Rails. One that I use regularly, with great success, is model where a many-to-many cross-reference relationship is accessed by a class that itself is a sub-class of the base class in the many-to-many relationship.
This way the cross-reference class can act as a stand-in for the base class.
A good example is where a navigation hierarchy node (NavigationNode) is cross-referenced to a user role. At the cross-reference point a class (RoleNavigationNode) can inherit from NavigationNode and still have intimate knowledge of the user role.
My question is (in the case above) can RoleNavigationNode inherit from NavigationNode and yet access the cross-reference table rather than the one that NavigationNode accesses -- this of course using ActiveRecord.
I have not investigated polymorphic association, which may be more appropriate.
Thanks in advance...,
tried set_table_name on the subclass?
Also, look into setting #abstract_class in model classes.
Lastly, what you need may simply be a Mixin that you include in both models.
Anyway, what you're trying to do sounds rather un-ActiveRecord-ish. You might want to post a clearer example of what you're trying to achieve, maybe we'll be able to come up with something simpler.
This works in Rails 3:
class Common < ActiveRecord::Base
#abstract_class = true
def common
"Foobar!"
end
end
class Model < Common
end
class AnotherModel < Common
end
Without setting abstract_class, Rails will look for a table named commons in your database.

Resources