Ruby: Can I use instance methods inside a class method? - ruby-on-rails

I have a class that contains this class method:
def self.get_event_record(row, participant)
event = Event.where(
:participant_id => participant.id,
:event_type_code => row[:event_type],
:event_start_date => self.format_date(row[:event_start_date])
).first
event = Event.new(
:participant_id => participant.id,
:event_type_code => row[:event_type],
:event_start_date => self.format_date(row[:event_start_date])
) if event.blank?
event
end
And I also have, in the same class, an instance method:
def format_date(date)
parsed_date = date.split('/')
# if month or day are single digit, make them double digit with a leading zero
if parsed_date[0].split("").size == 1
parsed_date[0].insert(0, '0')
end
if parsed_date[1].split("").size == 1
parsed_date[1].insert(0, '0')
end
parsed_date[2].insert(0, '20')
formatted_date = parsed_date.rotate(-1).join("-")
formatted_date
end
I'm getting an 'undefined method' error for #format_date. (I tried it without the self in front, at first). Can you not use instance methods in class methods of the same class?

Short answer is no, you cannot use instance methods of a class inside a class method unless you have something like:
class A
def instance_method
# do stuff
end
def self.class_method
a = A.new
a.instance_method
end
end
But as far as I can see, format_date does not have to be an instance method. So
write format_date like
def self.format_date(date)
# do stuff
end

Just create class method
def self.format_date (..)
...
end
And if u need instance method, delegate it to class method
def format_date *args
self.class.format_date *args
end
And i don't think that it is good idea to call instance methods from class scope

You could do YourClassName.new.format_date(your_date), although I think it's pretty clear you should be restructuring your code - this method probably doesn't belong on an instance. Why don't you extend the Date Class, or make format_date a class method on the class you are using?
EDIT: Here are a few other things to think about with your code:
Your whole format_date method goes to a lot of lengths to manipulate dates as strings. Why not use Ruby's Date Class? Using Date.parse or Date.strptime or even "01/01/2001".to_date might be useful depending on your locale
Consider extending the String class for your method, if you really need to make your own method:
class String
def to_friendly_formatted_date
Date.strptime(self, "%d/%m/%y")
end
end
"01/08/09".to_friendly_formated_date
Your class method is crying our for the find_or_initialize_by helper methods:
self.get_event_record(row, participant)
find_or_initialize_by_participant_id_and_event_type_code_and_event_start_date(:participant_id => participant.id, :event_type_code => row[:event_type_code], :event_start_date => row[:event_start_date].to_friendly_formatted_date)
end
By god it's long, but it achieves what you're trying to do more elegantly (although I'm open to argument!)

Related

Loss of context in multiple blocks

I'm trying to create a super class that will be extended. The super class will call a method that has to be implemented by the child class. The thing is, that method is called sometimes 3 blocks deep. In those blocks, I also refer to attributes of the class.
But, I get an error saying that there is no variable or method, and it's because the methods and variables are assumed to be from the block class.
This is how it looks like:
class SuperClass
attr_accessor :model
def initialize(model)
#model = model
end
def resources
s = Tire.search(get_index) do
query do
boolean do
must { term :model_id, model.id } #attr_accessor fails
must { all }
search_scope(self) #search_scope fails
end
end
sort do
sort_scope(self) #sort_scope fails
end
end
s.results
end
end
class SubClass < SuperClass
attr_accessor :params
def initialize(model, params)
#params = params
super(model)
end
def search_scope(boolean_query)
boolean_query.must { term field: params[:feild] }
#...
end
def sort_scope(sort_query)
sort_query.by :field, params[:sort_dir]
#...
end
end
search = SubClass.new(model, {})
results = search.resources # undefined method error as explained below
What I'm trying to achieve is calling the method search_scope and sort_scope (Implemented in child classes) that will set also set a few search and sort parameters. But I get undefined method 'search_scope' for #<Tire::Search::BooleanQuery:0x00000004fc9820>. As you can see, it's trying to call search_scope on the class of the block context. Same with the attr_accessor :model.
I know I can remedy this by doing
def resources
instance = self
# ...
end
And then calling instance.model and instance.search_scope, but this means my child classes have to define the instance in their own search_scope and sort_scope methods too.
I was wondering whether there is a better way to solving this?

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

Ruby: invoke class methods in a module

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

How to discover the overrided methods in Ruby/Rails?

Hey guys.
How do I know the methods that a child class overrided in my super class?
I have this:
class Test
def self.inherited(child)
# child.overrided_methods???
end
def self.foo
end
def self.bar
end
end
def Child < Test
def self.bar
puts "bar"
end
end
The method self.inherited is called when a subclass of Test is loaded. So I get the reference to this subclass in child, but I don't know how to get the methods that were overrided by this subclass.
Any ideas?
--
Arsen suggested the use of self.method_added(name) instead of self.inherited(child), but this method catches only instance methods and I want to catch class methods. Does anyone know another methods that does the same thing but with class methods?
In the last case I'll consider using a singleton and convert all this class methods to instance methods then the problem is solved.
For instance methods there is an Object::method_added(name) method you can override, similar to 'inherited' you have used:
class test
def self.method_added(name)
puts "method_added(#{name.inspect})"
super
end
end
irb(main):002:0> class Child < Test; def foo; end; end
method_added(:foo)
=> nil
You can then compare a received name to a list of your methods:
Test.instance_methods.include?(name.to_s)
With class methods this approach does not work (even if you do things like class << self magic), but a helpful fellow knew the answer: http://www.ruby-forum.com/topic/120416 :
class Test
def self.singleton_method_added(name)
puts "Class method added #{name.inspect}"
end
end
This is only the first part of the problem, because you need to know which class defined the method (it will be self) and whether the method is a new one, or overridden one. Experiment with this code:
class Test
def self.singleton_method_added(name)
if self == Test
puts "My own class method added: #{self.name}.#{name.inspect}"
elsif Test.methods(false).include?(name.to_s)
puts "Class method overriden: #{self.name}.#{name.inspect}"
elsif Test.methods(true).include?(name.to_s)
puts "My parent's class method overriden: #{self.name}.#{name.inspect}"
else
puts "New class method added: #{self.name}.#{name.inspect}"
end
end
end
Maybe a first step to the solution:
By calling child.instance_method(:bar) (if child refers to the class) or child.method(:bar) (if it refers to an instance of Child) you can get an UnboundMethod or Method object representing your method:
a = Test.instance_method(:foo)
b = Child.instance_method(:foo)
Unfortunately, a == b evaluates to false, although both refer to the same method.
def overridden_methods
klass = self.class
klass.instance_methods.select {|m| klass.instance_method(m).owner == klass}
end
Change according to your needs.

Make all subclasses of ActiveRecord::Base methods say their name

For cruft-removal purposes I would like to log whenever a method from one of my AR models is called.
I can get get all those classes with something like this:
subclasses = [] ; ObjectSpace.each_object(Module) {|m| subclasses << m if m.ancestors.include? ActiveRecord::Base } ; subclasses.map(&:name)
But then I need a list of only the methods defined on those classes (instance and class methods), and a way to inject a logger statement in them.
The result would be the equivalent of inserting this into every method
def foo
logger.info "#{class.name} - #{__method__}"
# ...
end
def self.foo
logger.info "#{name} - #{__method__}"
# ...
end
How can I do that without actually adding it to every single method?
Some awesome meta perhaps?
If you want only the methods defined in the class you can do this:
>> Project.instance_methods
=> ["const_get", "validates_associated", "before_destroy_callback_chain", "reset_mocha", "parent_name", "inspect", "slug_normalizer_block", "set_sequence_name", "require_library_or_gem", "method_exists?", "valid_keys_for_has_and_belongs_to_many_association=", "table_name=", "validate_find_options_without_friendly", "quoted_table_name" (another 100 or so methods)]
Only the methods defined in your class
>> Project.instance_methods(false)
=> ["featured_asset", "category_list", "before_save_associated_records_for_slugs", "asset_ids", "primary_asset", "friendly_id_options", "description", "description_plain"]
You should be using Aspect Oriented Programming pattern for this. In Ruby Aquarium gem provides the AOP DSL.
Create a log_method_initializer.rb in config/initializers/ directory.
require 'aquarium'
Aspect.new(:around, :calls_to => :all_methods,
:in_types => [ActiveRecord::Base] ) do |join_point, object, *args|
log "Entering: #{join_point.target_type.name}##{join_point.method_name}"
result = join_point.proceed
log "Leaving: #{join_point.target_type.name}##{join_point.method_name}"
result
end
Every method calls of classes inherited from ActiveRecord::Base will be logged.
You have
AR::Base.instance_methods
and
AR::Base.class_eval "some string"
so you can probably use them to put a header on every existing method.
For instance method call you can use this proxy pattern:
class BlankSlate
instance_methods.each { |m| undef_method m unless m =~ /^__/ }
end
class MyProxy < BlankSlate
def initialize(obj, &proc)
#proc = proc
#obj = obj
end
def method_missing(sym, *args, &block)
#proc.call(#obj,sym, *args)
#obj.__send__(sym, *args, &block)
end
end
Example:
cust = Customer.first
cust = MyProxy.new(cust) do |obj, method_name, *args|
ActiveRecord::Base.logger.info "#{obj.class}##{method_name}"
end
cust.city
# This will log:
# Customer#city
This is inspired from: http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
You will need to find a way to apply this pattern on ActiveRecord::Base object creation.
For Aquarium, seems like adding method_options => :exclude_ancestor_methods does the trick.
I had the stack too deep problem as well.
Source
http://andrzejonsoftware.blogspot.com/2011/08/tracing-aspect-for-rails-application.html

Resources