I want to use an instance variable throughout a class's methods. Unfortunately, the methods are all class (not instance) methods, so the classic #my_data ||= [1,2,3] doesn't work, since every call to the class's class-methods will reuse the same data forever:
class MyThing
class << self
def do_something
do_something_else
end
private
def my_data
#my_data ||= Time.now
end
def do_something_else
puts my_data
end
end
end
MyThing.do_something # puts the current time
MyThing.do_something # puts the same time as above (not what I want)
One way around it is to declare the variable, then set it to nil at the end of the method like this:
class MyThing
class << self
def do_something
#my_data = my_data
do_something_else
#my_data = nil
end
private
def my_data
Time.now
end
def do_something_else
puts #my_data || my_data
end
end
end
MyThing.do_something # puts the current time
MyThing.do_something # puts the new current time (yay)
However, setting the data and destroying it at the start and end of the method seems messy.
Is there a cleaner way to say "set (#my_data = Time.now) until the end of this method"?
It's soluiton, if I understand you correctly.
class MyThing
class << self
def do_something
do_something_else
end
private
def my_data
#my_data = Time.now
end
def do_something_else
puts my_data
end
end
end
The name says it all "instance variable" is available as long as the instance lives. In your case that instance is the MyThing class, meaning it doesn't reset automatically. Classes stay loaded as long as your program runs, so it will never reset.
The simplest solution for the given scenario is to use method arguments.
class MyThing
class << self
def do_something
do_something_else(my_data)
end
private
def do_something_else(my_data)
puts my_data || Time.now
end
end
end
The other option you already discovered, that is, setting an instance variable and then resetting it.
Another alternative is moving the thing your doing into an class instance. The disadvantage here is that this solution produces more overhead code. However it does keep the MyThing class cleaner, since you can move helper methods into this new class.
class MyThing
class DataObject
attr_reader :data
def initialize(data)
#data = data || Time.now
end
def do_something_else
puts data
end
end
class << self
def do_something
DataObject.new(my_data).do_something_else
end
end
end
If possible you could also create a MyThing instance instead of an DataObject instance, but I assumed the MyThing class already has another purpose.
Related
Lets say I have a class with hundreds of instance methods in it. Now I have the requirement to run each method only if a certain thing is detected. Also I want to run my detection algorithm once for whole class instance no matter how many methods got called. If not detected first time no methods get called. I cannot afford if else around that many methods so I have to get a workaround. I have the following said class:
class CrawlerModule
extend Callbacks
before_run [:method_names, :of, :my, :class], :check_if_detected
#detected = nil
def check_if_detected
if #detected.nil?
detect
end
#detected
end
#hundreds of methods
private
def detect
detected_now = #my_detection_algorithm
#detected = detected_now
end
end
What I have done so far is to include following Callbacks module to call my check_if_detected method before every method but it doesn't work because method_added called at the very start of program and my detect function need some things to get initialized before detection. So the result array is always nil. Here's that complete module:
module Callbacks
def self.extended(base)
base.send(:include, InstanceMethods)
end
def overridden_methods
#overridden_methods ||= []
end
def callbacks
#callbacks ||= Hash.new { |hash, key| hash[key] = [] }
end
def method_added(method_name)
return if should_override?(method_name)
overridden_methods << method_name
original_method_name = "original_#{method_name}"
alias_method(original_method_name, method_name)
define_method(method_name) do |*args|
result = run_callbacks_for(method_name)
if result[0] || (self.class.callbacks.values.flatten.include? method_name)
send(original_method_name, *args)
end
end
end
def should_override?(method_name)
overridden_methods.include?(method_name) || method_name =~ /original_/
end
def before_run(method_names, callback)
method_names.each do |method_name|
callbacks[method_name] << callback unless method_name.eql? callback
end
end
module InstanceMethods
def run_callbacks_for(method_name)
result = []
self.class.callbacks[method_name].to_a.each do |callback|
result << send(callback)
end
result
end
end
end
This solution came to me while trying to get to sleep, so pardon the brevity and untested code.
Forget all of the callback stuff. Instead...
You could rename every method to include a prefix like prefix_method_name (or suffix if you prefer).
Then implement a method_missing method which implements your check, and then calls the appropriate method afterward.
Something like this:
def method_missing(method_name, *args, &block)
if detected_now
send("prefix_#{method_name}")
end
end
And then to run the detection once for the whole class instance do it in the constructor:
def initialize
detected_now
super
end
Cache the detected_now results if you wish in an instance variable as normal and work with it that way if that is something you want to do.
module A
module B
class Foo
def initialize(args)
#args = args
end
def call
puts 'Inside Foo'
end
end
end
end
module C
class Boo
def initialize(args)
#args = args
end
def call
puts 'Inside Boo'
A::B::Foo.new(#args).call
end
end
end
boo = C::Boo.new(nil).call
# Inside Boo
# Inside Foo
A::B::Foo.new(nil).call
# Inside Foo
How can I avoid A::B::Foo.new(nil).call ?
It should be accessible only from Boo class.
If anybody wants to access Foo class they will be able to access it from Boo.
How can I achieve this ?
Searched Internet but could not find what should be call this concept ?
This is ruby - so there's no such thing as an iron-clad way of making an object "private". (For example, you can access private methods via .send!) But you can at least define a private interface.
However, your interface doesn't actually make much sense from an OOP perspective. Why is A::B::Foo accessible only within C::Boo? If A::B::Foo is private, then it should only be accessible within A::B, and nowhere else.
The key method you're looking for is: private_constant
And you can circumvent a private constant lookup exception by using const_get.
Therefore we can "hack" your current implementation to work as follows:
module A
module B
class Foo
def initialize(args)
#args = args
end
def call
puts 'Inside Foo'
end
end
private_constant :Foo
end
end
module C
class Boo
def initialize(args)
#args = args
end
def call
puts 'Inside Boo'
A::B.const_get(:Foo).new(#args).call
end
end
end
# This works:
C::Boo.new(nil).call
# This errors:
A::B::Foo.new(nil).call
I need a class variable which does not get inherited, so I decided to use a class instance variable. Currently I have this code:
class A
def self.symbols
history_symbols
end
private
def self.history_tables
##history_tables ||= ActiveRecord::Base.connection.tables.select do |x|
x.starts_with?(SOME_PREFIX)
end
end
def self.history_symbols
Rails.cache.fetch('history_symbols', expires_in: 10.minutes) do
history_tables.map { |x| x.sub(SOME_PREFIX, '') }
end
end
end
Can I safely transform ##history_tables to #history_tables without braking anything? Currently all my tests pass, but I am still not sure if this could be done just like that.
Since you wish to use the instance variable, you shall use instance of the class, instead of the singleton methods:
class A
def symbols
history_symbols
end
private
def history_tables
#history_tables ||= ActiveRecord::Base.connection.tables.select do |x|
x.starts_with?(SOME_PREFIX)
end
end
def history_symbols
Rails.cache.fetch('history_symbols', expires_in: 10.minutes) do
history_tables.map { |x| x.sub(SOME_PREFIX, '') }
end
end
end
A.new.symbols
instead of:
A.symbols
I'm creating a module that extends the functionality of an ActiveRecord model.
Here's my initial setup.
My class:
class MyClass < ActiveRecord::Base
is_my_modiable
end
And Module:
module MyMod
def self.is_my_modiable
class_eval do
def new_method
self.mod = true
self.save!
end
end
end
end
ActiveRecord::Base(extend,MyMod)
What I would like to do now is extend the functionality of the new_method by passing in a block. Something like this:
class MyClass < ActiveRecord::Base
is_my_modiable do
self.something_special
end
end
module MyMod
def self.is_my_modiable
class_eval do
def new_method
yield if block_given?
self.mod = true
self.save!
end
end
end
end
This doesn't work though, and it makes sense. In the class_eval, the new_method isn't being executed, just defined, and thus the yield statement wouldn't get executed until the method actually gets called.
I've tried to assign the block to a class variable within the class_eval, and then call that class variable within the method, but the block was being called on all is_my_modiable models, even if they didn't pass a block into the method.
I might just override the method to get the same effect, but I'm hoping there is a more elegant way.
If I understood you correctly, you can solve this by saving passed block to an instance variable on class object and then evaling that in instance methods.
bl.call won't do here, because it will execute in the original context (that of a class) and you need to execute it in scope of this current instance.
module MyMod
def is_my_modiable(&block)
class_eval do
#stored_block = block # back up block
def new_method
bl = self.class.instance_variable_get(:#stored_block) # get from class and execute
instance_eval(&bl) if bl
self.mod = true
self.save!
end
end
end
end
class MyClass
extend MyMod
is_my_modiable do
puts "in my modiable block"
self.something_special
end
def something_special
puts "in something special"
end
attr_accessor :mod
def save!; end
end
MyClass.new.new_method
# >> in my modiable block
# >> in something special
You can do this by assigning the block as a method parameter:
module MyMod
def self.is_my_modiable
class_eval do
def new_method(&block)
block.call if block
self.mod = true
self.save!
end
end
end
end
Is it possible to add a callback to a single ActiveRecord instance? As a further constraint this is to go on a library so I don't have control over the class (except to monkey-patch it).
This is more or less what I want to do:
def do_something_creazy
message = Message.new
message.on_save_call :do_even_more_crazy_stuff
end
def do_even_more_crazy_stuff(message)
puts "Message #{message} has been saved! Hallelujah!"
end
You could do something like that by adding a callback to the object right after creating it and like you said, monkey-patching the default AR before_save method:
def do_something_ballsy
msg = Message.new
def msg.before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
For something like this you can always define your own crazy handlers:
class Something < ActiveRecord::Base
before_save :run_before_save_callbacks
def before_save(&block)
#before_save_callbacks ||= [ ]
#before_save_callbacks << block
end
protected
def run_before_save_callbacks
return unless #before_save_callbacks
#before_save_callbacks.each do |callback|
callback.call
end
end
end
This could be made more generic, or an ActiveRecord::Base extension, whatever suits your problem scope. Using it should be easy:
something = Something.new
something.before_save do
Rails.logger.warn("I'm saving!")
end
I wanted to use this approach in my own project to be able to inject additional actions into the 'save' action of a model from my controller layer. I took Tadman's answer a stage further and created a module that can be injected into active model classes:
module InstanceCallbacks
extend ActiveSupport::Concern
CALLBACKS = [:before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_commit]
included do
CALLBACKS.each do |callback|
class_eval <<-RUBY, __FILE__, __LINE__
#{callback} :run_#{callback}_instance_callbacks
def run_#{callback}_instance_callbacks
return unless #instance_#{callback}_callbacks
#instance_#{callback}_callbacks.each do |callback|
callback.call
end
end
def #{callback}(&callback)
#instance_#{callback}_callbacks ||= []
#instance_#{callback}_callbacks << callback
end
RUBY
end
end
end
This allows you to inject a full set of instance callbacks into any model just by including the module. In this case:
class Message
include InstanceCallbacks
end
And then you can do things like:
m = Message.new
m.after_save do
puts "In after_save callback"
end
m.save!
To add to bobthabuilda's answer - instead of defining the method on the objects metaclass, extend the object with a module:
def do_something_ballsy
callback = Module.new do
def before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
msg = Message.new
msg.extend(callback)
end
This way, you can define multiple callbacks, and they will be executed in the opposite order you added them.
The following will allow you to use an ordinary before_save construction, i.e. calling it on the class, only in this case, you call it on the instance's metaclass so that no other instances of Message shall be affected. (Tested in Ruby 1.9, Rails 3.13)
msg = Message.new
class << msg
before_save -> { puts "Message #{self} is saved" } # Here, `self` is the msg instance
end
Message.before_save # Calling this with no args will ensure that it gets added to the callbacks chain (but only for your instance)
Test it thus:
msg.save # will run the before_save callback above
Message.new.save # will NOT run the before_save callback above