Using module_eval, my code allows me to dynamically create and add new methods to a class based on input parameters. See this post for an example
Ruby class_eval and yield
Now my question is how do I reset the class back to its original methods? Is there a simple method I can use to reset a class back to it's original state? The reason for this is that once new methods are added to the class, they persists and I need to be get rid of them if I create a new object with a different set of methods.
Also I must apologize if this question doesn't quite make sense, I've been up for 24 hours and probably needed to get some rest to think clearly. If this is not clear, I can provide an example. Thanks for your help in advance.
Edit: Added complete solution
If you keep a list of the methods added in your earlier post, you can use remove_method to remove these methods with something like:
class MyTest
##methods_list = []
def self.show_methods
##methods_list
end
def self.reset_methods
##methods_list.each do |method|
remove_method(method)
end
##methods_list = []
end
def self.add_methods
define_method("method1") { puts "This is method1" }
define_method("method2") { puts "This is method2" }
true
end
def self.method_added(method_name)
##methods_list << method_name.to_s
puts "Added: " + method_name.to_s + ", list: " + ##methods_list.inspect
end
end
Now you can try out the following:
>> require 'mytest.rb'
>> t = MyTest.new # => #<MyTest:0x2b1e293247f0>
>> MyTest.add_methods
Added: method1, list: ["method1"]
Added: method2, list: ["method1", "method2"]
>> t.method1 # Method is available:
This is method1
>> MyTest.reset_methods
>> t.method1 # Method is undefined now, so we'd expect an error
NoMethodError: undefined method `method1' for #<MyTest:0x2b1e293247f0>
from (irb):6
I would take one of two tacks:
either move the original class methods to a module, which you can re-include later
or use module_eval on a subclass of the original class, and just get a new subclass when you want to reset.
The second's a little easier to do:
subklass = Class.new(MyAwesomeClass)
subklass.module_eval #...
# ok, I'm done, I want to reset
subklass = Class.new(MyAwesomeClass)
I don't know of such a facility, and I would probably think that your code could be better structured if you need one, but there are a few things you can try:
Override the new methods with variants that will throw an exception, or do nothing. If you know the names of the new methods, it'd be pretty easy to do.
Instead of adding methods to one class, create an anonymous subclass that inherits from the base class, to which you add your methods. (See the answer to this question for a means of doing this.) Then, your base class is still unmodified, so there'd be no need to reset it.
HTH
Related
I am working on a project that requires very specific methods to be called on an ActiveRecord::Relation object. These methods cannot extend ActiveRecord::Relation because the Class has it's own initialize method to determine if the object should be collected. I have tried a dozen ways to handle this but because of method chaining in AR I have been unable to accomplish this. Currently I have monkey patched ActiveRecord::Relation with a method that converts it like so:
module ActiveRecord
class Relation
def to_claim_set
exec_queries unless loaded?
ClaimSet.new(#records)
end
end
end
Firstly I am sure this is an improper way to handle it. Secondly this causes me to have to call #to_claim_set constantly throughout the application.
I am hoping someone can assist on making this the default return after all method chaining is complete.
What I am hoping for is something like
Claim.policy_number('913006')
#=> ClaimSetObjectHere
But I need it to support chaining like AR does so that things like
Claim.policy_number('913006').by_program('Base')
#=> ClaimSetObjectHere
I also tried to patch the #where method inside Claim which works great unless I use a scope or I chain methods in which case it complains that ClaimSet does not define default_scoped?.
Any insight would be greatly appreciated. As for "Why would you want to do this" like I said I am constantly calling this method throughout the application and I need the methods defined in ClaimSet for this to function properly.
Note: This is being used outside of rails
Okay so what I ended up doing was imposing a wrapper for ActiveRecord::Relation like so:(removed specific business logic for brevity)
class ClaimSet
def initialize(object)
process_target(object)
# ...
end
# ...
def respond_to_missing?(method_name,include_private=false)
#target.respond_to?(method_name)
end
def method_missing(method_name, *args, &block)
if #target.respond_to?(method_name)
ClaimSet.new(#target.send(method_name,*args,&block))
else
super
end
end
private
def process_target(object)
#target = object if object.is_a?(ActiveRecord::Relation)
#target = object.target if object.is_a?(ClaimSet)
end
end
Then in the Claim class.
class Claim < ActiveRecord::Base
class << self
def where(*args)
ClaimSet.new(super(*args))
end
def localized_scope(name,proc)
scope_proc = lambda do |*args|
ClaimSet.new(proc.call(*args))
end
singleton_class.send(:define_method,name,scope_proc)
end
end
end
Then I define all my scopes as localized_scope e.g.
localized_scope :policy_number, ->(policy_number){where(policy_number: policy_number)}
Now it always returns a ClaimSet in place of an ActiveRecord::Relation for #where and #localized_scope and supports method chaining through #method_missing. It also removes the monkey patch on ActiveRecord::Relation.
If you have any other suggestions please let me know as I would be glad to entertain other ideas but this works for the time being.
I'm trying to fill in class variables for a class instance from a file, and the only way I've managed to figure how to do this is something like
a=Thing.new
File.read("filename.ext").each_line do |arguments| #this will => things like #variable=str\n
eval( "a.instance_eval {" + arguments.chop + "}") #hence the awkward eval eval chop
end
The only problem I've found is that in trying to impliment this in a class method (to do this for several instances in a go), I don't know how to make this happen:
class Thing
attr_accessor :variable
def self.method
File.read("filename.ext").each_line do |arguments|
eval("instance.instance_eval{" + arguments.chop + "}") #this line
end
end
end
namely, the reference to the instance calling the method. self will just just be Thing in this case, so is there any way to do this? More pertinent might be a better way to go about this overall. I only just learned ruby last night, so I haven't had an opportunity to see some of the neater tricks that are to be had, and my language maturity is a little fresh yet as a result.
For context, Thing is a character in a game, loading its base values from a savefile.
Well, first off, take a look at Marshal. It's specifically used for dumping data structures to serialized formats and loading them back.
The said, if you want to persist in your direction, then try something like this:
class Thing
attr_accessor :variable
def self.method
File.read("filename.ext").each_line do |arguments|
ivar, val = arguments.strip.split("=", 2)
instance.instance_variable_set(ivar, val)
end
end
end
#instance_variable_set allows you to...well, set instance variables on an object by name. No ugly eval necessary!
By way of demonstration:
class Foo
attr_accessor :bar
end
foo = Foo.new
foo.instance_variable_set("#bar", "whatzits")
puts foo.bar # => whatzits
I want to keep track of all the methods which have been run on an instance of a class I've built.
Currently I do:
class MyClass
def initialize
#completed = []
end
# Sends a welcome and information pack to the person who requested it
def one_of_many_methods
unless #completed.include? __method__
# Do methody things
#completed.push __method__
end
end
alias :another_name :one_of_many_methods
# Calling myClassInstance.another_name will insert
# :one_of_many_methods into #completed.
# Methody things should not now be done if .another_name
# or .one_of_many_methods is called.
end
But that gets very laborious when I have many methods in my class. I'm repeating myself! Is there a way to track the methods being called and to only allow them to be called once, like I'm doing above, but without having to repeat that block in every method?
Thanks!
(PS. I'm using Ruby 1.9)
This sounds like the perfect use case for a Proxy object. Fortunately, Ruby's dynamic nature makes implementing it quite easy:
class ExecuteOnceProxy
def initialize(obj)
#obj = obj
#completed = []
end
def method_missing(method, *args)
unless #completed.include?(method)
args.empty? ? #obj.send(method) : #obj.send(method, args)
#completed << method
end
end
end
Initialize your proxy simply by passing the original object in the constructor:
proxy = ExecuteOnceProxy.new(my_obj)
method_missing
There are frameworks that will do things like this but to answer your question, yes, there is an easy way.
And the easy way to only writing the code once is to front-end the entire class and implement method_missing. You can then uncover the real methods one at a time as each is discovered to be "missing" with an initial call.
I think there is new solution for your question.
Some times ago, Tobias Pfeiffer released after_do gem
This is not an answer because I do not have enough reputation to comment but please note that the answer #emboss posted has an error in it (missing star).
args.empty? ? #obj.send(method) : #obj.send(method, args)
should be
args.empty? ? #obj.send(method) : #obj.send(method, *args)
otherwise the method will receive a single arg: an array of the args you tried to pass it.
I'm trying to learn ruby by building a basic Campfire bot to screw around with at work. I've gotten pretty far (it works!) and learned a lot (it works!), but now I'm trying to make it a bit more complex by separating the actions to be performed out into their own classes, so that they can be easier to write / fix when broken. If you're interested in seeing all the (probably crappy) code, it's all up on GitHub. But for the sake of this question, I'll narrow the scope a bit.
Ideally, I would like to be able to create plugins easily, name them the same as the class name, and drop them into an "actions" directory in the root of the project, where they will be instantiated at runtime. I want the plugins themselves to be as simple as possible to write, so I want them all to inherit some basic methods and properties from an action class.
Here is action.rb as it currently exists:
module CampfireBot
class Action
#handlers = {}
def initialize(room)
#room = room
end
class << self
attr_reader :handlers
attr_reader :room
def hear(pattern, &action)
Action.handlers[pattern] = action
end
end
end
end
Where #room is the room object, and #handlers is a hash of patterns and blocks. I kind of don't understand why I have to do that class << self call, but that's the only way I could get the child plugin classes to see that hear method.
I then attempt to create a simple plugin like so (named Foo.rb):
class Foo < CampfireBot::Action
hear /foo/i do
#room.speak "bar"
end
end
I then have my plugins instantiated inside bot.rb like so:
def load_handlers(room)
actions = Dir.entries("#{BOT_ROOT}/actions").delete_if {|action| /^\./.match(action)}
action_classes = []
# load the source
actions.each do |action|
load "#{BOT_ROOT}/actions/#{action}"
action_classes.push(action.chomp(".rb"))
end
# and instantiate
action_classes.each do |action_class|
Kernel.const_get(action_class).new(room)
end
#handlers = Action.handlers
end
The blocks are then called inside room.rb when the pattern is matched by the following:
handlers.each do |pattern, action|
if pattern.match(msg)
action.call($~)
end
end
If I do puts #room inside the initialization of Action, I see the room object printed out in the console. And if I do puts "foo" inside Foo.rb's hear method, I see foo printed out on the console (so, the pattern match is working). But, I can't read that #room object from the parent class (it comes out as a nil object). So obviously I'm missing something about how this is supposed to be working.
Furthermore, if I do something to make the plugin a bit cleaner (for larger functions) and rewrite it like so:
class Foo < CampfireBot::Action
hear /foo/i do
say_bar
end
def say_bar
#room.speak "bar"
end
end
I get NoMethodError: undefined method 'say_bar' for Foo:Class.
The definition of hear can be pulled out of the class << self block and changed to:
def self.hear(pattern, &action)
Action.handlers[pattern] = action
end
to yield the exact same result. That also immediately explains the problem. hear Is a class method. say_bar is an instance method. You can't call an instance method from a class method, because there simply isn't an instance of the class available.
To understand the class << self bit, you'll have to do your own reading and experiments: I won't try to improve on what has already been said. I'll only say that within the class << self .. end block, self refers to the eigenclass or metaclass of the CampfireBot::Action class. This is the instance of the Class class that holds the definition of the CampfireBot::Action class.
Here is my situation:
XMLRPC::Client has a proxy constructor, new3, that takes a hash of options. It takes out the individual values to then delegate the construction to the default initializer, initialize
I am deriving from XMLRPC::Client. I want a class that is XMLRPC::Client but with some added functionality.
I want to be able to instantiate this derived class using a hash of options as well. This means that in my derived class' initializer, I have to somehow instantiate super using the new3 proxy constructor.
My Question Is if this is possible. If not, then is the only way to solve this is to practically 'copy and paste' the code within the XMLRPC::Client.new3 method into my derived class' constructor?
The reason I'm asking this is simply to see if there is a way of solving this problem, since there is this recurring theme of DRY (Don't Repeat Yourself) within the Ruby community. But of course, if this is the only way, it wont kill me.
I am only posting an answer supplement the other answers by showing you how XMLRPC's code is written
def new3(hash={})
# convert all keys into lowercase strings
h = {}
hash.each { |k,v| h[k.to_s.downcase] = v }
self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'],
h['use_ssl'], h['timeout'])
end
http://www.ensta.fr/~diam/ruby/online/ruby-doc-stdlib/libdoc/xmlrpc/rdoc/classes/XMLRPC/Client.html
Make a new class method in your derived class (much like they did to make new3 in the first place):
class MyDerived < XMLRPC::Client
def self.new3(hashoptions)
client = XMLRPC::Client.new3(hashoptions)
# Do further initialisation here.
end
end
myone = MyDerived.new3(:abc => 123, ...)
super only works in initialize (and only changes the parameters to the superclass's initialize), so it doesn't apply here.
You should probably be able to call new3 on you subclass
class MyClient < XMLRPC::Client
end
MyClient.new3({})
Or overwrite it if you need to do extra stuff:
class MyClient < XMLRPC::Client
def self.new3(args)
client = super(args)
# do some more stuff
client
end
end
MyClient.new3({})