I'm using Ruby's metaprogramming methods creating a bunch of methods within a class. Within the class OmekaItem there are a bunch of methods of this form dc_title and dc_subject, and there are a bunch of methods of this form itm_field1 and itm_field2. I'd like to group those methods better. Ideally, given an instance of the class named item, I'd like call the methods this way:
item.dublin_core.title
item.item_type_metadata.field
and so on. Is there a way to do this?
This question has the code I'm working with.
Would something like the following work for you?
class OmekaItem
class DublinCore
def initialize(omeka_item)
#omeka_item = omeka_item
end
def title
#omeka_item.dc_title
end
def subject
#omeka_item.dc_subject
end
end
class ItemTypeMetadata
def initialize(omeka_item)
#omeka_item = omeka_item
end
def field1
#omeka_item.itm_field1
end
def field2
#omeka_item.itm_field2
end
end
def dublin_core
#dublin_core ||= DublinCore.new(self)
end
def item_type_metadata
#item_type_metadata ||= ItemTypeMetadata.new(self)
end
end
The methods on DublinCore and ItemTypeMetadata could be dynamically generated using define_method as appropriate.
Related
I want a piece of code to run before any other static methods run, is it possible to do something in the spirit of the following?
class MyClass
def self.initialize
#stuff = 1
end
def self.print_stuff
puts #stuff
end
end
My Ruby version of interest is 2.3.
Every chunk of code in Ruby is an expression. Even a class definition is a series of expressions: method definitions are expressions that have the side-effect of adding the method to the class.
This is how meta-programming methods work. attr_reader is a private method call where the implicit self is the class. So, long story short, you aren't restricted inside a class body, you can put whatever code you want to run in the context of the class:
class MyClass
#stuff = 1
def self.print_stuff
puts #stuff
end
end
There's no such thing as an explicit metaclass initializer. The class itself is "initialized" as it's defined, so it's perfectly valid to do this:
class MyClass
# Code here will be executed as the class itself is defined.
#stuff = 1
def self.print_stuff
puts #stuff
end
end
MyClass.print_stuff
Remember that def itself is a form of method call and defining a class in Ruby involves sending a bunch of messages (method calls) around to the proper context objects, such as the class itself as it's being defined.
I'm trying to create a method to dynamically do the following: (as I will have to implement this on about 30 different sets of sub-classes)
def t1
FooT1.new
end
def t2
FooT2.new
end
def t3
FooT3.new
end
Where there will be 2 variables in the method generation, the tab number(t1...tx) and the name of the class (Foo)
I tried the following, but I'm new to Ruby and can not get this working.
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
define_method("t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
Then call it in the sub-class like so:
method_generator(3, "Bar")
I'm aware I'm probably quite far off in implementing this, so any help is appreciated.
Just do as below :
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
class_name.send(:define_method,"t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
Module#define_method is a private method, thus you can't call it on the class_name like class_name.define_method(:name) do ..end, as private method call not allows explicit receiver. But to do so Object#send will help you, as this method is here for this kind of scenarios, where you can't call private method by explicit receiver.
Lets verify with an example, if this tricks works or not.
class Foo;end
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
class_name.send(:define_method,"t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
method_generator(3,Foo)
Foo.instance_methods(false)
# => [:t1, :t2, :t3] # see here 3 instance methods has been created of class Foo
I have a class, with some fake relationships I want to implement:
module FormStack
class Connection
def forms; end
def fields; end
end
end
I have metaprogramically generated classes for both forms, and fields (as they are RESTful resources, they share the same action names and params), and I want to include those methods in my fake relationships in my FormStack::Connection class. can this be done?
I essentially want <FromStack::Connection Instance>.forms to behave as if it were FormStack::Form, so I can do things like <connection>.forms.all or <connection>.forms.find(id).
Is this possible?
Any best practices I should maybe be looking at? (This seems a little strange to me, but I think it's an elegant way to have the methods implemented in a useful way, while still having an ActiveRecord-esque abstraction of the restful resources / objects).
Here is the code I'm working with, if you want to look: https://github.com/TinderBox/formstack/tree/connection_instances
Why not just use simple composition? Pass whatever object has the has_many FormStack::Form relation in when you initialize a new FormStack::Connection instance. Then you can directly invoke the #forms method on the FormStack::Form collection instance, or you can use delegation.
FormStack::Connection.new(FormStack::FormCollection.new(params[:form]) #sample class name -- obviously use whatever has the real has_many :forms
module FormStack
class Connection
def initialize(form_collection)
#form_collection = form_collection
end
def forms
#form_collection.forms
end
def fields
#form_collection.fields
end
end
end
Or
module FormStack
class Connection
extend Forwardable
def_delegators :#form_collection, :forms, :fields
def initialize(form_collection)
#form_collection = form_collection
end
end
end
Unless there is a better way, this is how I've solved my problem for now:
def method_missing(meth, *args, &block)
method_name = meth.to_s
if "forms" == method_name
FormStack::Form.connection = self
FormStack::Form
elsif ...
else
super
end
end
https://github.com/TinderBox/formstack/blob/082793bed97e97cc65c703c8ca3cb382cbdf743a/lib/formstack/connection.rb
I have to add methods to Class in execution time.
class ExtendableClass
end
The methods to add are declared in independent Classes.
module ExtensionClassOne
def method_one
end
end
module ExtensionClassTwo
def method_two
end
end
I'm looking for an (elegant) mechanism to add all the extension class methods into the ExtendableClass.
Approach 1
I'm thinking in explicily include the extension classes like:
ExtendableClass.send( :include, ExtensionClassOne )
ExtendableClass.send( :include, ExtensionClassTwo )
but it looks a little forced to have to call this private method every time I define a new extension class.
Approach 2
So I was looking for an automatic way to include this methods into my ExtendableClass class.
I'm thinking in declare an specific ancestor for this extension classes:
class ExtensionClassOne < Extension
def method_one
end
end
and then I'd need a mechanism to know all the childs of a class... something like the oposite of ancestors.
Once I have this list I can easily ExtendableClass.include all the list of classes. Even if I have to call to the private method here.
Approach 3
Also inheriting from the Extension class and detect in declaration time when this class is used as ancestor. In the way that the ActiveSupport.included method works, like an event binding. Then make the include there.
Any solution for implement approach 2 or approach 3? Do you recommend approach 1? New approachs?
#fguillen, you are right that the "explicit way is the cleanest approach". Since that is so, why don't you use the most "explicit" code which could be imagined:
class Extendable
end
class Extendable
def method_one
puts "method one"
end
end
class Extendable
def method_two
puts "method two"
end
end
...In other words, if you are defining a module which will be automatically included in a class as soon as it is defined, why bother with the module at all? Just add your "extension" methods directly to the class!
Approach 4 would be to define a macro on class level in Object
class Object
def self.enable_extension
include InstanceExtension
extend ClassExtension
end
end
and calling this macro in all your classes you want to be extended.
class Bacon
enable_extension
end
Car.enable_extension
This way,
you don't have to use #send to circumvent encapsulation (Approach 1)
you can inherit from any Class you want, because everything inherits from Object anyway (except 1.9's BasicObject)
the usage of your extension is declarative and not hidden in some hook
Downside: you monkeypatch build-in Classes and may break the world. Choose long and decriptive names.
Edit: Given your answer to my comment on the question I suppose this is not what you wanted. I see no problem with your "Approach 1" in this case; it's what I'd do. Alternatively, instead of using send to bypass the private method, just re-open the class:
class ExtendableClass
include ExtensionOne
end
Assuming I understand what you want, I'd do this:
module DelayedExtension
def later_include( *modules )
(#later_include||=[]).concat( modules )
end
def later_extend( *modules )
(#later_extend||=[]).concat( modules )
end
def realize_extensions # better name needed
include *#later_include unless !#later_include || #later_include.empty?
extend *#later_extend unless !#later_extend || #later_extend.empty?
end
end
module ExtensionOne
end
module ExtensionTwo
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def class_can_do_it!; end
end
end
class ExtendableClass
extend DelayedExtension
later_include ExtensionOne, ExtensionTwo
end
original_methods = ExtendableClass.methods
p ExtendableClass.ancestors
#=> [ExtendableClass, Object, Kernel, BasicObject]
ExtendableClass.realize_extensions
p ExtendableClass.ancestors
#=> [ExtendableClass, ExtensionOne, ExtensionTwo, Object, Kernel, BasicObject]
p ExtendableClass.methods - original_methods
#=> [:class_can_do_it!]
The included method is actually a hook. It is called whenever you are inherited from:
module Extensions
def someFunctionality()
puts "Doing work..."
end
end
class Foo
def self.inherited(klass)
klass.send(:include, Extensions) #Replace self with a different module if you want
end
end
class Bar < Foo
end
Bar.new.someFunctionality #=> "Doing work..."
There is also the included hook, which is called when you are included:
module Baz
def self.included(klass)
puts "Baz was included into #{klass}"
end
end
class Bork
include Baz
end
Output:
Baz was included into Bork
A very tricky solution, I think too much over-engineering, would be to take the inherited hook that #Linux_iOS.rb.cpp.c.lisp.m.sh has commented and keep all and every child class in a Set and combined it with the #Mikey Hogarth proposition of method_missing to look for all this child class methods every time I call a method in the Extendable class. Something like this:
# code simplified and no tested
# extendable.rb
class Extendable
##delegators = []
def self.inherited( klass )
##delegators << klass
end
def self.method_missing
# ... searching in all ##delegators methods
end
end
# extensions/extension_one.rb
class ExtensionOne < Extendable
def method_one
end
end
But the logic of the method_missing (and respond_to?) is gonna be very complicate and dirty.
I don't like this solution, just let it here to study it like a possibility.
After a very interesting propositions you have done I have realized that the explicit way is the cleanest approach. If we add a few recommendations taking from your answers I think I'm gonna go for this:
# extendable.rb
class Extendable
def self.plug( _module )
include( _module )
end
end
# extensions/extension_one.rb
module ExtensionOne
def method_one
puts "method one"
end
end
Extendable.plug( ExtensionOne )
# extensions/extension_two.rb
module ExtensionTwo
def method_two
puts "method two"
end
end
Extendable.plug( ExtensionTwo )
# result
Extendable.new.method_one # => "method one"
Extendable.new.method_two # => "method two"
There has got to be a more efficient way to do this in Ruby. I have a list of methods that scrape the same things (title, price) across multiple sites but in slightly different ways based on the code in each store. For example:
def store1_get_title
def store1_get_price
def store2_get_title
def store2_get_price
def store3_get_title
def store3_get_price
When calling all of these functions, I would just like a generic call with say a "namespace" parameter to do invoke any of these methods without having to type out all of them, something like:
for get_all_stores().each do |store|
store::get_title
store::get_price
end
...which would invoke store1_get_title, store1_get_price, store2_get_title, store2_get_price like I want. Is there something like this or a better way to do this?
Hope that makes sense. Thanks for any input!
Edit: these tasks are in rake task code.
This is a perfect use for classes. If you find two stores with the same software powering them (maybe Yahoo commerce or EBay stores) you can make instances of the classes with different parameters.
class Amazon
def get_price; end
def get_title; end
end
class Ebay
def initialize seller; end
def get_price; end
def get_title; end
end
[Amazon.new, Ebay.new("seller1"), Ebay.new("seller2")] each do |store|
store.get_price
store.get_title
end
And you can do this in any other object-oriented language by defining a base class or interface that all of the stores implement/inherit.
I don't understand the logic of your application. Perhaps you should think about a class definition (see Ken Blooms answer).
Nevertheless you could try a dynamic call with send:
def store1_get_title
p __method__
end
def store1_get_price
p __method__
end
def store2_get_title
p __method__
end
def store2_get_price
p __method__
end
def store3_get_title
p __method__
end
def store3_get_price
p __method__
end
all_stores = ['store1', 'store2', 'store3']
all_stores.each do |store|
send("#{store}_get_title")
send("#{store}_get_price")
end
You didn't define what get_all_stores returns. In my example I used Strings. You could add some syntactical sugar and extend String (I don't recommend this)
class String
def get_title()
send("#{self}_get_title")
end
def get_price()
send("#{self}_get_price")
end
end
all_stores.each do |store|
store.get_title
store.get_price
end
One last remark. You wrote
for get_all_stores().each do |store|
each alone should be enough. for is not ruby-like and in combination with each it doen't look reasonable to me.