I am taking ruby-kickstart (Josh Cheek) challenges and even though I managed to pass all the test there is one thing I cannot understand.
In the exercise you are being asked to override the << method for an instance variable. Specifically here is what you have to do:
In Ruby on Rails, when a person goes to a URL on your site, your
application looks at the url, and maps it to a controller method to
handle the request
My boss wanted to be able to specify what CSS class the body of the
HTML output should have, based on which controller method was being
accessed. It fell to me to provide a method, which, when invoked,
would return a String that could handle the request There are a few
nuances, though. The String you return must be retained during the
object's entire life The method must be able to be called multiple
times The String you return should know how to add new classes: each
class is separated by a space The only method you need to worry about
being invoked is the << method.
(plus a few other irrelevant things)
EXAMPLE:
controller = ApplicationController.new
controller.body_class
#=> ""
controller.body_class << 'admin'
controller.body_class
#=> "admin"
controller.body_class << 'category'
controller.body_class
#=> "admin category"
controller.body_class << 'page' << 'order'
controller.body_class
#=> "admin category page order"
My working solution:
class ApplicationController
def initialize
#body_class = ""
end
def body_class
def #body_class.<<(str)
puts "self is:"+self
return self if self=~/\b#{Regexp.escape(str)}\b/
if self==""
self.concat(str)
else
self.concat(" ")
self.concat(str)
end
end
return #body_class
end
end
Everything works perfectly fine.
But an earlier solution I gave (and it didn't work) was the following
class ApplicationController
attr_accessor :body_class
def initialize
#body_class = ""
end
def #body_class.<<(str)
puts "self is:"+self
return self if self=~/\b#{Regexp.escape(str)}\b/
if self==""
self.concat(str)
else
self.concat(" ")
self.concat(str)
end
end
def body_class #Even this for the way I work it out on my mind is unnecessary
return #body_class
end
end
When someone runs on the second not-working sollution the following
obj = ApplicationController.new
obj.body_class << "Hi"
The << method is not being overridden by the object's singleton.
I do not understand why I have to wrap the singleton methods inside the body_class method. (Mind that in the second solution there is an attr_accessor.
Could anyone enlighten me please!
Thanks!
I do not understand why I have to wrap the singleton methods inside the body_class method.
To access the correct instance variable. When you attempt to override it outside of method, you're in the class context. This code runs at class load time. No instances have been created yet. (And even if instances did exist at this point, #body_class instance variable belongs to class ApplicationController, which is itself an instance of class Class).
You need instance context.
Also I am pretty sure that this problem can be solved without any method patching voodoo. Just provide a different object (conforming to the same API. This is called "duck typing").
class ApplicationController
def body_class
#body_class ||= CompositeClass.new
end
class CompositeClass
def initialize
#classes = []
end
def <<(new_class)
#classes << new_class
end
# I imagine, this is what is ultimately called on ApplicationController#body_class,
# when it's time to render the class in the html.
def to_s
#classes.join(' ')
end
end
end
Didn't test this code, naturally.
BTW, the proper way to do it is to explicitly extend the instance variable:
class A
attr_reader :body_class
def initialize
#body_class = "".extend(Module.new do
def <<(other)
return self if self[/\b#{Regexp.escape(other)}\b/]
concat(' ') unless empty?
concat(other)
end
end)
end
end
Related
class Restaurant
attr_accessor :average_rating, :city
def initialize(city, name)
#city = city
#name = name
#number_of_ratings = 0
#sum_of_ratings = 0
end
def rate(new_rate)
#number_of_ratings += 1
#sum_of_ratings += new_rate
#average_rating = #sum_of_ratings.to_f / #number_of_ratings
end
def self.filter_by_city(restaurants, city)
restaurants.select { |restaurant| restaurant.city == city }
end
end
The above code is part of a challenge and I kept failing the tests for the #filter_by_city method. I checked the solution and the only difference was the self. prior to the method name. I've tried to understand what self does exactly but it's difficult to understand without context. In this particular class method, what exactly is self doing? I know what the body of the method is doing i.e. the filtering of the restaurants by city, but how does it run exactly?
self is the class Restaurant. def self.method is how you implement a method on the class itself rather than an instance of the class. Restaurant.filter_by_city(...) rather than Restaurant.new.filter_by_city(...).
self changes in Ruby depending on context. Within a method, self is the object the method was called on.
Within the class Restaurant block, and outside of any method, self is the Restaurant object which is a Class object. Everything is an object in Ruby. Everything.
You can also do this by declaring a block where the class is the instance.
class << self
def filter_by_city(restaurants, city)
restaurants.select { |restaurant| restaurant.city == city }
end
end
Normally you'd use this syntax if you have a lot of class methods.
See Self in Ruby: A Comprehensive Overview for more.
When defining a method in ruby you can optionally explicitly define that method's receiver using def <receiver>.<method> syntax instead of plain def <method>
object = Object.new
def object.foo
:foo
end
object.foo #=> foo
The receiver must either be a singular reference OR an expression (but it must be enclosed by brackets):
a = [Object.new, Object.new]
def (a.first).foo
:foo
end
def (a[1]).bar
:bar
end
a[0].foo #=> :foo
a.last.bar #=> :bar
a.first.bar #=> undefined method
When receiver is defined, the method is defined directly on the receiver's singleton class, ignoring the context in which the method is defined:
class A
o = Obejct.new
def o.foo
end
end
A.new.foo #=> undefined method
Even though method foo was defined in class A body, it is not available to its instances because of the explicit receiver
self is a ruby keyword returning the current "context". Inside the methods, self is (usually) a receiver of the call, and inside the module self returns that module. So:
module Wrapper
module SomeModule
puts self.name
end
end
will print Wrapper::SomeModule.
This means that:
class A
def self.foo
end
end
Is exactly the same as:
class A
def A.foo
end
end
So, the method is defined directly on A and can only be called directly on the class as A.foo, rather than on its instances.
Is there a way to implement monkey patching while an object is being instantiated?
When I call:
a = Foo.new
Prior to the instance being instantiated, I would like to extend the Foo class based on information which I will read from a data store. As such, each time I call Foo.new, the extension(s) that will be added to that instance of the class would change dynamically.
tl;dr: Adding methods to an instance is possible.
Answer: Adding methods to an instance is not possible. Instances in Ruby don't have methods. But each instance can have a singleton class, where one can add methods, which will then be only available on the single instance that this singleton class is made for.
class Foo
end
foo = Foo.new
def foo.bark
puts "Woof"
end
foo.bark
class << foo
def chew
puts "Crunch"
end
end
foo.chew
foo.define_singleton_method(:mark) do
puts "Widdle"
end
foo.mark
are just some of the ways to define a singleton method for an object.
module Happy
def cheer
puts "Wag"
end
end
foo.extend(Happy)
foo.cheer
This takes another approach, it will insert the module between the singleton class and the real class in the inheritance chain. This way, too, the module is available to the instance, but not on the whole class.
Sure you can!
method_name_only_known_at_runtime = 'hello'
string_only_known_at_runtime = 'Hello World!'
test = Object.new
test.define_singleton_method(method_name_only_known_at_runtime) do
puts(string_only_known_at_runtime)
end
test.hello
#> Hello World!
Prior to the instance being instantiated, I would like to extend
Given a class Foo which does something within its initialize method:
class Foo
attr_accessor :name
def initialize(name)
self.name = name
end
end
And a module FooExtension which wants to alter that behavior:
module FooExtension
def name=(value)
#name = value.reverse.upcase
end
end
You could patch it via prepend:
module FooPatcher
def initialize(*)
extend(FooExtension) if $do_extend # replace with actual logic
super
end
end
Foo.prepend(FooPatcher)
Or you could extend even before calling initialize by providing your own new class method:
class Foo
def self.new(*args)
obj = allocate
obj.extend(FooExtension) if $do_extend # replace with actual logic
obj.send(:initialize, *args)
obj
end
end
Both variants produce the same result:
$do_extend = false
Foo.new('hello')
#=> #<Foo:0x00007ff66582b640 #name="hello">
$do_extend = true
Foo.new('hello')
#=> #<Foo:0x00007ff66582b280 #name="OLLEH">
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
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.
I would like to write a module that provides active record like functionality on an array instance variable.
Examples of its use would be
x = Container.new
x.include(ContainerModule)
x.elements << Element.new
x.elements.find id
module ContainerModule
def initialize(*args)
#elements = []
class << #elements
def <<(element)
#do something with the Container...
super(element)
end
def find(id)
#find an element using the Container's id
self
#=> #<Array..> but I need #<Container..>
end
end
super(*args)
end
end
The problem is that I need the Container object within these methods. Any reference to self will return the Array, not the Container object.
Is there any way to do this?
Thanks!
Would something like this work for you?
class Container
attr_accessor :elements
def initialize
#elements = ContainerElements.new
end
end
class ContainerElements < Array
def find_by_id(id)
self.find {|g| g.id == id }
end
end
So i create a container-class, and a ContainerElements that inherits from Array, with an added (specific) find_by_id method.
If you really want to call it find you need to alias it.
Example code would be:
class ElemWithId
attr_accessor :id
def initialize(value)
#id = value
end
end
cc = Container.new
cc.elements << ElemWithId.new(1)
cc.elements << ElemWithId.new(5)
puts "elements = #{cc.elements} "
puts "Finding: #{cc.elements.find_by_id(5)} "
Hope this helps ...
Your best approach may be to work with the Hash class, which has operations like finding by id already. Particularly, the fetch method may help you out.