Each user has many roles; to find out whether a user has the "admin" role, we can use the has_role? method:
some_user.has_role?('admin')
Which is defined like this:
def has_role?(role_in_question)
roles.map(&:name).include?(role_in_question.to_s)
end
I'd like to be able to write some_user.has_role?('admin') as some_user.is_admin?, so I did:
def method_missing(method, *args)
if method.to_s.match(/^is_(\w+)[?]$/)
has_role? $1
else
super
end
end
This works fine for the some_user.is_admin? case, but fails when I try to call it on a user referenced in another association:
>> Annotation.first.created_by.is_admin?
NoMethodError: undefined method `is_admin?' for "KKadue":User
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.4/lib/active_record/associations/association_proxy.rb:215:in `method_missing'
from (irb):345
from :0
What gives?
Rails checks if you respond_to? "is_admin?" before doing a send.
So you need to specialize respond_to? also like:
def respond_to?(method, include_private=false)
super || method.to_s.match(/^is_(\w+)[?]$/)
end
Note: Don't ask me why rails checks for respond_to? instead of just doing a send there, I don't see a good reason.
Also: The best way (Ruby 1.9.2+) is to define respond_to_missing? instead, and you can be compatible with all versions with something a bit fancy like:
def respond_to_missing?(method, include_private=false)
method.to_s.match(/^is_(\w+)[?]$/)
end
unless 42.respond_to?(:respond_to_missing?) # needed for Ruby before 1.9.2:
def respond_to?(method, include_private=false)
super || respond_to_missing?(method, include_private)
end
end
The ActiveRecord::Associations::AssociationProxy class overrides method_missing and intercepts the call you are looking for before it gets to the model.
This happens because AP checks if the model respond_to? the method, which in your case, it doesn't.
You have a few solutions aside from editing Rails' source:
First, manually define each of the is_* methods for the user object using metaprogramming. Something like:
class User
Role.all.each do |role|
define_method "is_#{role.name}?" do
has_role?(role.name)
end
end
end
Another is to load the User object via some other means such as
User.find(Annotation.first.user_id).is_admin?
Or use one of the other answers listed.
Related
We are trying to overide the reindex method of Searchkick in order to avoid reindexing in case we are on local env.
So we created a initializers/record_indexer.rb :
class Searchkick::RecordIndexer
def reindex(options= {})
unless Rails.env == 'local'
super(options)
end
end
end
When I try to update an associated model that cause a reindexation of my 'indexed record' it throws a NoMethodError (super: no superclass method `reindex' for #<Searchkick::RecordIndexer)
I notice that searchkick has at least 3 reindex methods in:
Searchkick::RecordIndexer (for Foo.first.reindex I guess)
Searchkick::Index (for Foo.reindex I guess)
Searchkick::Model (for ??)
Did someone already have this kind of problematic on the #reindex method of Gem Searckick (v4.4.2)?
In your code you are completely replacing a method with your implementation.
If you override a method, and want to call the original, you have two options:
Store the original method with an alias
class Searchkick::RecordIndexer
alias_method :orig_reindex, :reindex
def reindex(options={})
unless Rails.env == 'local'
orig_reindex(options)
end
end
end
Prepend a module
module YourPatch
def reindex(options={})
unless Rails.env == 'local'
super # no need to specify args if it's just pass-through
end
end
end
Searchkick::RecordIndexer.prepend(YourPatch)
I am using the active_record-acts_as gem to implement multi-table inheritance in my rails app. This gem helps a child class act like the parent class, so it responds to a parent class's methods. I want to make the parent class also respond to the child class's methods because it simplifies routing.
So far I have:
class ParentClass < ActiveRecord::Base
actable as: :assetable
def method_missing_with_specific(method, *args, &block)
# specific is the associated child class instance
if specific.respond_to?(method)
specific.send(method, *args, &block)
else
method_missing_without_specific(method, *args, &block)
end
end
alias_method_chain :method_missing, :specific
def is_a_with_specific?(type)
if assetable_type.constantize == type
true
else
is_a_without_specific?(type)
end
end
alias_method_chain :is_a?, :specific
end
This works great, but I'm having trouble implementing the respond_to? method to go with the method_missing one.
I tried:
def respond_to?(method, private=false)
super || specific.respond_to?(method, private)
end
and:
def respond_to_with_specific?(method, private=false)
if specific.respond_to?(method, private)
true
else
respond_to_without_specific?(method, private)
end
end
alias_method_chain :respond_to?, :specific
Both of these methods result in my tests failing with:
Failure/Error: Unable to find matching line from backtrace
SystemStackError:
stack level too deep
# /Users/blueye/.rvm/gems/ruby-2.1.1/gems/activerecord-4.1.2/lib/active_record/transactions.rb:286
#
# Showing full backtrace because every line was filtered out.
# See docs for RSpec::Configuration#backtrace_exclusion_patterns and
# RSpec::Configuration#backtrace_inclusion_patterns for more information.
Clearly I am causing some sort of infinite recursion when interacting with ActiveRecord, but I'm not sure how.
How can I implement respond_to? in this case?
Update:
I found the following code in the gem:
def respond_to?(name, include_private = false)
super || acting_as.respond_to?(name)
end
Which seems like it would create some sort of circular logic where each class keeps delegating respond_to? to the other. I tried overriding this method in the child classes, but calling super seems to just delegate to the included method from the gem module.
You are calling respond_to? inside your respond_to_with_specific? method, hence infinite recursion.
Since Child classes should have all the methods of the Parents class plus some methods, you should be able to just do:
def respond_to_with_specific?(method, private=false)
specific.respond_to_without_specific?(method, private)
end
This is the weirdest thing ever happened to me with ruby/rails.
I have a model, Store, which has_many Balances. And I have a method that gives me the default balance based on the store's currency.
Store model.
class Store < ActiveRecord::Base
has_many :balances, as: :balanceable, dependent: :destroy
def default_balance
#puts self.inspect <- weird part.
balances.where(currency: self.currency)[0]
end
...
end
Balance model.
class Balance < ActiveRecord::Base
belongs_to :balanceable, :polymorphic => true
...
end
Ok, so then in the Balance controller I have the show action, that will give me a specific balance or the default one.
Balance controller.
class Api::Stores::BalancesController < Api::Stores::BaseController
before_filter :load_store
# Returns a specific alert
# +URL+:: GET /api/stores/:store_id/balances/:id
def show
#puts #store.inspect <- weird part.
#balance = (params[:id] == "default") ? #store.default_balance : Balance.find(params[:id])
respond_with #balance, :api_template => :default
end
...
private
# Provides a shortcut to access the current store
def load_store
#store = Store.find(params[:store_id])
authorize! :manage, #store
end
end
Now here is where the weird part comes...
If I make a call to the show action; for example:
GET /api/stores/148/balances/default
It returns null (because the currency was set as null, and there is no Balance with null currency), and the SQL query generated is:
SELECT `balances`.* FROM `balances` WHERE `balances`.`balanceable_id` = 148 AND `balances`.`balanceable_type` = 'Store' AND `balances`.`currency` IS NULL
So I DON'T know why... it is setting the currency as NULL. BUT if in any where in that process I put
puts #store.inspect
or inside the default_balance method:
puts self.inspect
it magically works!!!.
So I don't know why is that happening?... It seems like the store object is not getting loaded until I "inspect" it or something like that.
Thanks
Sam and Adrien are on the right path.
ActiveRecord overrides method_missing to add a whole bunch of dynamic methods including the accessors for the column-backed attributes like Store#currency. While I'm glossing over a lot, suffice it to say that when the logic is invoked then the dynamic class/instance methods are added to the Store class/instances so that subsequent calls no longer require the method_missing hook.
When YOU overrode method_missing without calling super, you effectively disabled this functionality. Fortunately, this functionality can be invoked by other means, one of which you tripped upon when you called store#inspect.
By adding the call to super, you simply assured that ActiveRecord's dynamic methods are always added to the class when they're needed.
OK finally after a lot of debugging, I found the reason...
In the Store model I have a method_missing method and I had it like this:
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
end
end
So when I was calling self.currency it went first to the method_missing and then returned null. What I was missing here was the super call.
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
else
super
end
end
But I continue wondering why after I had called puts #store.inspect or puts self.inspect it worked well?. I mean, why in that case that super call wasn't needed?
I'm installing a forum using the Forem gem. There's an option that allows avatar personalization, since it's possible to login with Facebook. You just specify your method in the User model and that's it.
# Forem initializer
Forem.avatar_user_method = 'forem_avatar'
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
end
end
But I want a fallback on Gravatar for normal, non-facebook accounts. I've found the method on Forem and in theory, I need to call the avatar_url method:
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
else
Forem::PostsHelper.avatar_url self.email
end
end
However, Forem isn't an instance, but a module and I can't call it nor create a new instance. The easy way is to copy the lines of that method, but that's not the point. Is there a way to do it?
Thanks
Update
Both answers are correct, but when I call the method either way, there's this undefined local variable or method 'request' error, which is the last line of the original avatar_url.
Is there a way to globalize that object like in PHP? Do I have to manually pass it that argument?
perhaps reopen the module like this:
module Forem
module PostsHelper
module_function :avatar_url
end
end
then call Forem::PostsHelper.avatar_url
if avatar_url call other module methods, you'll have to "open" them too via module_function
or just include Forem::PostsHelper in your class and use avatar_url directly, without Forem::PostsHelper namespace
If you want to be able to use those methods in the user class, include them and use
class User < ActiveRecord::Base
include Forem::PostsHelper
def forem_avatar
return user_pic if user_pic.present?
avatar_url email
end
end
Another way would be to set the Forem.avatar_user_method dynamically since the Forem code checks it it exists before using it and defaults to avatar_url if it does not.
class User < ActiveRecord::Base
# This is run after both User.find and User.new
after_initialize :set_avatar_user_method
# Only set avatar_user_method when pic is present
def set_avatar_user_method
unless self.user_pic.empty?
Forem.avatar_user_method = 'forem_avatar'
end
end
def forem_avatar
self.user_pic
end
end
This way you dont pollute your model with unnecessary methods from Forem and don't monkey patch Forem itself.
I have submissions that might be in various states and wrote a method_missing override that allows me to check their state with calls like
submission.draft?
submission.published?
This works wonderfully.
I also, for various reasons that might not be so great, have a model called Packlet that belongs_to a meeting and belongs_to a submission. However, I was surprised to find that
packlet.submission.draft?
returns a NoMethodError. On the other hand, if I hard-code a #draft? method into Submission, the above method call works.
How do I get my method_missing methods to be recognized even when the instance is defined via an ActiveRecord association?
Have you added the draft? method to your respond_to? method for that object? My guess would be that the issue might arise there. What happens when you type:
submission.respond_to?(:draft?)
To fix this, actually write a respond_to? method like this:
def respond_to?(method, include_priv = false) #:nodoc:
case method.to_sym
when :draft?, :published?
true
else
super(method, include_priv)
end
end
My recommendation would be to implement this without using method_missing instead though, so by doing some meta-programming like this:
class Submission
[:draft, :published].each do |status|
define_method "#{status}?" do
status == "#{status}?"
end
end
end