How to refactor code for deprecated alias_method_chain - ruby-on-rails

I'm upgrading my rails application and I'm getting a warning saying alias_method_chain is deprecated. Please, use Module#prepend instead. But I'm not really understanding how to handle this. How can I change the code below?
def read_attribute_with_mapping(attr_name)
read_attribute_without_mapping(ADDRESS_MAPPING[attr_name] || attr_name)
end
alias_method_chain :read_attribute, :mapping

prepend is basically like importing a module, but it ends up "in front" of other code (so the module can call super to run the code it's in front of).
This is a runnable example with something close to your situation.
module MyModule
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end
class Example
prepend MyModule
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo
I defined read_attribute directly on Example, but it could just as well have been a method inherited from a superclass (such as ActiveRecord::Base).
This is a shorter but more cryptic version that uses an anonymous module:
class Example
prepend(Module.new do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end)
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo
UPDATE:
Just for fun and to address a question below, here's how it could be done without having to explicitly make any modules yourself. I don't think I'd choose to do it this way myself, since it obscures a common pattern.
# You'd do this once somewhere, e.g. config/initializers/prepend_block.rb in a Rails app.
class Module
def prepend_block(&block)
prepend Module.new.tap { |m| m.module_eval(&block) }
end
end
# Now you can do:
class Example
prepend_block do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo

Related

Is there a way to use class method in a module without extend it in rails?

currently I have a module like this:
module MyModule
def A
end
.....
end
and I have a model that I want to use that method A as a class method. However, the thing is I only need that A method. If I extend it, I am gonna extend the other unnecessary class methods into my model. Therefore, is there a way for me to do sth like MyModule.A without rewriting the module like this:
module MyModule
def A
...
end
def self.A
...
end
.....
end
It is kind of repeating myself if I do it that way. I still feel there is a better way to do it in Rails.
Use Module#module_function to make a single function to be a module function:
module M
def m1; puts "m1"; end
def m2; puts "m2"; end
module_function :m2
end
or:
module M
def m1; puts "m1"; end
module_function # from now on all functions are defined as module_functions
def m2; puts "m2"; end
end
M.m1 #⇒ NoMethodError: undefined method `m1' for M:Module
M.m2 #⇒ "m2"
Yes, you can define it as a module_function, then you should be able to access it using module name.
Ex:
module Mod
def my_method
100
end
def self.my_method_1
200
end
module_function :my_method
end
Mod.my_method
# => 100
Mod.my_method_1
# => 200
Note: No need to add the self defined methods in module_function, they are accessible directly. But it's needed for methods defined without self

Call method of included module from class

I have a use case where I have class A which includes module B.
class A
include B
def do_one_thing
# override module's method. do something different instead
end
def do_another_thing
# Call `do_one_thing` from here,
# but call the module's method, not the one I overrode above.
end
end
module B
included do
def do_one_thing
# ...
end
end
# some other methods
end
As shown above, I'm calling do_one_thing from do_another_thing. My problem is that I need to call the module's method (i.e. the super method). Is this possible in Rails?
To property use the included method, you'll need your B module to extend ActiveSupport::Concern but that won't give you the behaviour you want.
If I were you I'd abandon that pattern and use simple native Ruby module patterns:
module B
def do_one_thing
puts 'in module'
# ...
end
# some other methods
end
class A
include B
def do_one_thing
super
puts 'in class'
# override module's method. do something different instead
end
def do_another_thing
do_one_thing
# Call `do_one_thing` from here,
# but call the module's method, not the one I overrode above.
end
end
A.new.do_one_thing
The above code will correctly use the module inheritance you are looking for.
Read more about Ruby module inheritance here
You can 'save' included method before override
module B
extend ActiveSupport::Concern
included do
def do_one_thing
puts 'do_one_thing'
end
end
end
class A
include B
alias_method :old_do_one_thing, :do_one_thing
def do_one_thing
puts "I'd rather do this"
end
def do_another_thing
old_do_one_thing
end
end
a= A.new
a.do_one_thing
a.do_another_thing

How would I implement my own Rails-style validates() method in Ruby?

I'm trying to understand some Ruby metaprogramming concepts.
I think I understand classes, objects, and metaclasses. Unfortunately, I'm very unclear on exactly what happens with included Modules with respect to their instance/'class' variables.
Here's a contrived question whose solution will answer my questions:
Suppose I'm writing my own crappy Rails "validates" method, but I want it to come from a mixed-in module, not a base class:
module MyMixin
# Somehow validates_wordiness_of() is defined/injected here.
def valid?
# Run through all of the fields enumerated in a class that uses
# "validate_wordiness_of" and make sure they .match(/\A\w+\z/)
end
end
class MyClass
include MyMixin
# Now I can call this method in my class definition and it will
# validate the word-ness of my string fields.
validate_wordiness_of :string_field1, :string_field2, :string_field3
# Insert rest of class here...
end
# This should work.
MyMixin.new.valid?
Ok, so how would you store that list of fields from the validate_wordiness_of invocation (in MyClass) in such a way that it can be used in the valid? method (from MyMixin)?
Or am I coming at this all wrong? Any info would be super appreciated!
So here are two alternative ways of doing it:
With "direct" access
module MyMixin
def self.included(base)
base.extend(ClassMethods)
end
def wordy?(value)
value.length > 2
end
module ClassMethods
def validates_wordiness_of(*attrs)
define_method(:valid?) do
attrs.all? do |attr|
wordy?(send(attr))
end
end
end
end
end
class MyClass
include MyMixin
validates_wordiness_of :foo, :bar
def foo
"a"
end
def bar
"asrtioenarst"
end
end
puts MyClass.new.valid?
The downside to this approach is that several consecutive calls to validates_wordiness_of will overwrite each other.
So you can't do this:
validates_wordiness_of :foo
validates_wordiness_of :bar
Saving validated attribute names in the class
You could also do this:
require 'set'
module MyMixin
def self.included(base)
base.extend(ClassMethods)
end
module Validation
def valid?
self.class.wordy_attributes.all? do |attr|
wordy?(self.send(attr))
end
end
def wordy?(value)
value.length > 2
end
end
module ClassMethods
def wordy_attributes
#wordy_attributes ||= Set.new
end
def validates_wordiness_of(*attrs)
include(Validation) unless validation_included?
wordy_attributes.merge(attrs)
end
def validation_included?
ancestors.include?(Validation)
end
end
end
class MyClass
include MyMixin
validates_wordiness_of :foo, :bar
def foo
"aastrarst"
end
def bar
"asrtioenarst"
end
end
MyClass.new.valid?
# => true
I chose to make the valid? method unavailable until you actually add a validation. This may be unwise. You could probably just have it return true if there are no validations.
This solution will quickly become unwieldy if you introduce other kinds of validations. In that case I would start wrapping validations in validator objects.

Creating a module for raising class-specific errors

In my rails projects, I often use this sort of behavior in my classes and models:
class Whatever
class WhateverError < StandardError; end
def initialize(params={})
raise WhateverError.new("Bad params: #{params}") if condition
# actual class code to follow
end
end
The trouble is, this is both hugely repetitive and fairly verbose. I'd love it if I could just do this whenever I need to raise a class-specific error:
class ErrorRaiser
include ClassErrors
def initialize(params={})
error("Bad params: #{params}") if condition
error if other_condition # has default message
# actual class code to follow
end
def self.class_method
error if third_condition # class method, behaves identically
end
end
I'm having major trouble creating such a module. My sad early attempts have tended to look something like the below, but I'm pretty confused about what's available within the scope of the module, how to dynamically create classes (within methods?) or whether I have straightforward access to the "calling" class at all.
My basic requirements are that error be both a class method and an instance method, that it be "namespaced" to the class calling it, and that it have a default message. Any thoughts/help? Is this even possible?
module ClassErrorable
# This and the "extend" bit (theoretically) allow error to be a class method as well
module ClassMethods
def self.error(string=nil)
ClassErrorable.new(string).error
end
end
def self.included(base)
set_error_class(base)
base.extend ClassMethods
end
def self.set_error_class(base)
# I'm shaky on the scoping. Do I refer to this with # in a class method
# but ## in an instance method? Should I define it here with # then?
##error_class = "##{base.class}Error".constantize
end
def self.actual_error
# This obviously doesn't work, and in fact,
# it raises a syntax error. How can I make my
# constant a class inheriting from StandardError?
##actual_error = ##error_class < StandardError; end
end
def initialize(string)
#string = string || "There's been an error!"
end
def error(string=nil)
raise ##actual_error.new(string)
end
end
How about something like this (written in pure Ruby; it could be refactored to use some Rails-specific features like .constantize):
module ClassErrorable
module ClassMethods
def error(message = nil)
klass = Object::const_get(exception_class_name)
raise klass.new(message || "There's been an error!")
end
def exception_class_name
name + 'Error'
end
end
def self.included(base)
base.extend ClassMethods
Object::const_set(base.exception_class_name, Class.new(Exception))
end
def error(message = nil)
self.class.error(message)
end
end

How to alias a class method within a module?

I am using Ruby v1.9.2 and the Ruby on Rails v3.2.2 gem. I had the following module
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
end
end
and I wanted to alias the class method my_method. So, I stated the following (not working) code:
module MyModule
extend ActiveSupport::Concern
included do
def self.my_method(arg1, arg2)
...
end
# Note: the following code doesn't work (it raises "NameError: undefined
# local variable or method `new_name' for #<Class:0x00000101412b00>").
def self.alias_class_method(new_name, old_name)
class << self
alias_method new_name, old_name
end
end
alias_class_method :my_new_method, :my_method
end
end
In other words, I thought to extend the Module class someway in order to add an alias_class_method method available throughout MyModule. However, I would like to make it to work and to be available in all my Ruby on Rails application.
Where I should put the file related to the Ruby core extension of the Module class? Maybe in the Ruby on Rails lib directory?
How should I properly "extend" the Module class in the core extension file?
Is it the right way to proceed? That is, for example, should I "extend" another class (Object, BasicObject, Kernel, ...) rather than Module? or, should I avoid implementing the mentioned core extension at all?
But, more important, is there a Ruby feature that makes what I am trying to accomplish so that I don't have to extend its classes?
You could use define_singleton_method to wrap your old method under a new name, like so:
module MyModule
def alias_class_method(new_name, old_name)
define_singleton_method(new_name) { old_name }
end
end
class MyClass
def my_method
puts "my method"
end
end
MyClass.extend(MyModule)
MyClass.alias_class_method(:my_new_method, :my_method)
MyClass.my_new_method # => "my method"
Answering your comment, you wouldn't have to extend every single class by hand. The define_singleton_method is implemented in the Object class. So you could simply extend the Object class, so every class should have the method available...
Object.extend(MyModule)
Put this in an initializer in your Rails app and you should be good to go...
I found an answer on this website: http://engineering.lonelyplanet.com/2012/12/09/monitoring-our-applications-ruby-methods/
The solution is to use class_eval with a block. That enables using variables from the enclosing scope.
module Alias
def trigger
#trigger = true
end
def method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_method(with_x) do
"#{send(without_x)} with x"
end
alias_method without_x, name
alias_method name, with_x
end
end
def singleton_method_added(name)
if #trigger
#trigger = false
with_x = "#{name}_with_x"
without_x = "#{name}_without_x"
define_singleton_method(with_x) do
"singleton #{send(without_x)} with x"
end
singleton_class.class_eval do
alias_method without_x, name
alias_method name, with_x
end
end
end
end
class TestAlias
extend Alias
trigger
def self.foo
'foo'
end
trigger
def bar
'bar'
end
end
TestAlias.foo # => 'singleton foo with x'
TestAlias.new.bar # => 'bar with x'
If you don't have singleton_class then you should probably upgrade your version of Ruby. If that's not possible you can do this:
class Object
def singleton_class
class << self
self
end
end
end
The accepted answer was confusing and did not work.
class Module
def alias_class_method(new_name, old_name)
define_singleton_method(new_name, singleton_method(old_name))
end
end
module MyModule
def self.my_method
'my method'
end
end
MyModule.alias_class_method(:my_new_method, :my_method)
MyModule.my_new_method # => "my_method"

Resources