Rails 3 run_callbacks with parameter - ruby-on-rails

i'm playing with run_callbacks and had a problem. Can somebody help me out?
## loveable.rb
module Loveable
extend ActiveSupport::Concern
included do
define_callbacks :love
end
def loved_by!(lover)
run_callbacks :love do
do_love(lover)
end
end
def do_love(lover)
...implementation goes here...
end
end
## product.rb
class Product < ActiveRecord::Base
include Loveable
set_callback :after, :love, :after_love
def after_love
## How to get lover here??
end
end
I need "lover" user in after_love method. How could I achieved that?
For now I using instance variable but don't like the solution.
## loveable.rb
def loved_by!(lover)
#lover = lover
run_callbacks...
end
## product.rb
def after_love
#lover.do_something
end
Any better idea?

Use an instance variable :)
The only serious concern you have with using instance variables in modules is the risk that the variable names used in the module will clash with names in the class itself or other included modules. If you use a sufficiently obscure name, say #_xxx_loveble_lover, that should effectively minimise the risk.
From your code it appears that lover is an attribute associated with each object, so instance variables are the best device for this.

Related

How to store custom class-level data on models, using Concerns

I'm trying to make a concern that checks if a user is subscribed to an appropriate plan for my SaaS app.
Here's basically what I'm trying to do:
module SubscriptionControlled extend ActiveSupport::Concern
class_methods do
def requires_subscription_to(perm)
##perms = [perm]
end
end
included do
validate :check_subscription
end
def check_subscription
##perms.each do |perm|
self.errors.add(:base, "Subscription upgrade required for access to this feature") unless self.user[perm]
end
end
end
This provides this api for a model:
class SomeModel < ApplicationModel
include SubscriptionControlled
requires_subscription_to :pro
end
The problem I'm having is that ##perms seems to be scoped to the CONCERN, rather than the MODEL. So this value is the same for all models. So whichever model is loaded last sets this value for all models.
eg: if loaded in this order:
Model1 -> sets ##perms to [:pro]
Model2 -> sets ##perms to [:business]
Both model 1 and model 2 will only require a subscription to :business
Is there a way of storing class-level variables in a concern that take effect on a per-model basis to accomplish this API?
I don't have a Ruby interpreter at hand right now but I'm fairly certain that using a single # in the class method should do the trick. Another thing that comes to mind is something along the lines of
included do
define_singleton_method :requires_subscription_to do |new_perm|
##perms ||= []
##perms << Array(new_perm)
end
end
Since that will create a new method every time the concern is included, it should work. I just seem to remember that methods defined like that are slightly slower - but since it will probably only be called during initialization, it shouldn't pose a problem in any case.
So I found the right way to do this using a ClassMethods module
module SubscriptionControlled extend ActiveSupport::Concern
module ClassMethods
#perms = []
def requires_subscription_to(perm)
#perms = [perm]
end
def perms
#perms
end
end
included do
validate :check_subscription
end
def check_subscription
self.class.perms.each do |perm|
self.errors.add(:base, "Subscription upgrade required for access to this feature") unless self.user[perm]
end
end
end
this keeps the permissions scoped to the class, not the concern.
I think you're overcomplicating this. You don't need the check_subscription method at all and that method is why you're trying to make ##perms (or #perm) work.
validate is just a class method like any other and you can give validate block. You can use that block to capture the perm and do away with all the extra machinery:
module SubscriptionControlled extend ActiveSupport::Concern
module ClassMethods
def requires_subscription_to(perm)
validate do
self.errors.add(:base, "Subscription upgrade required for access to this feature") unless self.user[perm]
end
end
end
end

Rails: Concern with before_filter type of method

I am just getting my hands on Concerns in Rails and try to implement a simple logging for ActiveRecord classes. In there I want to define the field that should go into the log and have the log written automatically after save.
What I have is this:
#logable.rb (the concern)
module Logable
extend ActiveSupport::Concern
#field = nil
module ClassMethods
def set_log_field(field)
#feild = field
end
end
def print_log
p "LOGGING: #{self[#index.to_s]}"
end
end
#houses.rb (the model using the concern)
class House < ActiveRecord::Base
include Logable
after_save :print_log
set_log_field :id
end
Unfortunately the call to set_log_field does not have an effect - or rather the given value does not make it to print_log.
What am I doing wrong?
Thanks for your help!
You probably mean this (btw, why not Loggable?):
# logable.rb
module Logable
extend ActiveSupport::Concern
# Here we define class-level methods.
# Note, that #field, defined here cannot be referenced as #field from
# instance (it's class level!).
# Note also, in Ruby there is no need to declare #field in the body of a class/module.
class_methods do
def set_log_field(field)
#field = field
end
def log_field
#field
end
end
# Here we define instance methods.
# In order to access class level method (log_field), we use self.class.
included do
def print_log
p "LOGGING: #{self.class.log_field}"
end
end
end
Update You also asked about what's the difference between methods in included block and those within method body.
To make a short resume there is seemingly no difference. In very good approximation you can consider them the same. The only minor difference is in dependency management. Great illustration of it is given in the end of ActiveSupport::Concern documentation. It worth reading, take a look!

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

Rails: side effect of including the same filter twice

I have a gem I'm developing that is based around using filters on ApplicationController. It's basically for logging, and one of the modules defines an around filter like so:
module LogExceptionFilter
self.included(base)
base.around_filter :do_a_bunch_of_logging_stuff
end
def do_a_bunch_of_logging_stuff
...
end
end
It happens to be an around filter where I deal with exception logging, but my question would apply for any filter.
So it's supposed to be used like this
class ApplicationController
include LogExceptionFilter
end
So what I'm worried about is if someone does:
class ApplicationController
include LogExceptionFilter
include LogExceptionFilter
end
I don't want to execute do_a_bunch_of_logging_stuff twice. So first
1)If do_a_bunch_of_logging_stuff is included twice, will rails apply the filter twice?
2)Is it my responsibility to protect the user from doing this? I could do so with a class variable, something like:
module LogExceptionFilter
class << self
cattr_accessor :filter_loaded
end
self.included(base)
unless filter_loaded
base.around_filter :do_a_bunch_of_logging_stuff
filter_loaded = true
end
end
def do_a_bunch_of_logging_stuff
...
end
end
This variable is not thread safe so it's something that I'd want to be careful about putting in. But I don't want to write a library that can be easily broken. Thanks.
Here are some relevant links:
http://www.ruby-forum.com/topic/95269
http://www.ruby-forum.com/topic/164588
Basically, a module will only be included once, but the included callback may be called multiple times.

how to define/attach rails validations in multiple generations of modules

I've found a way to make this work, but am curious about a better way / the Rails 3 way. (I'm using 2.3.5 still, but hope to migrate around New Year's.)
The situation: I've got two layers of module inheritance, the second layer gets mixed into a Rails model. Both modules define validation methods and I'd like both of them to attach the validations to the base class, but because of the two levels of inheritance, the following doesn't work:
def self.included(base)
base.validate :yadda_yadda
end
When that module is included by another module, the interpreter grinds to a screeching halt because Modules don't know about ActiveRecord::Validations. Including the validations module begs the question of "where is save?" thanks to alias_method.
The following works, as long as you remember to call super whenever you override validate(). I don't trust myself or future maintainers to remember that, so I'd like to use the validate :yadda_yadda idiom instead, if possible.
module Grandpa
def validate
must_be_ok
end
def must_be_ok
errors.add_to_base("#{self} wasn't ok")
end
end
module Dad
include Grandpa
def validate
super
must_be_ok_too
end
def must_be_ok_too
errors.add_to_base("#{self} wasn't ok either")
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
def must_be_ok_three
errors.add_to_base("#{self} wasn't ok furthermore")
end
end
Suggestions? Tips for Rails 3 approach? I don't think the validations API has changed that much.
I solved it (when I ran into the same problem, but with something other than validation).
Short answer: you can call send(:included, base) on the module you want to bring in. Within the higher-up included() definition, you need to check whether the base is a Class or a Module.
Why would you ever want to do this? Well, I've got some modules that extract some common functionality out of my models. For instance, the module HasAllocable sets up a polymorphic belongs_to relationship, and a getter/setter pair for a virtual attribute. Now I have another module that needs to pull in HasAllocable, to spare the base classes from having to remember it.
I'd be interested to know whether this smells funny to anyone. I haven't seen anything like it on the web, so I wonder if multiple layers of model inheritance is more of an antipattern.
module Grandpa
def self.included(base)
if base.kind_of?(Class)
base.validate :must_be_ok
end
end
end
module Dad
include Grandpa
def self.included(base)
if base.kind_of?(Class)
# you can do this
#base.send(:include, Grandpa)
# you can also do this
Grandpa.send(:included, base)
# this does not invoke Grandpa.included(Kid)
#super(base)
base.validate :must_be_ok_too
end
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
end

Resources