how to add syntactic sugar to rails similar to collections - ruby-on-rails

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'

Related

Ruby: Singleton method of an instance variable inside a class

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

Singleton in rails immutable

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

Define class methods dynamically in Rails

I define class methods dynamically in Rails as follows:
class << self
%w[school1 school2].each do |school|
define_method("self.find_by_#{school}_id") do |id|
MyClass.find_by(school: school, id: id)
end
end
end
How can I use method missing to call find_by_SOME_SCHOOL_id without having to predefine these schools in %w[school1 school2]?
It is not completely clear to me what you want to achieve. If you want to call a method, you naturally have to define it first (as long as ActiveRecord does not handle this). You could do something like this:
class MyClass
class << self
def method_missing(m, *args, &block)
match = m.to_s.match(/find_by_school([0-9]+)_id/)
if match
match.captures.first
else
nil
end
end
end
end
puts MyClass.find_by_school1_id
puts MyClass.find_by_school2_id
puts MyClass.find_by_school22_id
puts MyClass.find_by_school_id
This will output:
1
2
22
nil
You could then do something with the ID contained in the method name. If you are sure that the method is defined, you can also use send(m, args) to call that method on an object/class. Beware though, if you do that on the same class that receives the method missing call and the method is not defined, you will get a stack overflow.
I recommend return the super unless you have a match
class MyClass
class << self
def method_missing(m, *args, &block)
match = m.to_s.match(/find_by_school([0-9]+)_id/)
match ? match.captures.first : super
end
end
end

Active Record like functionality on array instance variable

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.

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