Can a rails model observe a subject? - ruby-on-rails

I'm trying to have a rails model observe another object (which acts as a subject). I saw that there is an update method (which has different semantics) in ActiveRecord::Base, but in order to use the Observable module, I need to implement an update method in my model so that I can be notified of changes to the subject.
Any thoughts on how this could be achieved?

You probably want to use a regular Observer which will receive event callbacks when something happens to the observed model.
Why do you need to encapsulate your observer functionality into another model?
You're better off putting the events/callbacks in your observer and calling any needed functionality as a helper method on the other model instead of making your model an observer.
EDIT: Adding example code
class User < ActiveRecord::Base
end
class UserObserver < ActiveRecord::Observer
observe :user
def after_save(user)
MyHelperClass.do_some_stuff_for_user(user)
end
end
class MyHelperClass
def self.do_some_stuff_for_user(user)
puts "OMG I just found out #{user.name} was saved so I can do stuff"
end
end

It appears that you can override the default update that comes with ActiveRecord, so that it can receive notifications from subjects (assuming you have mixed in Observable). The procedure for doing something like this is in the book "Pro ActiveRecord" published by APress (Chap. 5, "Bonus Features").
It involves the use of alias_method / alias_method_chain with some metaprogramming involved...
I haven't tried this out personally yet, but just leaving a note here in case anyone else is intested.

Related

Best code structure for Rails associations

The Stage
Lets talk about the most common type of association we encounter.
I have a User which :has_many Post(s)
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :user
end
Problem Statement
I want to do some (very light and quick) processing on all the posts of a user. I am looking for the best way to structure my code to achieve it. Below are a couple of ways and why they work or don't work.
Method 1
Do it in the User class itself.
class User < ActiveRecord::Base
has_many :posts
def process_posts
posts.each do |post|
# code of whatever 'process' does to posts of this user
end
end
end
Post class remains the same:
class Post < ActiveRecord::Base
belongs_to :user
end
The method is called as:
User.find(1).process_posts
Why doesn't this look the best way to do it
The logic of doing something with the posts of the user should really belong to the Post class. In a real world scenario, a user might also have :has_many relations with a lot of other classes e.g. orders, comments, children etc.
If we start adding similar process_orders, process_comments, process_children (yikes) methods to the User class, it'll result in one giant file with lots of code much of which could (and should) be distributed to where it belongs i.e. the target associations.
Method 2
Proxy Associations and Scopes
Both of these constructs require addition of methods/code to the User class which again makes it bloated. I'd rather have all implementation shifted to the target classes.
Method 3
Class Method on target Class
Create class methods in the target class and call those methods on the User object.
class User < ActiveRecord::Base
has_many :comments
# all target specific code in target classes
end
class Post < ActiveRecord::Base
belongs_to :user
# Class method
def self.process
Post.all.each do |post| # see Note 2 below
# code of whatever 'process' does to posts of this user
end
end
end
The method is called as:
User.find(1).posts.process # See Note 1 below
Now, this looks and feels better than Method 1 and 2 because:
User model remains clutter free.
The process function is called process instead of process_posts. Now we can have a process for other classes as well and invoke them as: User.find(1).orders.process etc. instead of User.find(1).process_orders (Method 1).
Note 1:
Yes you can call a class method like this on a association. Read why here. TL;DR is that User.find(1).posts returns a CollectionProxy object which has access to class methods of the target (Post) class. It also conveniently passes a scope_attributes which stores the user_id of the user which called posts.process. This comes handy. See Note 2 below.
Note 2:
For people not sure whats going on when we do a Post.all.each in the class method, it returns all the posts of the user this method was called on as against all the posts in the database.
So when called as User.find(99).posts.process, Post.all executes:
SELECT "notes".* FROM "posts" WHERE "posts"."user_id" = $1 [["user_id", 99]]
which are all the posts for User ID: 99.
Per #Jesuspc's comment below, Post.all.each can be succinctly written as all.each. Its more idiomatic and doesn't make it look like we are querying all posts in the database.
The Answer I am looking for
Explains what is the best way to handle such associations. How do people do it normally? and if there are any obvious design flaws in Method 3.
There's a fourth option. Move this logic out of the model entirely:
class PostProcessor
def initialize(posts)
#posts = posts
end
def process
#posts.each do |post|
# ...
end
end
end
PostProcessor.new(User.find(1).posts).process
This is sometimes called the Service Object pattern. A very nice bonus of this approach is that it makes writing tests for this logic really simple. Here's a great blog post on this and other ways to refactor "fat" models: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Personally, I think that Method 1 is the cleanest one. It will be very clean and understandable write something like this:
Class User < ActiveRecord::Base
has_many :posts
def process_posts
posts.each do |post|
post.process
end
end
end
And put all the logic of process method in Post model (with an instance variable):
Class Post < ActiveRecord::Base
belongs_to :user
def process
# Logic of your Post process
end
end
That way, the very logic of a Post process belong to Post class. Even if your User model will have many "process" functions, these will be very basic and small. That seems very clean to me, as a developer.
Method 3 has many technical implications that are pretty complex and unintuitive (yourself had to clarify your question).
NOTE: If you want better performance, maybe you should use eager loading to reduce ActiveRecord calls, but that is out of the scope of this question.
First of all excuse me for the opinionated answer.
ActiveRecord models are a controversial matter. Its essence is against the Single responsibility principle since they handle both database interaction via class methods and domain objects (which use to implement their own behaviour) via its instances. At the same time they also break the Liskov Substitution Principle because the models are not sub cases of ActiveRecord::Base and implement their own set of methods. And finally the ActiveRecord paradigm often leads to code that breaks the Law of Demeter, as in your proposal for the third method:
User.find(1).posts.process
Thus, there is a trend that in order to reduce coupling would recommend to use ActiveRecord objects only to interact with the database and therefore no behaviour should be added to them (in your case the process method). Under my point of view that is the lesser evil, even though it is still not a perfect solution.
So if I were to implement what you describe I would have a ProcessablePostsCollection object (where the name Processable can be customised to better describe what the processing is about, or even neglected completely so you would simple have a PostsCollection class) that would probably be a wrapper over a list of posts using SimpleDelegator and would have a method process.
class ProcessablePostsCollection < SimpleDelegator
def self.from_collection(collection)
new collection
end
def initialize(source)
super source
end
def process
# code of whatever 'process' does to posts
end
end
And the usage would be something like:
ProcessablePostsCollection.from_collection(User.find(1).posts).process
even though the from_collection and the call to process should happen in different clases.
Also, in case you have a big posts table it would probably be wise to process stuff in batches. For that your process method could call find_in_batches on your posts ActiveRecord::Relation.
But as always it depends on your needs. If you are simply building a prototype is perfectly fine to let your models grow fat, and if you are building an enormous application Rails itself is probably not going to be the best choice since discourages some OOP best practises with things such as ActiveRecord models.
You shouldn't be putting this in the User model - put it in Post (unless - of course - the scope of process involves the User model directly) :
#app/models/post.rb
class Post < ActiveRecord::Base
def process
return false if post.published?
# do something
end
end
Then you can use an ActiveRecord Association Extension to add the functionality to the User model:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :posts do
def process
proxy_association.target.each do |post|
post.process
end
end
end
end
This will allow you to call...
#user = User.find 1
#user.posts.process

Override ActiveRecord::Base

some of my models has a "company_id" column, that I want to set automatically. So I thought to override some method in activerecord base.
I tried this, in config/initializers, but does not work:
class ActiveRecord::Base
after_initialize :init
def init
if (self.respond_to(:company_id))
self.company_id= UserSession.find.user.company_id
end
end
end
Solution after Simone Carletti answer:
I created a module:
module WithCompany
def initialize_company
self.company_id= UserSession.find.user.company_id
end
end
And included this in the model:
class Exam < ActiveRecord::Base
include WithCompany
after_initialize :init
def init
initialize_company
end
end
Is there something else that I can do?
update 2
Best practices says to do not set session related fields in models. Use controllers for that.
There are two problems here. The first, is that you are injecting a bunch of stuff into all ActiveRecord models, whereas it would be better to add the feature only to the relevant models.
Secondary, you are breaking the MVC pattern trying to inject into the model the session context.
What you should do instead, is to code your feature in a module, and mix the module only in the relevant models. As per the context, rather than overriding the default AR behavior, add a new method where you pass the current session context (dependency injection) and returns the model initialized with the required company, when the session is set properly and the model is company-aware.

How do I build an Observer for ToyStore model?

Given a model:
class User
include Toy::Store
attribute :name
end
Can I use ActiveModel::Observer to build an observer? I remove the ActiveRecord framework, as I am not using it in my Rails app.
Adding an observer like:
class UserObserver < ActiveModel::Observer
def after_save(model)
puts "Hello!"
end
end
does not seem to work. Including the observer in the application configuration does not work, because the ActiveRecord framework is removed.
I also wanted to use Observers with Toy::Store too. It turns out that the normal object lifecycle events, like create, save, update, and delete that are observable in Rails are observable because of ActiveRecord. Toy Store objects are ActiveModel objects and don't have the same hooks. I did a deep dive on the subject in a blog post on Why Toy Store Doesn't Work With Observers.
But, good news, it's still possible to use observers with toy store, it's just up to you to implement them. I also did a blog post on How to Use Observers With Toy::Store
, but here's the gist of it: your Toy Object, in this case User, must include ActiveModel::Observing and must fire the event when it's appropriate for that model:
class User
include Toy::Store
attribute :name
after_save :notify_observers_save_occured
private
def notify_observers_save_occured
self.class.notify_observers(:after_save, self)
end
end
You can only observe ActiveModel descendants. They don't have to be ActiveRecord objects though as you can read in Yehuda Katz's article:
ActiveModel makes any ruby object feel like ActiveRecord

custom callbacks with observer in rails3

i'm desperate with one problem.
i want to register custom callback :after_something
and then use that callback into observer.
I have try with define_callbacks, set_callbacks but observer never trigger that callback.
I know that i can use AR callbacks, but i just prefer in some situation to use my own callbacks.
Thanks
Here is a solution if someone looking for:
in your model call this:
notify_observers(:after_my_custom_callback)
In your observer just define method:
def after_my_custom_callback(record)
#do things
end
Observer use standard AR callbacks so it will not trigger your default one.
I think rather that inventing new callback (and monkeypatch default behaviour of AR) maybe you should use AR's. It is difficult to say what do you want. Can you give some use case?
try maybe something like
before_save MyCallbacks.new, :if => :some_condiditons_meet?
class MyCallbacks
def before_save(object)
sophistcated_use(object) if object.valid?
damn_have_it?
end
end
it actually covers behaviour of observer at some level
<--update-->
The entire callback chain of a save, save!, or destroy call runs within a transaction. That includes after_* hooks. If everything goes fine a COMMIT is executed once the chain has been completed.
I think the whole Idea of SINGLE observer is not best solution. After more dependencies will arrive your logic will be very complicated. Defining own transaction wrappers is good, but do you really need it? Maybe you can rearrange model for achieve it without writing up own transaction case.
For instance
class Friendship < ActiveRecord::Base
belongs_to :user
has_many :users, :through => :groups
after_validation :save_my_records
def save_my_records
user.friend.history.save
user.history.save
end
end
Where friend is object => has got own observer
where history is object => has got own observer
It is very abstract, but without your code I had no idea how to give some constructive example also after_validation is not best place to saving anything I think.
also notify_observer sound like hack to me :-)
Thanks to Alex's answer I could solve this problem by defining a define_model_callbacks_for_observers method that should be called instead of define_model_callbacks (you might want to put this into a module):
def define_model_callbacks_for_observers(*args)
types = Array.wrap(args.extract_options![:only] || [:before, :around, :after])
callbacks = define_model_callbacks(*args)
callbacks.each do |callback|
types.each do |filter|
set_callback(callback, filter) do
notify_observers :"#{filter}_#{callback}"
true
end
end
end
end

How to invoke a method every time database is modified

I am writing a Ruby on Rails app and I want a method to be called every time the database is modified. Is it possible to do this without inserting a method call in every location where the database is modified?
I like KandadaBooggu's answer but if you did not want to monkey with AR you might be able to do this with an Observer.
class AllObserver < ActiveRecord::Observer
observe :model_a, :model_b
def after_save(record)
logger.info("CREATED #{record.class}")
end
def after_update(record)
logger.info("UPDATED #{record.class}")
end
end
Just add the models that you want to observer. In this example it will log updates to ModelA and ModelB
Depends on the database. Many databases have very powerful stored procedure languages that can, among other things, invoke web services.
You could have a trigger on the important database tables call a ruby web service that calls your method.
Or you can have triggers that update an event table, and then have a process that watches for changes on that table and then fires the method.
There's likely some meta-programming magic that you might be able to use to tweak your ruby code to invoke the change as well.
All sorts of options.
If you want to log all models:
Monkey patch the ActiveRecord::Base class.
class ActiveRecord::Base
after_save :log_something
after_destroy :log_something
private
def log_something
end
end
For a specific model:
class User < ActiveRecord::Base
after_save :log_something
after_destroy :log_something
private
def log_something
end
end
Have you considered using: after_update or before_update in ActiveRecord:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Resources