remote model callbacks - rails 3? - ruby-on-rails

I'm not phrasing the question correctly in the title, but heres what I'd like to do.
I have a method, like such:
class User < ActiveRecord::Base
def myMethod(abc, xyz)
#do stuff
end
end
I want 5 different models in my app to call this function on their after_create callback.
It seems very anti-DRY to put a function in each of those models to call this function.
Is there a way in this model (above) that holds the method - to remotely use the callbacks of the other models?
Or can anyone suggest a different way I should be approaching something like this?

That's what I would do:
Create a module:
module MyCallbacks
extend ActiveSupport::Concern
included do
after_create :my_method
end
def my_method
#do stuff
end
end
And then, you just need to include this module in the models of your choice:
class MyModel < ActiveRecord::Base
include MyCallbacks
end

Related

Before Create in Active Record

I would like to setup a before_create for all of my modules
what i have been trying is:
module ActiveRecord
module UserMonitor
require 'securerandom'
before_create :attach_uuid
def attach_uuid
self.uuid = SecureRandom.uuid.gsub("-","")
end
end
end
This does not seem to be working.
if i go into each module and add it in there it works, but i want to do it on a global scale.
Any thoughts or ideas on how i can achieve this in this manner? i know i could do it in triggers and such but i don't want to go that route and i would like to avoid hitting every module/class in case i need to change something.
Currently using Ruby 1.9.3 Can not currently upgrade my app until i make future code changes.
Thanks!
An other solution - I use, is to put the logic for UUID in an own module, that you include. I already have some (class-) methods I add to my AR, like set_default_if, so it was a good place for me.
module MyRecordExt
def self.included base
base.extend ClassMethods # in my case some other stuff
base.before_create :attach_uuid # now add the UUID
end
def attach_uuid
begin
self.uuid = SecureRandom.uuid
rescue
# do the "why dont we have a UUID filed?" here
end
end
# some other things not needed for add_uuid
module ClassMethods
include MySpecialBase # just an eg.
def default_for_if(...)
...
end
end
end
and then
class Articel < ActiveRecord::Base
include MyRecordExt
...
end
In general I avoid doing something for ALL models modifying AR base - I made the first bad experience with adding the UUID to all, and crashed with devise GEMs models ...
If you define attach_uuid in the ActiveRecord module, can't you just call the before_create :attach_uuid at the top of each controller? This is DRY.
Is there a UserMonitor controller that you could add it to?
class UserMonitor < ActiveRecord::Base
before_create :attach_uuid
end

Injecting custom callback in rails 3

I have a custom module which sets up a hash to be stored in my sql. As part of this it rolls a its own _changed accessor.
module MyAwesomeCustomModule
extend ActiveSupport::Concern
included do
after_save: wipe_preferences_changed
end
module ClassMethods
def blah
end
etc
end
end
and then in my model:
class MyModel < ActiveRecord::Base
include MyAwesomeCustomModule
after_save :something_that_expects_preferences_changed_to_be_available
blah
end
unfortunately, the after_save defined in the custom module runs before the one defined in the model. Is there a way to get the array of all callbacks and append to it? Is there a way to write a custom after_after_save callback? Is there a way to specify priority/ordering of after_save callbacks?
What would be a good way to resolve this race condition?
In spite of the order of model callbacks, the current design makes the module and the class very coupled.
To solve the current problem as well as improve design, you can define an expected callback in the module's method, and then the class who includes this module is free to respond it or not.
module MyAwesomeCustomModule
extend ActiveSupport::Concern
included do
after_save: wipe_preferences_changed
end
def wipe_preferences_changed
# previous logic to wipe
process_further if respond_to :process_further
end
end
class MyModel < ActiveRecord::Base
include MyAwesomeCustomModule
# Feel free to write this or not
# The content is the previous
# :something_that_expects_preferences_changed_to_be_available
def process_further
end
end
If you want to keep your original strategy (2 after_save callbacks) all you should need to do is move the include statement below the model after_save.
class MyModel < ActiveRecord::Base
after_save :something_that_expects_preferences_changed_to_be_available
include MyAwesomeCustomModule
blah
end
Callbacks are executed in the order they are defined. The include statement acts (very roughly) like you had copy and pasted the code from the module at that point, so by putting the include statement above the after_save in your model you were causing that callback to execute first.

How to make a method available to all controllers? And how to all models?

I am new to Rails and have written a method to_csv which I have put it in products_controller.rb, but I want it to available to all other controllers too. What is the preferred way to do that? Is it in application.rb?
Similarly, if I am writing a method in some model.rb, how to share that method between all the models?
application_controller will be the place. If for model, maybe you can write in a module, then
include in your model which you want to use.
1) Try ActiveRecord::Base monkeypatching.
The initializer directory is a best place to collect all those little task
So, try /config/initializers/active_record_extension.rb,
class ActiveRecord::Base
def self.export(parameters)
#your csv logic goes here
end
end
or
2) create master class, which is used to inherit by all active_record model
for example /models/your_class.rb
class YourClass < ActiveRecord::Base
def self.export(parameters)
#your csv logic goes here
end
end
class CsvDB < YourClass
end
You can also create a separate model without inheriting from ActiveRecord::Base and define your csv method in that particular model. And from any controller just call
model_name.method_name(parameters)
For example, in the model CsvDB:
class CsvDB
def export(parameters)
# your csv logic goes here
end
end
From any controller just call
CsvDB.export(parameters)

How to dynamically generate association names?

I am using Ruby on Rails 3.2.2 and the Squeel gem. I have following statements and I am trying to refactoring the my_squeel_query method in a Mixin module (since it is used by many of my models):
# Note: 'article_comment_associations' and 'model_as_like_article_comment_associations'
# refer to database table names.
class Article < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
class ModelAsLikeArticle < ActiveRecord::Base
def my_squeel_query
commenters.
.where{
model_as_like_article_comment_associations.article_id.eq(my{self.id}) & ...
}
end
end
My problem is that I can not refactoring article_comment_associations and model_as_like_article_comment_associations statements by generating a dynamic name in the Mixin module. That is, if that was a String I could dynamically generate the related name by using something like "#{self.class.to_s.singularize}_comment_associations" as the following:
class Article < ActiveRecord::Base
include MyModule
end
class ModelAsLikeArticle < ActiveRecord::Base
include MyModule
end
module MyModule
def my_squeel_query
commenters.
.where{
# Note: This code doesn't work. It is just an sample.
"#{self.class.to_s.singularize}_comment_associations".article_id.eq(my{self.id}) & ...
}
end
end
But, since it is not my case, I cannot "build" the name and make the my_squeel_query to be "shared" across models.
How can I dynamically generate association names related to the Squeel gem? Should I think to refactoring in another way? What do you advice about?
Since the DSL is instance_evaled, you can actually say something like:
def my_squeel_query
base = self
commenters.
.where{
# Note: This code does work. Because it's awesome.
__send__("#{base.class.to_s.singularize}_comment_associations").
article_id.eq(my{self.id})
}
end
You can do this if you generate the methods dynamically. The Module.included method is provided for this purpose:
module ModuleAsLikeArticle
def self.included(base)
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This gets triggered when the module is imported with include and allows you to create methods specifically tailored for that.
As a note you might want to use base.name.underscore.singularize for a more readable method name. By convention, method names should not have upper-case in them, especially not as the first character.
Conventional Rails type applications use a different approach, though, instead defining a class method that can be used to create these on-demand:
module ModuleAsLikeArticle
def has_comments
base.send(:define_method, "#{base.to_s.singularize}_comment_associations") do
# ...
end
end
end
This would be used like this:
class ModelAsLikeArticle < ActiveRecord::Base
extend MyModule
has_comments
end
Since the method is not created until has_comments is called, you can safely extend ActiveRecord::Base and then insert the appropriate call in all the classes which require that functionality.
I think you might find what you need in the Rails Reflection class (http://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html), which, as the page says, allows you to interrogate ActiveRecord classes about their associations and aggregations.

Ruby on Rails: shared method between models

If a few of my models have a privacy column, is there a way I can write one method shared by all the models, lets call it is_public?
so, I'd like to be able to do object_var.is_public?
One possible way is to put shared methods in a module like this (RAILS_ROOT/lib/shared_methods.rb)
module SharedMethods
def is_public?
# your code
end
end
Then you need to include this module in every model that should have these methods (i.e. app/models/your_model.rb)
class YourModel < ActiveRecord::Base
include SharedMethods
end
UPDATE:
In Rails 4 there is a new way to do this. You should place shared Code like this in app/models/concerns instead of lib
Also you can add class methods and execute code on inclusion like this
module SharedMethods
extend ActiveSupport::Concern
included do
scope :public, -> { where(…) }
end
def is_public?
# your code
end
module ClassMethods
def find_all_public
where #some condition
end
end
end
You can also do this by inheriting the models from a common ancestor which includes the shared methods.
class BaseModel < ActiveRecord::Base
def is_public?
# blah blah
end
end
class ChildModel < BaseModel
end
In practice, jigfox's approach often works out better, so don't feel obligated to use inheritance merely out of love for OOP theory :)

Resources