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
Related
I would like to implement certain relationship between 2 models.
I have 2 models: quiz and question that have many-to-many relationship.
Quiz model have quiz_flag and question model have question_flag.
What I want to happen is when quiz_flag is changed to true, every question that is in direct relationship (basically every question that is contained within that quiz), should also change question_flag to true.
Logic is similar to dependent: :destroy, but it's a custom function that I want to trigger when quiz_flag becomes true.
But how do I specifically do that?
You could just add additional logic to whatever form/action is responsible for setting quiz.
I.e.:
if params[:quiz_flag] #if the quiz_flag params is set to true.
#quiz.questions.update_all(question_flag: true)
end
Or if it's for multiple controllers, you could use callbacks:
Quiz Model:
before_save :some_method #will work before object is saved
(works with both create and update, if you just want update use before_update)
def some method
if self.quiz_flag == true
self.questons.update_all(question_flag:true)
end
end
I would caution you on using callbacks though. It can lead to some messy code that will be difficult to test for later.
You can use the callback :before_update inside your model.
I'd do something like this:
class Quiz < ApplicationRecord
before_update :update_question_flags, :if => :question_flag_changed?
def update_question_flags
self.questons.update_all(question_flag:true)
end
end
I know that before_create is called before the object gets commuted to the database and after_create gets called after.
The only time when before_create will get called and after_create while not is if the object fails to meet data base constants (unique key, etc.). Other that that I can place all the logic from after_create in before_create
Am I missing something?
In order to understand these two callbacks, firstly you need to know when these two are invoked. Below is the ActiveRecord callback ordering:
(-) save
(-) valid
(1) before_validation
(-) validate
(2) after_validation
(3) before_save
(4) before_create
(-) create
(5) after_create
(6) after_save
(7) after_commit
you can see that before_create is called after after_validation, to put it in simple context, this callback is called after your ActiveRecord has met validation. This before_create is normally used to set some extra attributes after validation.
now move on to after_create, you can see this is created after the record is stored persistently onto DB. People normally use this to do things like sending notification, logging.
And for the question, when should you use it? The answer is 'you should not use it at all'. ActiveRecord callbacks are anti-pattern and seasoned Rails developer consider it code-smell, you can achieve all of that by using Service object to wrap around. Here is one simple example:
class Car < ActiveRecord::Base
before_create :set_mileage_to_zero
after_create :send_quality_report_to_qa_team
end
can be rewritten in
# app/services/car_creation.rb
class CarCreation
attr_reader :car
def initialize(params = {})
#car = Car.new(params)
#car.mileage = 0
end
def create_car
if car.save
send_report_to_qa_team
end
end
private
def send_report_to_qa_team
end
end
If you have simple app, then callback is okay, but as your app grows, you will be scratching your head not sure what has set this or that attribute and testing will be very hard.
On second thought, I still think you should extensively use callback and experience the pain refactoring it then you'll learn to avoid it ;) goodluck
The before_create callback can be used to set attributes on the object before it is saved to the database. For example, generating a unique identifier for a record. Putting this in an after_create would require another database call.
before_create:
will be called before saving new object in db. When this method will return false it will prevent the creation by rolling back.
So when you need to do something like check something before saving which is not appropriate in validations you can use them in before_create.
For example: before creation of new Worker ask Master for permission.
before_create :notify_master
def notify_master
# notify_master via ipc and
# if response is true then return true and create this successfully
# else return false and rollback
end
Another use is as Trung LĂȘ suggested you want to format some attribute before saving
like capitalizing name etc.
after_create:
Called after saving object in database for first time. Just when you don't want to interrupt creation and just take a note of creation or trigger something after creation this is useful.
for example: After creating new user with role mod we want to notify other mods
after_create :notify_mod, :is_mod?
def notify_mod
# send notification to all other mods
end
EDIT: for below comment
Q: What's the advantage of putting notify_mod in after_create instead of before_create?
A: Sometimes while saving the object in database it can rollback due to database side validations or due to other issues.
Now if you have written notify_mod in before create then it will be processed even if the creation is not done. No doubt it will rollback but it generates overhead. so it's time consuming
If you have placed it in after_create then notify_mod will only execute if the record is created successfully. Thus decreasing the overhead if the rollback takes places.
Another reason is that it's logical that notification must be sent after user is created not before.
I have a Game model which has_many :texts. The problem is that I have to order the texts differently depending on which game they belong to (yes, ugly, but it's legacy data). I created a Text.in_game_order_query(game) method, which returns the appropriate ordering.
My favourite solution would have been to place a default scope in the Text model, but that would require knowing which game they're part of. I also don't want to create separate classes for the texts for each game - there are many games, with more coming up, and all the newer ones will use the same ordering. So I had another idea: ordering texts in the has_many, when I do know which game they're part of:
has_many :texts, :order => Text.in_game_order_query(self)
However, self is the class here, so that doesn't work.
Is there really no other solution except calling #game.texts.in_game_order(#game) every single time??
I had a very similar problem recently and I was convinced that it wasn't possible in Rails but that I learned something very interesting.
You can declare a parameter for a scope and then not pass it in and it will pass in the parent object by default!
So, you can just do:
class Game < ActiveRecord
has_many :texts, -> (game) { Text.in_game_order_query(game) }
Believe or not, you don't have to pass in the game. Rails will do it magically for you. You can simply do:
game.texts
There is one caveat, though. This will not work presently in Rails if you have preloading enabled. If you do, you may get this warning:
DEPRECATION WARNING: The association scope 'texts' is instance dependent (the scope block takes an argument). Preloading happens before the individual instances are created. This means that there is no instance being passed to the association scope. This will most likely result in broken or incorrect behavior. Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
Following up using PradeepKumar's idea, I found the following solution to work
Assuming a class Block which has an attribute block_type, and a container class (say Page), you could have something like this:
class Page
...
has_many :blocks do
def ordered_by_type
# self is the array of blocks
self.sort_by(&:block_type)
end
end
...
end
Then when you call
page.blocks.ordered_by_type
you get what you want - defined by a Proc.
Obviously, the Proc could be much more complex and is not working in the SQL call but after there result set has been compiled.
UPDATE:
I re-read this post and my answer after a bunch of time, and I wonder if you could do something as simple as another method which you basically suggested yourself in the post.
What if you added a method to Game called ordered_texts
def ordered_texts
texts.in_game_order(self)
end
Does that solve the issue? Or does this method need to be chainable with other Game relation methods?
Would an Association extension be a possibility?
It seems that you could make this work:
module Legacy
def legacy_game_order
order(proxy_association.owner.custom_texts_order)
end
end
class Game << ActiveRecord::Base
includes Legacy
has_many :texts, :extend => Legacy
def custom_texts_order
# your custom query logic goes here
end
end
That way, given a game instance, you should be able to access instance's custom query without having to pass in self:
g = Game.find(123)
g.texts.legacy_game_order
Here is a way where you can do it,
has_many :texts, :order => lambda { Text.in_game_order_query(self) }
This is another way which I usually wont recommend(but will work),
has_many :texts do
def game_order(game)
find(:all, :order => Text.in_game_order_query(game))
end
end
and you can call them by,
game.texts.game_order(game)
Im not sure what your order/query looks like in the in_game_order_query class method but i believe you can do this
has_many :texts, :finder_sql => proc{Text.in_game_order_query(self)}
Just letting you know that I have never used this before but I would appreciate it if you let me know if this works for you or not.
Check out http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many for more documentation on :finder_sql
I think if you want runtime information processed you should get this done with:
has_many :texts, :order => proc{ {Text.in_game_order_query(self)} }
I'm trying to do this
has_many :roles, :before_add => :enforce_unique
def enforce_unique(assoc)
false if exists? assoc
end
From the docs: "If a before_add callback throws an exception, the object does not get added to the collection". The using false above does not prevent the add, so I'm forced to do this:
def enforce_unique(assoc)
raise if exists? assoc
end
This way, it's true that it doesn't get added, but it also raises an exception that has to be handled. Not very useful to me here. I would prefer this to behave more like regular AR callback before_save, where returning FALSE also prevents the save (or add) but doesn't raise an exception.
In this case above, I would prefer this to just not add the assoc silently. Is there a way to do this? I missing something? Or is raising an exception the only option here?
The way to solve it I think is to use throw and catch, which in Ruby are meant for flow control. Raising an exception is not a good fit, since this isn't an exceptional circumstance.
I ended up doing:
catch(:duplicate) do
association.create({})
end
And then in the before_add callback, I did:
if(Class.where({}).first)
throw :duplicate
end
More on throw/catch here:
http://rubylearning.com/blog/2011/07/12/throw-catch-raise-rescue-im-so-confused/
If the association isn't polymorphic you can do something like:
validates_uniqueness_of :name_of_model
inside of Role where name_of_model us what you are associating with
this question is a bit old, but i came across the same problem recently. here is how i solved it:
def enforce_unique |obj, x|
v = obj.roles
if i = v.index(x)
v.slice! i
end
end
Since this question is about saving rather than preventing it being included in the list temporarily (eg by a controller that is not interested in controlling its models) you could try overriding save in the related model and just not save it if the role exists:
class Role < ActiveRecord::Base
belongs_to :user, inverse_of: :roles
def save
super unless self.new_record? && user.has_existing_role?(self)
end
end
Sidenote: I don't buy the skinny controller argument when used with the Active Record pattern as business logic has to be put somewhere. With a business domain poor pattern like Active Record (not referring to the Ruby AR gem specifically) it really needs to exist a layer above (ie in the controller layer), you may use service objects or the decorator pattern as a means to achieve this.
Another approach would be to override update methods like << for the association and silently drop the role if it matches an existing one. Details on overriding association methods are in the ActiveRecord Association Class Methods Documentation
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.