Ruby: invoke class methods in a module - ruby-on-rails

I have a module, whose purpose is to act on any given ActiveRecord instance.
For argument's sake, let's say that this method puts the string "match" if it matches certain properties with another instance of the same type.
module Foo
def check_against_other_instances
self.all.each do |instance|
if self.respond_to? :color && self.color == instance.color
puts "match"
end
end
end
end
However, I can't just simply call self.all here, because self is an instance. How do I call the class method all from here?

Ah.. found the solution almost right after I asked...
self.class.all.each do |instance|
...

if you want to extend the behavior of rails classes, then you are best of using ActiveSupport::Concern!
http://apidock.com/rails/ActiveSupport/Concern

You can pull the name of a class from an instance and then constantize it.
For example, given a class Thing:
t = Thing.new
t.class.name
# => "Thing"
t.class.name.constantize
# => Thing

Related

No method error for class variable defined with attr_accessor

I want to define methods dynamically using an array of strings.
Here is a simple piece of code that should achieve that.
class SomeClass
attr_accessor :my_array
def initialize(user, record)
#my_array=[]
end
my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
When I instantiate this class in the console, I get the following error
NoMethodError (undefined method `each' for nil:NilClass)
What is wrong with this code and how to make it work. any help highly appreciated :)
Edit 1:
What I ultimately want to achieve is to inherit from SomeClass and override my_array in the child class to dynamically define methods with its attributes like so
class OtherClass < SomeClass
my_array = %w[method1 method2 method3]
# Some mechanism to over write my_array.
end
And then use self.inherited to dynamically define methods in child class.
Is there a good way to achieve this?
In your code, you use an instance variable (#my_array) and an attr_accessor over it, and then try to access my_array from class level (that is, from the body of the class definition, outside of any methods). But instance variables only exist at instance level, so it is not available in the class scope.
One solution (the natural one, and the one which you would probably use in other languages) is to use a class variable: ##my_array. But class variables in ruby are a little problematic, so the best solution would be to make use of class instance variables, like that:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def initialize(user, record)
end
#my_array.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end
The syntax is a little tricky, so, if you look that up and it still doesn't makes sense, try just reading about scopes and using a regular class variable with ##.
Edit:
Ok, so, after your edit, it became more clear what you are trying to accomplish. A full working example is like follows:
class SomeClass
class << self
attr_accessor :my_array
end
#my_array=[]
def awesome_method
puts 'awesome'
end
def self.build!
#my_array.each do |element|
self.define_method("#{element}?".to_sym){ awesome_method }
end
end
end
class ChildClass < SomeClass
#my_array = %w[test little_test]
self.build!
end
child_instance = ChildClass.new
child_instance.test?
>> awesome
child_instance.little_test?
>> awesome
So, I've made some tweaks on SomeClass:
It does not need an initialize method
I tried to use the inherited hook for this problem. It won't ever work, because this hook is called as soon as "ChildClass < SomeClass" is written, and this must be before you can define something like #my_array = %w[test little_test]. So, I have added a self.build! method that must be called in the child instances so that they build their methods from my_array. This is inevitable, but I think it is also good, because it makes more explicit in the subclasses that you are doing something interesting there.
I think you want "define_method", not "alias_method".
awesome_method in passed in a block, which is ruby's way of doing functional programming.
With that done, ChildClass inherits from SomeClass, and it's instances have the dynamically created methods 'test?' and 'little_test?'.
You need to change my_array to class level accessible, in my case class constant.
class SomeClass
DYNAMIC_METHOD_NAMES = %w(method_a method_b method_C).freeze
def initialize(user, record)
end
DYNAMIC_METHOD_NAMES.each do |element|
alias_method "#{element}?".to_sym, :awesome_method
end
def awesome_method
puts 'awesome'
end
end

Pass a symbol to a method and call the corresponding method

In a Rails controller you can pass a symbol to the layout method that corresponds to a method in you controller that will return the layout name like this:
layout :my_method
def my_method
'layout_1'
end
I want to have a similar functionality to likewise pass a symbol to my classes method and that class should call the corresponding function and use its return value, like this
myClass.foo :my_method
def my_method
'layout_1'
end
I've read posts[1] that tell me I need to pass
myClass.foo(method(:my_method))
which I find ugly and inconvenient. How is rails here different allowing to pass just the symbol without any wrapper? Can this be achieved like Rails does it?
[1] How to implement a "callback" in Ruby?
If you want to only pass a :symbol into the method, then you have to make assumptions about which method named :symbol is the one you want called for you. Probably it's either defined in the class of the caller, or some outer scope. Using the binding_of_caller gem, we can snag that information easily and evaluate the code in that context.
This surely has security implications, but those issues are up to you! :)
require 'binding_of_caller'
class Test
def foo(sym)
binding.of_caller(1).eval("method(:#{sym})").call
end
end
class Other
def blork
t = Test.new
p t.foo(:bar)
p t.foo(:quxx)
end
def bar
'baz'
end
end
def quxx
'quxx'
end
o = Other.new
o.blork
> "baz"
> "quxx"
I still don't understand, what is author asking about. He's saying about "callbacks", but only wrote how he wants to pass parameter to some method. What that method(foo) should do - i have no idea.
So I tried to predict it's implementation. On class initialising it gets the name of method and create private method, that should be called somewhere under the hood. It possible not to create new method, but store method name in class variable and then call it somewhere.
module Foo
extend ActiveSupport::Concern
module ClassMethods
def foo(method_name)
define_method :_foo do
send method_name
end
end
end
end
class BaseClass
include Foo
end
class MyClass < BaseClass
foo :my_method
private
def my_method
"Hello world"
end
end
MyClass.new.send(:_foo)
#=> "Hello world"
And really, everything is much clearer when you're not just wondering how it works in rails, but viewing the source code: layout.rb

Dynamically defining instance method within an instance method

I have a several classes, each of which define various statistics.
class MonthlyStat
attr_accessor :cost, :size_in_meters
end
class DailyStat
attr_accessor :cost, :weight
end
I want to create a decorator/presenter for a collection of these objects, that lets me easily access aggregate information about each collection, for example:
class YearDecorator
attr_accessor :objs
def self.[]= *objs
new objs
end
def initialize objs
self.objs = objs
define_helpers
end
def define_helpers
if o=objs.first # assume all objects are the same
o.instance_methods.each do |method_name|
# sums :cost, :size_in_meters, :weight etc
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
end
end
end
YearDecorator[mstat1, mstat2].yearly_cost_sum
Unfortunately define method isn't available from within an instance method.
Replacing this with:
class << self
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
...also fails because the variables method_name and objs which are defined in the instance are no longer available. Is there an idomatic was to accomplish this in ruby?
(EDITED: I get what you're trying to do now.)
Well, I tried the same approaches that you probably did, but ended up having to use eval
class Foo
METHOD_NAMES = [:foo]
def def_foo
METHOD_NAMES.each { |method_name|
eval <<-EOF
def self.#{method_name}
\"#{method_name}\".capitalize
end
EOF
}
end
end
foo=Foo.new
foo.def_foo
p foo.foo # => "Foo"
f2 = Foo.new
p f2.foo # => "undefined method 'foo'..."
I myself will admit it's not the most elegant solution (may not even be the most idiomatic) but I've run into similar situations in the past where the most blunt approach that worked was eval.
I'm curious what you're getting for o.instance_methods. This is a class-level method and isn't generally available on instances of objects, which from what I can tell, is what you're dealing with here.
Anyway, you probably are looking for method_missing, which will define the method dynamically the first time you call it, and will let you send :define_method to the object's class. You don't need to redefine the same instance methods every time you instantiate a new object, so method_missing will allow you to alter the class at runtime only if the called method hasn't already been defined.
Since you're expecting the name of a method from your other classes surrounded by some pattern (i.e., yearly_base_sum would correspond to a base method), I'd recommend writing a method that returns a matching pattern if it finds one. Note: this would NOT involve making a list of methods on the other class - you should still rely on the built-in NoMethodError for cases when one of your objects doesn't know how to respond to message you send it. This keeps your API a bit more flexible, and would be useful in cases where your stats classes might also be modified at runtime.
def method_missing(name, *args, &block)
method_name = matching_method_name(name)
if method_name
self.class.send :define_method, name do |*args|
objs.inject(0) {|obj, sum| sum + obj.send(method_name)}
end
send name, *args, &block
else
super(name, *args, &block)
end
end
def matching_method_name(name)
# ... this part's up to you
end

Rails: Passing Variables from a Class Method to an Instance Method

I have several models that share a concern. Each model passes in a hash, which is meant to handle minor differences in the way they use the concern. I pass the hash in through a class method like so:
add_update_to :group, :user
The full code for the concern is:
module Updateable
extend ActiveSupport::Concern
attr_accessor :streams
module ClassMethods
def add_updates_to(*streams)
#streams = streams
end
end
module InstanceMethods
def update_streams
#streams.collect{|stream| self.public_send(stream)}
end
end
included do
has_one :update, :as => :updatable
after_create :create_update_and_history
end
private
def create_update_and_history
update = self.create_update(:user_id => User.current.id)
self.update_streams.each do |stream|
stream.histories.create(:update_id => update.id)
end
end
end
Most of this code works, but I'm having trouble passing the hash from the class to an instance. At the moment, I'm trying to achieve this effect by creating a virtual attribute, passing the hash to the attribute, and then retrieving it in the instance. Not only does this feel hacky, it doesn't work. I'm assuming it doesn't work because #streams is an instance variable, so the class method add_update_to can't actually set it?
Whatever the case, is there a better way to approach this problem?
You could probably use class variables here, but those are pretty reviled in the Ruby community due to their unpredictable nature. The thing to remember is that classes in Ruby are actually also instances of classes, and can have their own instance variables that are only accessible to themselves, and not accessible to their instances (if that is in any way clear).
In this case, you are defining behavior, and not data, so I think neither instance nor class variables are appropriate. Instead, I think your best bet is to define the instance methods directly within the class method, like this:
module Updateable
extend ActiveSupport::Concern
module ClassMethods
def add_updates_to(*streams)
define_method :update_streams do
streams.collect {|stream| public_send(stream) }
end
end
end
end
BTW, there is no hash involved here, so I'm not sure what you were referring to. *streams collects your arguments into an Array.

Can I define_method in rails models?

My rails model has code that is attempting to define_method(method_name) inside the model.
I keep getting:
NoMethodError: undefined method `define_method'
What am I doing wrong? Am I doing this in the wrong place. I need this method attached to this model. Where else can I define this method?
EDIT:
For those asking to see the code:
for field in rdev_fields
next if self.attributes.include?(field)
count = count + 1
rdev_hash[field.to_sym] = self.attributes["attribute#{count}"]
if !self.respond_to?(field) then
define_method("#{field}") do
self.send("attribute#{count}".to_sym)
end
end
end
There's nothing magical or about a rails model, it's just a normal class with a bunch of pre-existing methods,
So, the question is "can I define_method in a class"?
Part 1: Yes you can.
The important distinction is than you can define method in a class not in an instance method
For example:
class Cow
define_method "speak" do
"MOOOO"
end
end
Cow.new.speak
=> "MOOOO"
This should work fine. Note you're defining it on the class Cow, so any other Cows that you already have will automatically get that method added.
Part 2: What do you do if you want to define a method from within an instance method?
You can't define methods from an instance method, so you have to grab the class, and use that to define the method. Like this:
class Cow
def add_speak
self.class.send(:define_method, :speak) do
"MOOOO added"
end
end
end
Cow.new.speak
NoMethodError: undefined method 'speak' for #<Cow:0xb7c48530>
Cow.new.add_speak
Cow.new.speak
=> "MOOOO added"
Problem solved. Astute readers will note that in this example I'm using send(:define_method) - this is needed because define_method is private, and private methods are only accessible to the thing they're in. In this case, define_method is in the class, we are in the instance, so we can't directly access it.
As above though, we're adding the method directly to the class, so all other Cows which already exist will automatically also get the speak method added.
Part 3: What do you do if you want to define a method for only 1 object, not all objects of that class?
Example:
class Cow
def add_speak_just_me
class << self
define_method "speak" do
"MOOOO added for just me"
end
end
end
end
Cow.new.speak
NoMethodError: undefined method 'speak' for #<Cow:0xb7c72b78>
c = Cow.new
c.add_speak_just_me
c.speak
=> "MOOOO added for just me" # it works, hooray
Cow.new.speak # this new cow doesn't have the method, it hasn't been automatically added
NoMethodError: undefined method `speak' for #<Cow:0xb7c65b1c>
How does this work? Down the rabbithole you go!
Read this: http://dannytatom.me/metaid/ and good luck. It helps when you realise that 'adding a method' to an instance isn't actually adding it to the instance at all :-)
If you came here searching for how to dynamically define a CLASS method, because define_method wasn't working (because it defines INSTANCE methods), here is your answer:
Use define_singleton_method :)
was able to cobble this together. Very little understanding of what's actually going on though.
My instance method foo is opening the class and defining bar on it so that I can then call that on my instance. More experienced folks will let us know if this is opening a can of worms at the same time.
Would be useful to know your specific use for this though.
class User < ActiveRecord::Base
def foo
(class << self; self; end).class_eval do
define_method(:bar) {puts "bar"}
end
end
end
u = User.first
u.foo
u.bar #=> "bar"
The answer to your question is "yes, you can". As for why it's not working for you - it's impossible to say for sure why, if you don't provide some context for the code.

Resources