Calling Super from an Aliased Method - ruby-on-rails

I am trying to DRY up my code a bit so I am writing a method to defer or delegate certain methods to a different object, but only if it exists. Here is the basic idea: I have Shipment < AbstractShipment which could have a Reroute < AbstractShipment. Either a Shipment or it's Reroute can have a Delivery (or deliveries), but not both.
When I call shipment.deliveries, I want it to check to see if it has a reroute first. If not, then simply call AbstractShipment's deliveries method; if so, delegate the method to the reroute.
I tried this with the simple code below:
module Kernel
private
def this_method
caller[0] =~ /`([^']*)'/ and $1
end
end
class Shipment < AbstractShipment
...
def deferToReroute
if self.reroute.present?
self.reroute.send(this_method)
else
super
end
end
alias_method :isComplete?, :deferToReroute
alias_method :quantityReceived, :deferToReroute
alias_method :receiptDate, :deferToReroute
end
The Kernel.this_method is just a convenience to find out which method was called. However, calling super throws
super: no superclass method `deferToReroute'
I searched a bit and found this link which discusses that this is a bug in Ruby 1.8 but is fixed in 1.9. Unfortunately, I can't upgrade this code to 1.9 yet, so does anyone have any suggestions for workarounds?
Thanks :-)
Edit: After a bit of looking at my code, I realized that I don't actually need to alias all of the methods that I did, I actually only needed to overwrite the deliveries method since the other three actually call it for their calculations. However, I would still love to know y'all's thoughts since I have run into this before.

Rather than using alias_method here, you might be better served by hard-overriding these methods, like so:
class Shipment < AbstractShipment
def isComplete?
return super unless reroute
reroute.isComplete?
end
end
if you find you are doing this 5-10 times per class, you can make it nicer like so:
class Shipment < AbstractShipment
def self.deferred_to_reroute(*method_names)
method_names.each do |method_name|
eval "def #{method_name}; return super unless reroute; reroute.#{method_name}; end"
end
end
deferred_to_reroute :isComplete?, :quantityReceived, :receiptDate
end
Using a straight eval offers good performance characteristics and allows you to have a simple, declarative syntax for what you are doing within your class definition.

Related

How to define rescue_from-type handler for ActiveRecord models?

Developing in Rails 5.2.2.1. I want to define a "global" rescue handler for my model, so that I can catch NoMethodError and take appropriate action. I find that controllers can do this with rescue_from, but models cannot. Knowing that the Rails Developers are smart people ;) I figure there must be some Good Reason for this, but I'm still frustrated. Googling around, and I can't even find any examples of people asking how to do this, and other people either telling them how, or why they can't, or why they shouldn't want to. Maybe it's because rescue handlers can't return a value to the original caller?
Here's what I'm trying to do: I need to refactor my app so that what used to be a single model is now split into two (let's call them Orig and New). Briefly, I want to make it so that when an attribute getter method (say) is called against an Orig object, if that attribute has moved to New, then I can catch this error and call new.getter instead (understanding that Orig now belongs_to a New). This solution is inspired by my experience doing just this sort of thing with Perl5's AUTOLOAD feature.
Any ideas of how to get this done are much appreciated. Maybe I just have to define getters/setters for all the moved attributes individually.
Overide method_missing :) !?
You could try overriding the method_missing method. This could potentially cause confusing bugs, but overriding that method is definitely used to great effect in at least one gem that i know of.
I didn't want to call the class new because it is a reserved keyword and can be confusing. So I changed the class name to Upgraded.
This should get you started.
class Upgraded
def getter
puts "Congrats, it gets!"
end
end
class Original
def initialize
#new_instance = Upgraded.new
end
def method_missing(message, *args, &block)
if message == :attribute_getter
#new_instance.send(:getter, *args, &block)
else
super
end
end
def respond_to_missing?(method_name, *args)
method_name == :attribute_getter or super
end
end
c = Original.new
c.attribute_getter
You will have to change names of the getter and setter methods. Because you have a belongs_to association you can just use that.
Or you could try just using delegate_to
like #mu_is_too_short suggests, you could try something like this?
class Original < ApplicationRecord
belongs_to :upgraded
delegate :getter_method, :to => :upgraded
end
class Upgraded < ApplicationRecord
def getter_method
end
end
Apparently what I needed to know is the word "delegation". It seems there are a variety of ways to do this kind of thing in Ruby, and Rails, and I should have expected that Ruby's way of doing it would be cleaner, more elegant, and more evolved than Perl5. In particular, recent versions of Rails provide "delegate_missing_to", which appears to be precisely what I need for this use case.

Ruby Metaprogramming Q: Calling an external class method on after_save

I have the following classes:
class AwardBase
class AwardOne < AwardBase
class Post < ActiveRecord::Base
The Post is an ActiveRecord, and the Award has a can_award? class method which takes a post object and checks to see if it meets some criteria. If yes, it updates post.owner.awards.
I know I can do this using an Observer pattern (I tested it and the code works fine). However, that requires me to add additional code to the model. I'd like not to touch the model at all if possible. What I'd like to do is run the Award checks like this (the trigger will be invoked at class load time):
class AwardOne < AwardBase
trigger :post, :after_save
def self.can_award?(post)
...
end
end
The intention with the above code is that it should automatically add AwardOne.can_award? to Post's after_save method
So essentially what I'm trying to do is to get the trigger call be equivalent to:
class Post < ActiveRecord::Base
after_save AwardOne.can_award?(self)
...
end
which is basically:
class Post < ActiveRecord::Base
after_save :check_award
def check_award
AwardOne.can_award?(self)
end
end
How can I do this without modifying the Post class?
Here's what I've done (which does not appear to work):
class AwardBase
def self.trigger (klass, active_record_event)
model_class = klass.to_class
this = self
model_class.instance_eval do
def award_callback
this.can_award?(self)
end
end
model_class.class_eval do
self.send(active_record_event, :award_callback)
end
end
def self.can_award? (model)
raise NotImplementedError
end
end
The above code fails with the error:
NameError (undefined local variable or method `award_callback' for #<Post:0x002b57c04d52e0>):
You should think about why you want to do it this way. I would argue it is even worse than using the observer pattern. You are violating the principle of least surprise (also called principle of least astonishment).
Imagine that this is a larger project and I come as a new developer to this project. I am debugging an issue where a Post does not save correctly.
Naturally, I will first go through the code of the model. I might even go through the code of the posts controller. Doing that there will be no indication that there is a second class involved in saving the Post. It would be much harder for me to figure out what the issue is since I would have no idea that the code from AwardOne is even involved.
In this case it would actually be most preferable to do this in the controller. It is the place that is easiest to debug and understand (since models have enough responsibilities already and are generally larger).
This is a common issue with metaprogramming. Most of the time it is better to avoid it precisely because of principle of least surprise. You will be glad you didn't use it a year from now when you get back to this code because of some issue you need to debug. You will forget what "clever" thing you have done. If you don't have a hell-of-a-good reason then just stick to the established conventions, they are there for a reason.
If nothing else then at least figure out a way to do this elegantly by declaring something in the Post model. For example by registering an awardable class method on ActiveRecord::Base. But the best approach would probably be doing it in the controller or via a service object. It is not the responsibility of AwardOne to handle how Post should be saved!
Because you are adding award_callback as class method. I bet it will be registered if you grep class methods.
So change your code like below. It should work fine.
model_class.class_eval do ## Changed to class_eval
def award_callback
this.can_award?(self)
end
end
Let me give a detailed example if it sounds confusing.
class Test
end
Test.instance_eval do
def class_fun
p "from class method "
end
end
Test.class_eval do
def instance_fun
p "from instance method "
end
end
Test.methods.grep /class_fun/
# => [:class_fun]
Test.instance_methods.grep /instance_fun/
# => [:instance_fun]
Test.class_fun
# => "from class method "
Test.new.instance_fun
# => "from instance method "

How does Rails method call like "has_one" work?

I am PHP dev and at the moment I am learning Rails (3) and of course - Ruby. I don't want to believe in magic and so I try to understand as much as I can about things that happen "behind" Rails. What I found interesting are the method calls like has_one or belongs_to in ActiveRecord models.
I tried to reproduce that, and came with naive example:
# has_one_test_1.rb
module Foo
class Base
def self.has_one
puts 'Will it work?'
end
end
end
class Model2 < Foo::Base
has_one
end
Just running this file will output "Will it work?", as I expected.
While searching through rails source I found responsible function: def has_one(association_id, options = {}).
How could this be, because it is obviously an instance (?) and not a class method, it should not work.
After some researching I found an example that could be an answer:
# has_one_test_2.rb
module Foo
module Bar
module Baz
def has_one stuff
puts "I CAN HAS #{stuff}?"
end
end
def self.included mod
mod.extend(Baz)
end
end
class Base
include Bar
end
end
class Model < Foo::Base
has_one 'CHEEZBURGER'
end
Now running has_one_test_2.rb file will output I CAN HAS CHEEZBURGER. If I understood this well - first thing that happens is that Base class tries to include Bar module. On the time of this inclusion the self.included method is invoked, which extends Bar module with Baz module (and its instance has_one method). So in the essence has_one method is included (mixed?) into Base class. But still, I don't fully get it. Object#extend adds the method from module but still, I am not sure how to reproduce this behaviour using extend. So my questions are:
What exactly happened here. I mean, still don't know how has_one method become class method? Which part exactly caused it?
This possibility to make this method calls (which looks like configuration) is really cool. Is there an alternative or simpler way to achieve this?
You can extend and include a module.
extend adds the methods from the module as class methods
A simpler implementation of your example:
module Bar
def has_one stuff
puts "I CAN HAS #{stuff}?"
end
end
class Model
extend Bar
has_one 'CHEEZBURGER'
end
include adds the methods from the module as instance methods
class Model
include Bar
end
Model.new.has_one 'CHEEZBURGER'
Rails uses this to dynamically add methods to your class.
For example you could use define_method:
module Bar
def has_one stuff
define_method(stuff) do
puts "I CAN HAS #{stuff}?"
end
end
end
class Model
extend Bar
has_one 'CHEEZBURGER'
end
Model.new.CHEEZBURGER # => I CAN HAS CHEEZBURGER?
I commend you for refusing to believe in the magic. I highly recommend you get the Metaprogramming Ruby book. I just recently got it and it was triggering epiphanies left and right in mah brainz. It goes over many of these things that people commonly refer to as 'magic'. Once it covers them all, it goes over Active Record as an example to show you that you now understand the topics. Best of all, the book reads very easily: it's very digestible and short.
Yehuda went through some alternatives on way to Rails3: http://yehudakatz.com/2009/11/12/better-ruby-idioms/
Moreover, you can use a (usually heavily abused, but sometimes quite useful) method_missing concept:
class Foo
def method_missing(method, *arg)
# Here you are if was some method that wasn't defined before called to an object
puts "method = #{method.inspect}"
puts "args = #{arg.inspect}"
return nil
end
end
Foo.new.abracadabra(1, 2, 'a')
yields
method = :abracadabra
args = [1, 2, "a"]
Generally, this mechanism is quite often used as
def method_missing(method, *arg)
case method
when :has_one
# implement has_one method
when :has_many
# ...
else
raise NoMethodError.new
end
end

Is aliasing methods frowned upon?

I'm developing an online store, and the customer needs the ability to delete an order and have its products automatically restocked (e.g., for test orders). Here's my first try at implementing this:
class Order < ActiveRecord::Base
def destroy_and_restock
restock_products
destroy
end
protected
def restock_products
line_items.each do |li|
li.product.quantity_on_hand += li.quantity
li.product.save
end
end
end
But what if I need to create another destroy_and_x method later? Why not allow that X to be passed as a parameter to the destroy() method? So now I'm thinking of going with this:
alias :old_destroy :destroy
def destroy(options = {})
if options['restock'] == true
restock_products
end
old_destroy
end
protected
def restock_products
line_items.each do |li|
li.product.quantity_on_hand += li.quantity
li.product.save
end
This is more extensible, but makes me feel somewhat dirty. Am I wrong to feel dirty? Is there a better way of doing this?
I'd say "yes, this is dirty." Your intention isn't to modify the behavior of the 'destroy' method, but rather to do some domain-specific work, then run destroy. Your first approach is great -- define a method that does what you want, and invoke destroy as needed. I think that 'wrapping' or 'monkey-patching' a method, as you're considering, is a technique that's best applied when standard OO approaches can't be used -- eg, when you need to modify/augment behavior in a class that is defined and used outside of your realm of control.
Even if you are considering modifying the behavior of the destroy method itself, I'd suggest overriding the method here, rather than wrapping it:
def destroy(options = {})
restock_products if options['restock']
super() # I think parens are necessary here, to avoid passing options up the chain
end
How about just use a block? Then you dont have to pull your hair apart while designing this in the class and you can do more as and when you need to:
def destroy_after &block
yield if block
destroy
end
Then call it like this:
order.destroy_after { order.restock_products }
I can not think of a good name for this function... but I hope you get the idea.
Horace, I misunderstood your question. I think you are looking for this:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Now you can keep your method protected and add as many before_destroy things as you like. Hope this works for you without overriding destroy.
Best of luck.
If monkey patching doesn't let you sleep at night, you can achieve the same thing by subclassing. When I'm in the need of a quick hack, or a quick debug hack, I monkey patch as well.

Ruby mixins and calling super methods

Ok, so I've been refactoring my code in my little Rails app in an effort to remove duplication, and in general make my life easier (as I like an easy life). Part of this refactoring, has been to move code that's common to two of my models to a module that I can include where I need it.
So far, so good. Looks like it's going to work out, but I've just hit a problem that I'm not sure how to get around. The module (which I've called sendable), is just going to be the code that handles faxing, e-mailing, or printing a PDF of the document. So, for example, I have a purchase order, and I have Internal Sales Orders (imaginatively abbreviated to ISO).
The problem I've struck, is that I want some variables initialised (initialized for people who don't spell correctly :P ) after the object is loaded, so I've been using the after_initialize hook. No problem... until I start adding some more mixins.
The problem I have, is that I can have an after_initialize in any one of my mixins, so I need to include a super call at the start to make sure the other mixin after_initialize calls get called. Which is great, until I end up calling super and I get an error because there is no super to call.
Here's a little example, in case I haven't been confusing enough:
class Iso < ActiveRecord::Base
include Shared::TracksSerialNumberExtension
include Shared::OrderLines
extend Shared::Filtered
include Sendable::Model
validates_presence_of :customer
validates_associated :lines
owned_by :customer
order_lines :despatched # Mixin
tracks_serial_numbers :items # Mixin
sendable :customer # Mixin
attr_accessor :address
def initialize( params = nil )
super
self.created_at ||= Time.now.to_date
end
end
So, if each one of the mixins have an after_initialize call, with a super call, how can I stop that last super call from raising the error? How can I test that the super method exists before I call it?
You can use this:
super if defined?(super)
Here is an example:
class A
end
class B < A
def t
super if defined?(super)
puts "Hi from B"
end
end
B.new.t
Have you tried alias_method_chain? You can basically chained up all your after_initialize calls. It acts like a decorator: each new method adds a new layer of functionality and passes the control onto the "overridden" method to do the rest.
The including class (the thing that inherits from ActiveRecord::Base, which, in this case is Iso) could define its own after_initialize, so any solution other than alias_method_chain (or other aliasing that saves the original) risks overwriting code. #Orion Edwards' solution is the best I can come up with. There are others, but they are far more hackish.
alias_method_chain also has the benefit of creating named versions of the after_initialize method, meaning you can customize the call order in those rare cases that it matters. Otherwise, you're at the mercy of whatever order the including class includes the mixins.
later:
I've posted a question to the ruby-on-rails-core mailing list about creating default empty implementations of all callbacks. The saving process checks for them all anyway, so I don't see why they shouldn't be there. The only downside is creating extra empty stack frames, but that's pretty cheap on every known implementation.
You can just throw a quick conditional in there:
super if respond_to?('super')
and you should be fine - no adding useless methods; nice and clean.
Rather than checking if the super method exists, you can just define it
class ActiveRecord::Base
def after_initialize
end
end
This works in my testing, and shouldn't break any of your existing code, because all your other classes which define it will just be silently overriding this method anyway

Resources