Scrubbing a block from a call to super - ruby-on-rails

I'm working with ActiveAttr which gives you that nice initialize via block option:
person = Person.new() do |p|
p.first_name = 'test'
p.last_name = 'man'
end
However, in a specific class that include ActiveAttr::Model, I want to bypass this functionality since I want to use the block for something else. So here we go:
class Imperator::Command
include ActiveAttr::Model
end
class MyCommand < Imperator::Command
def initialize(*args, &block)
#my_block = block
super(*args)
end
end
This fails miserably, because the block still gets passed up the chain, and eventually inside of ActiveAttr, this code gets run:
def initialize(*)
super
yield self if block_given?
end
So if my call looks like so:
MyCommand.new() { |date| date.advance(month: 1) }
it fails as follows:
NoMethodError: undefined method `advance' for #<MyCommand:0x007fe432c4fb80>
since MyCommand has no method :advance it the call to MyCommand obviously fails.
So my question is this, is there a way that I can remove the block from the method signature before I call super again, so that the block travels no further than my overridden initializer?

Try
super(*args,&nil)
The & makes ruby use nil as the block and ruby seems smart enough to realise this means no block.

That is certainly a neat trick, but a better approach would be to not use ActiveAttr::Model module directly and instead include only the modules you need.
Rather than
class Imperator::Command
include ActiveAttr::Model
end
Do
class Imperator::Command
include BasicModel
# include BlockInitialization
include Logger
include MassAssignmentSecurity
include AttributeDefaults
include QueryAttributes
include TypecastedAttributes
def initialize(*args, &block)
#my_block = block
super(*args)
end
end
Once you see the exploded view of ActiveAttr::Model is doing there may be other things you really don't want. In that case just simply omit the includes. The intention was to provide an à la carte approach to model constructing.

Related

Rails: Concern with before_filter type of method

I am just getting my hands on Concerns in Rails and try to implement a simple logging for ActiveRecord classes. In there I want to define the field that should go into the log and have the log written automatically after save.
What I have is this:
#logable.rb (the concern)
module Logable
extend ActiveSupport::Concern
#field = nil
module ClassMethods
def set_log_field(field)
#feild = field
end
end
def print_log
p "LOGGING: #{self[#index.to_s]}"
end
end
#houses.rb (the model using the concern)
class House < ActiveRecord::Base
include Logable
after_save :print_log
set_log_field :id
end
Unfortunately the call to set_log_field does not have an effect - or rather the given value does not make it to print_log.
What am I doing wrong?
Thanks for your help!
You probably mean this (btw, why not Loggable?):
# logable.rb
module Logable
extend ActiveSupport::Concern
# Here we define class-level methods.
# Note, that #field, defined here cannot be referenced as #field from
# instance (it's class level!).
# Note also, in Ruby there is no need to declare #field in the body of a class/module.
class_methods do
def set_log_field(field)
#field = field
end
def log_field
#field
end
end
# Here we define instance methods.
# In order to access class level method (log_field), we use self.class.
included do
def print_log
p "LOGGING: #{self.class.log_field}"
end
end
end
Update You also asked about what's the difference between methods in included block and those within method body.
To make a short resume there is seemingly no difference. In very good approximation you can consider them the same. The only minor difference is in dependency management. Great illustration of it is given in the end of ActiveSupport::Concern documentation. It worth reading, take a look!

Overriding methods in an ActiveSupport::Concern module which are defined by a class method in the same module

I have an ActiveSupport::Concern module which looks roughly like the following:
module MyModel
module Acceptance
extend ActiveSupport::Concern
included do
enum status: [:declined, :accepted]
end
def declined!
self.status = :declined
# some extra logic
self.save!
end
def accepted!
self.status = :accepted
# some extra logic
self.save!
end
end
end
This is only ever going to be included into ActiveRecord classes, hence the use of enum. Basically, I'm overriding the declined! and accepted! methods that are created by ActiveRecord::Enum.enum with some extra, custom logic of my own.
The problem is, this doesn't work, because when I call #model.declined! it justs call the original implementation of declined! and ignores my custom method.
Looks like my custom methods are being included into the calling class before the included block is being run - meaning my custom methods are being overridden by the ones defined by enum, instead of the other way around.
There some easy workarounds in this particular situation (e.g. I could move the call enum back into the including class and make sure it's above the line include MyModel::Acceptance, but I'm wondering if there's a way I can solve this problem while keeping it all in the same module.
Is there any way I can call a class method within included that defines an instance method, then override that instance method from within the same Concern module?
I think you're looking for define_method.
module MyModel
module Acceptance
extend ActiveSupport::Concern
included do
enum status: [:declined, :accepted]
define_method :declined! do
self.status = :declined
# some extra logic
self.save!
end
define_method :accepted! do
self.status = :accepted
# some extra logic
self.save!
end
end
end
end

Call module subclass without specify the name

I would like to access my subclass using only the name of my module.
module MyModule
class UselessName
include OtherModel
# only self method
def self.x
end
end
# No other class
end
And I would like to write MyModule.x and not MyModule::UselessName.x
I could transform my module in class, but I use RoR Helpers, and I would prefer that MyModule remains a module and not a class.
Is there a way to do this ?
Thanks ;)
OK, let's split problem into two - getting list of such methods and making proxies in the module.
Getting list might be a little tricky:
MyModule::UselessName.public_methods(false) - MyModule::UselessName.superclass.public_methods(false)
Here we start with list of all public class methods and subtract list of all superclass's public class methods from it.
Now, assuming we know method's name, we need to make proxy method.
metaclass = class << MyModule; self; end
metaclass.send(:define_method, :x) do |*args, &block|
MyModule::UselessName.send(:x, *args, &block)
end
This code will just make equivalent of following definition at runtime.
module MyModule
def x(*args, &block)
MyModule::UselessName.send(:x, *args, &block)
end
end
So let's put it together in simple function.
def make_proxies(mod, cls)
methods = cls.public_methods(false) - cls.superclass.public_methods(false)
metaclass = class << mod; self; end
methods.each do |method|
metaclass.send(:define_method, method) do |*args, &block|
cls.send(method, *args, &block)
end
end
end
So now you'll just need to call it for needed modules and classes. Note that "destination" module can be different from "source" module owning the class, so you can slurp all methods (given they have different names or you'll prefix them using class name) to one module. E.g. for your case just make following call.
make_proxies(MyModule, MyModule::UselessName)
Ok, I've found a VERY DIRTY way to accomplish what I THINK you mean:
module MyModule
class UselessName
include OtherModule
# have whatever you want here
end
def self.x
# whatever
end
end
So somewhere in your code you can do, and I repeat THIS IS VERY, VERY DIRTY!
MyModule.methods(false).each do |m|
# m = method
# now you can redefine it in your class
# as an instance method. Not sure if this
# is what you want though
MyModule::UselessName.send(:define_method, :m) do
# in this NEW (it's not the same) method you can
# call the method from the module to get the same
# behaviour
MyModule.send(m)
end
end
I don't know if this overwrites an instance method with the same name if it's in the class before or if it throws an exception, you have to try that.
In my opinion you should overthink your app design, because this is not the way it should be, but here you go...

why use include module when class_eval by itself would suffice

In the following code include module is used. The way I see it if include module is removed then also an instance method would be created. Then why user include module ?
http://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations.rb#L1416
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy # def destroy
super # super
#{reflection.name}.clear # posts.clear
end # end
RUBY
}
First of all let's make one thing clear. When they call super inside the class_eval — it has absolutely nothing to do with why they used include Module.new {} thing. In fact the super which was called inside the destroy method is completely irrelevant to answering your question. There could be any arbitrary code inside that destroy method.
Now that we got it out of the way, here's what's going on.
In ruby, if you simply define a class method, and then define it again in the same class, you will not be able to call super to access the previous method.
For example:
class Foo
def foo
'foo'
end
def foo
super + 'bar'
end
end
Foo.new.foo # => NoMethodError: super: no superclass method `foo' for #<Foo:0x101358098>
This makes sense, because the first foo was not defined in some superclass, or anywhere up the lookup chain (which is where super points). However, you can define the first foo in such a way that when you later overwrite it — it will be available by calling super. This is exactly what they wanted to achieve with doing module include.
class Foo
include Module.new { class_eval "def foo; 'foo' end" }
def foo
super + 'bar'
end
end
Foo.new.foo # => "foobar"
This works, because when you include a module, ruby inserts it into the lookup chain. This way you can subsequently call super in the second method, and expect the included method to be called. Great.
However, you may wonder, why not simply include a module without all the tricks? Why are they using block syntax? We know that my above example is exactly equivalent to the following:
module A
def foo
'foo'
end
end
class Foo
include A
def foo
super + 'bar'
end
end
Foo.new.foo # => "foobar"
So why didn't they do that? The answer is — the call to reflection. They needed to capture the variable (or method) which was available in the current context, which is reflection.
Since they are defining the new module using block syntax, all the variables outside of the block are available for usage inside the block. Convenient.
Just to illustrate.
class Foo
def self.add_foo_to_lookup_chain_which_returns(something)
# notice how I can use variable something in the class_eval string
include Module.new { class_eval "def foo; '#{something}' end" }
end
end
# so somewhere else I can do
class Foo
add_foo_to_lookup_chain_which_returns("hello")
def foo
super + " world"
end
end
Foo.new.foo # => "hello world"
Neat, huh?
Now let me stress it again. The call to super inside of the destroy method in your example has nothing to do with any of the above. They called it for their own reasons, because maybe the class where this is happening is subclassing another class which already defined destroy.
I hope this made it clear.
I'm guessing but... they don't want to overwrite the "destroy" method, and want to leave it available to be overloaded by some end-user (you or me), without it removing this "reflection.clear" functionality.
So - by including it as a module, they can call "super" which will call the original destroy or the overloaded version (written by the end-user).
Thanks to include, destroy method is not overwritten. It lands in ghost class that actual class derives from. That way, when one will call destroy on AR object, original one will be called, and super will call one from anonymous module (which will later call original destroy from class that it derived from).
A bit tricky, indeed.

Ruby: Alter class static method in a code block

Given the Thread class with it current method.
Now inside a test, I want to do this:
def test_alter_current_thread
Thread.current = a_stubbed_method
# do something that involve the work of Thread.current
Thread.current = default_thread_current
end
Basically, I want to alter the method of a class inside a test method and recover it after that.
I know it sound complex for another language, like Java & C# (in Java, only powerful mock framework can do it). But it's ruby and I hope such nasty stuff would be available
You might want to take a look at a Ruby mocking framework like Mocha, but in terms of using plain Ruby it can be done using alias_method (documentation here) e.g.
beforehand:
class Thread
class << self
alias_method :old_current, :current
end
end
then define your new method
class Thread
def self.current
# implementation here
end
end
then afterwards restore the old method:
class Thread
class << self
alias_method :current, :old_current
end
end
Update to illustrate doing this from within a test
If you want to do this from within a test you could define some helper methods as follows:
def replace_class_method(cls, meth, new_impl)
cls.class_eval("class << self; alias_method :old_#{meth}, :#{meth}; end")
cls.class_eval(new_impl)
end
def restore_class_method(cls, meth)
cls.class_eval("class << self; alias_method :#{meth}, :old_#{meth}; end")
end
replace_class_method is expecting a class constant, the name of a class method and the new method definition as a string. restore_class_method takes the class and the method name and then aliases the original method back in place.
Your test would then be along the lines of:
def test
new_impl = <<EOM
def self.current
"replaced!"
end
EOM
replace_class_method(Thread, 'current', s)
puts "Replaced method call: #{Thread.current}"
restore_class_method(Thread, 'current')
puts "Restored method call: #{Thread.current}"
end
You could also write a little wrapper method which would replace a method, yield to a block and then ensure that the original method was reinstated afterwards e.g.
def with_replaced_method(cls, meth, new_impl)
replace_class_method(cls, meth, new_impl)
begin
result = yield
ensure
restore_class_method(cls, meth)
end
return result
end
Inside your test method this could then be used as:
with_replaced_method(Thread, 'current', new_impl) do
# test code using the replaced method goes here
end
# after this point the original method definition is restored
As mentioned in the original answer, you can probably find a framework to do this for you but hopefully the above code is interesting and useful anyway.

Resources