Ruby mixins and calling super methods - ruby-on-rails

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

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.

Mongoid remove unsaved embedded documents

I'm using mongoid for an app where the user is the parent document, and pretty much all other information is embedded in the user. So for instance, my controller #new action for a Relationship belonging to the user looks something like:
def new
#relationship = current_user.relationships.new(friend_id: params[:fid])
#relationship.validate
end
Because I run validations on the relationship that will show up in the view and some of those validations need to be able to reference the parent, I can't just call #relationship = Relationship.new(friend_id: params[:fid]), but having instantiated this relationship in the user's relationship array, it's now hanging out in there, even if the user decides they don't want to make a new relationship after all and they go to another part of the site. If they go to the relationship index page, they'll see it in the list unless I filter it out.
If the relationship is valid and they do something elsewhere that causes the user to save, that dummy relationship is now a real one. If it's not valid, the save is going to fail for unknown reasons.
I have a number of models I intend to embed in the user, so I will have this issue with every one of them.
I know I can call current_user.reload to clear the junk out, but it feels ridiculous to me that I would have to hit the database every time I wanted to do this. I could also orphan the relationship after validating, but that feels hacky.
It seems to me that this is a problem people should run into all the time with embedded documents, so I would think there'd be some kind of built in solution, but I can't seem to find it anywhere. I saw this question, which is similar to mine, but I want something more extensible, so that I don't have to put it everywhere.
I'm about to make a module that will add a clear_unsaved_#{relation} method to the class for each embedded relation, but the idea frustrates me, so I wanted to see if anyone has a better idea of how to do it, and also where is best to call it.
I ended up making a monkey patch that overrides Mongoid's embeds_many and embeds_one class methods to also define an instance method for clearing unsaved documents for that relation. This felt like the most straightforward way to me because it's very little code and it means I don't have to remember to include it places.
# config/initializers/patches/dirty_tracking_embedded.rb
module DirtyTrackingEmbedded
# override the embedding methods to also make dirty-tracking
def embeds_many(name, options= {}, &block)
define_method "clear_unsaved_#{name}" do
# remove_child removes it from the array without hitting the database
send(name).each {|o| remove_child(o) unless o.persisted?}
end
super
end
def embeds_one(name, options={}, &block)
define_method "clear_unsaved_#{name}" do
dirty = send(name)
remove_child(dirty) unless dirty.persisted?
end
super
end
end
module Mongoid
module Association
module Macros
module ClassMethods
prepend DirtyTrackingEmbedded
end
end
end
end
Then in my controller I resorted to an after_action:
# app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
after_action :clear_unsaved, only: [:new]
def new
#relationship = current_user.relationships.new(friend_id: params[:fid])
#relationship.validate
end
private
def clear_unsaved
current_user.clear_unsaved_relationships
end
end
Other Possibilities
Different monkey patch
You could monkey patch the setup_instance_methods! methods in Mongoid::Association::Embedded::EmbedsMany and Mongoid::Association::Embedded::EmbedsOne to include setting up an instance method to clear unsaved. You can find an example of how the Mongoid folks do that sort of thing by looking at Mongoid::Association::Accessors#self.define_ids_setter!. I'd recommend doing your patching with a prepend like in the solution I went with, so you can inherit the rest of the method.
Combo monkey patch and inheritance
Mongoid chooses which class to use to instantiate an association from a constant called MACRO_MAPPING in Mongoid::Association, so you could make classes that inherit from EmbedsMany and EmbedsOne with just setup_instance_methods! overridden to add the needed instance method, then you would only have to monkey patch MACRO_MAPPING to map to your new classes.
Concern
If you're anti-monkey patching, you could use the code from my DirtyTrackingEmbedded module to make an ActiveSupport::Concern that does the same thing. You'll want to put the overridden methods in the class_methods block, and then just make sure you include this module after you include Mongoid::Document in any model class you want it in.

ruby string formatting in rails

So the goal is to turn for instance "ProductCustomer", which comes from the class, into "product customer".
I used to have this:
notification.notifiable.model_name.human.downcase
It didn't work out of course, since if the notifiable is nil it breaks. I don't
want to use try or something similar since it can be solved with using notifiable_type.
So now I changed to this:
notification.notifiable_type.split(/(?=[A-Z])/).join(' ').downcase
But this is way too complex to use every time in the view. So either I would like to define this as a view helper or using some ruby formatting method if there is a simple one.
Can somebody tell me what the Rails convention is in this case? If it's a helper, how does the method looks like and where should I put it?
Options:
Initializer
/your_app/config/initializers/my_initializer.rb
module MyModule
def human_model_name
self.class.to_s.tableize.singularize.humanize.downcase
end
end
ActiveRecord::Base.send(:include, MyModule)
Including MyModule in ActiveRecord::Base will add human_model_name in all ActiveRecord instances. So, you will be able to do...
user.human_model_name #=> user
notification.human_model_name #=> notification
notification.notifiable.human_model_name #=> product customer
any_active_record_instance.human_model_name #=> etc.
To avoid exceptions when notifiable is nil, you can use try method.
notification.try(:notifiable).try(:human_model_name)
A cleaner way can be use delegate
class Notification < ActiveRecord::Base
delegate :human_model_name, to: :notifiable, prefix: true, allow_nil: true
end
Then, you can do:
notification.notifiable_human_model_name # if notifiable is nil will return nil as result instead of an exception
A simple method in your Notification model
class Notification < ActiveRecord::Base
def human_notifable_name
return unless self.notifiable # to avoid exception with nil notifiable
self.notifiable.class.to_s.tableize.singularize.humanize.downcase
end
end
Then...
notification.human_notifable_name
View Helper (If you think this is a view related method only)
module ApplicationHelper # or NotificationHelper
def human_model_name(instance)
return unless instance # to avoid exception with nil instance
instance.class.to_s.tableize.singularize.humanize.downcase
end
end
Then, in your view...
<%= human_model_name(notification.notifiable) %>
Either option is fine. I would use one or the other depending on the case. In this case, I would use the first option. I think you are adding behaviour that can be useful in any model. I mean your method is not directly related with something about notifications. In a more generic way you want a method to return the class name of an ActiveRecord's instance. Today you want the model name of the notifiable ActiveRecord's instance. But, tomorrow you may want the model name of any ActiveRecord model.
To answer the question "Where should I put a method?" I suggest to break (without fear) a little bit the MVC pattern and read about:
Decorators, presenters and delegators
Services
(a little bit old, but you can get the idea)
"ProductCustomer".tableize.singularize.humanize.downcase

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 "

Calling Super from an Aliased Method

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.

Resources