I have an instance of singleton class SingletonClass within module Modulename
Modulename::SingletonClass.instance
which has a hash #hashname. I have a method on SingletonClass that adds new keys to #hashname.
When I add a new key to #hashname, I can see the new key exists by doing puts #hashname in the controller, but when I do it in SingletonClass, it seems that the new key is not added. Why is that? Why am I able to see the change in the #hashname from controller but not from the singleton class?
Here is a code that reproduces the behaviour I'm trying to describe :
module MyModule
module SubModule
class SingletonClass
include Singleton
def initialize
#items = {}
#items = MyMode.all.map{|c| {c.name => c.secondary_name}}.reduce(:merge)
end
def add_new_item(name, secondary_name)
#items[name] = secondary_name
end
def do_something
#items.each do |k,v|
ap "#{k} => #{v}"
end
end
def another_method
do_something
end
end
end
end
When I do this from my controller :
singleton = MyModule::SubModule::SingletonClass.instance
singleton.add_new_item('test', 'test1')
Then this also from controller :
singleton.do_something
The new item gets printed out so its good.
But when I invoke another_method from my within my singleton class, the new item appears not to be added
This code worked fine for me when MyMode.all returned values. But when it was an empty array, then #items = became nil.
that happens because:
[].reduce(:merge)
=> nil
Easiest way to fix is probably:
#items = {}
people = MyMode.all.map{|c| {c.name => c.secondary_name}}.reduce(:merge)
#items = people if people
Related
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 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
I have created a class which I have some constant hashes. I'd like to type Myclass.myhash.hashkey and to show the value of the hash. Right Now I have created a similar behavior with method_missing but I have to initialize the object, so I am calling it like Myclass.new.myhash.hashkey and it works. Here is my code so far:
class Myclass
def initialize
#attributes = []
end
def method_missing(name, *args)
#attributes << name
if #attributes.length == 2
eval("#{#attributes.first.upcase}[:#{#attributes.last.downcase}]")
else
self
end
end
MYHASH = {
id: 1,
description: "A nice hash",
hashkey: "hash key"
}
end
How can I do it without initialize and without new so it won't create an object of MyClass everytime?
Update:
The first question was explained by toro2k but I don't know if using it I can have the behavior of my second question...
Question 2
I have many openstructs in my class, how can I define them as a class methods dynamically without every time adding something like:
def self.myhash
MYHASH
end
You could use an OpenStruct object instead of the Hash:
class MyClass
MYHASH = OpenStruct.new(id: 1,
description: 'A nice Ostruct',
hashkey: 'hash key')
def self.myhash
MYHASH
end
end
MyClass.myhash.id # => 1
MyClass.myhash.description # => "A nice Ostruct"
MyClass.myhash.foo # => nil
Update You could replace constants with class instance variables like this:
class MyClass
def self.myhash
#myhash ||= OpenStruct(id: ...)
end
end
MyClass.myhash.id
Or you could use class variables and cattr_reader:
class MyClass
cattr_reader :myhash
##myhash = OpenStruct(id: ...)
end
MyClass.myhash.id
Or you could get rid of the myhash method and access the constant directly:
class MyClass
MYHASH = OpenStruct(id: ...)
end
MyClass::MYHASH.id
I have finally found a solution for my second question also:
class << self
Myclass.constants.each do |constant|
define_method(constant.to_s.downcase) do
eval("#{constant}")
end
end
end
I just have to add it at the end of the class to work, after I have defined all the openstruct variables.
How one can add syntactic sugar similar to rails "add to collection" << operator, i.e.
#object.collection << item
I was trying to do
class Object
def collection<<(item)
...
end
end
but it does not work. Optionally I would like to define my own "operators" on attributes.
Note - i am aware hot to use def <<(value) but it works for the whole object not for its attribute.
#object.collection << item
Let's take this apart.
#object - well, some object.
collection - when #object is sent
this message it returns something.
<< - this message is sent to the
object that was returned from the collection message.
item -
parameter to << message.
Example
class Foo
def << val
puts "someone pushed #{val} to me"
end
end
class Bar
def collection
#foo ||= Foo.new
end
end
b = Bar.new
b.collection << 'item'
# someone pushed item to me
By the way, these forms do the same thing and produce the same output.
b.collection << 'item'
b.send(:collection).send(:<<, 'item')
b.collection.<<('item')
b.collection.<< 'item'
This isn't possible based on how Ruby works. You will need your collection method to return an object which has your custom << method on it.
<< is a method of Array, so this works in plain Ruby:
def MyClass
def initialize
#collection = []
end
def collection
#collection
end
end
MyClass.new.collection << 'foo'
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.