Rename overlapping methods, added by observable, without affecting the others - ruby-on-rails

There's an ActiveRecord model, which has it's own (basically, included from the other ActiveRecord's module) #changed? and #change methods. And there's a module Observable which also has it's own changed? and change definitions.
I need to define a custom module, which automatically includes Observable module and performs some underlying logic, but the problem is, that when I try to alias and undef original Observable method, it also undefs methods from other modules, which is critical.
Is there any elegant way to solve this? As I don't really want to implemet a custom Observable module.
Here's an example code:
require 'observer'
# Trying to undef Observable's #changed and #changed?
# But really, when included, it also undefs methods from
# other modules included by original class
module TryingToRewriteChanged
include ::Observable
alias triggerable_changed? changed?
alias triggerable_changed changed
undef_method :changed?
undef_method :changed
end
# Custom module which has some logic in .included
module Triggerable
def self.included(obj)
obj.class_eval do
include TryingToRewriteChanged
# ... And other magic
end
end
end
# Mock for some ActiveRecord module with
# #changed and #changed? definitions
module ActiveRecord
module SomeActiveRecordModule
def changed
puts 'original changed'
end
def changed?
puts 'original changed?'
end
end
end
# Mock for ActiveRecord::Base class
module ActiveRecord
class Base
include SomeActiveRecordModule
end
end
# Example model, which need to include Triggerable module
class SomeModel < ActiveRecord::Base
include Triggerable
end
# ActiveRecord's #changed is no more available
SomeModel.new.changed
# -> undefined method `changed'
https://repl.it/repls/KeyQuickwittedAsiantrumpetfish
Thank you.

It you try to print ancestors of model, it will show
SomeModel.ancestors # [SomeModel, TryingToRewriteChanged, Observable, Triggerable, ActiveRecord::Base, ActiveRecord::SomeActiveRecordModule, Object, JSON::Ext::Generator::GeneratorMethods::Object, Kernel, BasicObject]
Hence, when calling SomeModel.new.changed, it will call changed of Observable. And this method already undef_method, it will throw exception as the document: https://apidock.com/ruby/Module/undef_method
Prevents the current class from responding to calls to the named
method. Contrast this with remove_method, which deletes the method
from the particular class; Ruby will still search superclasses and
mixed-in modules for a possible receiver.
There is 2 way you can use to resolve this issue:
1 - Prepend ActiveRecord::SomeActiveRecordModule before TryingToRewriteChanged in inheritance chain.
# Mock for ActiveRecord::Base class
module ActiveRecord
class Base
include Triggerable
include SomeActiveRecordModule
end
end
# Example model, which need to include Triggerable module
class SomeModel < ActiveRecord::Base
end
ref: https://repl.it/repls/ProudGuiltyMorpho
But using this way, you have to accept that Triggerable will be included in all ActiveRecord subclasses which may larger scope than your expectation.
2 - Implement changed and changed? methods in SomeModel to call corresponding methods in SomeActiveRecordModule explicitly. Using some techniques of metaprogramming may help to shorten the code.
class SomeModel < ActiveRecord::Base
include Triggerable
def changed
ActiveRecord::SomeActiveRecordModule.instance_method('changed').bind(self).call
end
def changed?
ActiveRecord::SomeActiveRecordModule.instance_method('changed?').bind(self).call
end
end
ref: https://repl.it/repls/ContentTameIsabellinewheatear

Found pretty ugly but working solution, the main magic happens under the TryingToRewriteChanged module:
require 'observer'
# Create aliases for #changed and changed? methods from
# Observable - basically, renaming them.
# And then call top-level methods of the first includer when
# receiving #changed / changed?
module TryingToRewriteChanged
include ::Observable
alias triggable_changed changed
alias triggable_changed? changed?
[:changed, :changed].each do |method|
define_method(method) do |*args|
return super(*args) unless origin_method_present?(method)
call_origin_method(method, *args)
end
end
private
def call_origin_method(name, *args)
method(name).super_method.super_method.call(*args)
end
def origin_method_present?(name)
method(name).super_method&.super_method&.name == name
end
end
# Custom module which has some logic in .included
module Triggerable
def self.included(obj)
obj.class_eval do
include TryingToRewriteChanged
end
end
end
# Mock for some ActiveRecord module with
# #changed and changed? definitions
module ActiveRecord
module SomeActiveRecordModule
def changed
puts 'original changed'
end
def changed?
puts 'original changed?'
end
end
end
# Mock for ActiveRecord::Base class
module ActiveRecord
class Base
include SomeActiveRecordModule
end
end
# Example model, which need to include Triggerable module
class SomeModel < ActiveRecord::Base
include Triggerable
end
# ActiveRecord's #changed is no more available
SomeModel.new.changed
https://repl.it/repls/ThirstyFlawedXanthareel

Related

Loading module into User Model in Rails

Trying to make available the methods I have stored in a Module which is located in app/models (side note: not sure if this is the correct place for modules?).
Module:
module MyModule
class MyClass
def some_method
# do something
end
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
I am getting a NoMethodError:
NoMethodError (undefined method 'some_method' for #<User:0x00007f6a3ce452c0>
You seem to have missunderstood what what modules and classes do in Ruby. In Ruby a module is simply an object that wraps a set of methods and constants.
A module can extend other modules, classes and objects and can be included in classes thus implementing multiple inheritance. Modules in Ruby fill the role that traits, namespaces and singletons do in other languages.
Classes are actually modules (Module is part of the ancestors chain of Class) with the key difference that you can make instances of a class and that class can inherit from a single other class and cannot extend other objects or be included.
The code example here actually doesn't make sense. If you want to declare a method that will be available to classes that include a module you want to declare it in the module itself:
module MyModule
def some_method
# do something
end
end
When you then call User#another_method it will look in the ancestors chain of the User class until it finds the method which is defined in MyModule.
module MyModule
class MyClass
def some_method
# do something
end
end
end
Will actually definte the class MyClass with an instance method that is only available to instances of MyClass. The only thing that the module does here is change the module nesting so that the class is defined in MyModule instead of the global namespace.
If you want to mix in a method from a method into your class then just put the methods directly in the module (without an intermediate class).
Module:
module MyModule
def some_method
# do something
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
Have a look at this answer, you need to instantiate your Class first. Or if you want to
class User < ApplicationRecord
include MyModule
def another_method
my_instance = MyClass.new
my_instance.some_method
end
end
As for a place to store your Module, have a look at this guide about service objects, it gave me some inspiration when it comes to different modules.

Ruby/Rails: Circular dependency when including concern in ApplicationRecord

I have a concern that creates a class macro that I want available for all the models in my Rails application. So I'm including it in ApplicationRecord. The code is as follows:
# application_record.rb
class ApplicationRecord < ActiveRecord::Base
include ::TestConcern
end
# app/concerns/test_concern.rb
module TestConcern
extend ActiveSupport::Concern
class_methods do
def some_class_macro_all_models_must_have
User.some_class_instance_variable << self
end
end
included do
User.include(UserModule)
end
module UserModule
def self.included(base)
base.class_eval do
def self.some_class_instance_variable
#some_class_instance_variable ||= Set.new
end
end
end
end
end
As you can see, the class macro will actually interact with a class instance variable in the model User.
So that's why, on the included hook of the concern, I'm trying to class_eval the User model to have that class instance variable initialized. The plan was to do it like this because otherwise any model can be invoking the class macro BEFORE the class instance variable is initialized in the User model.
However, this errors out with Circular dependency detected while autoloading constant User. As far as I can understand, ApplicationRecord loads, it includes the module, the module included hooks is called, it references the User model, and so the User model is loaded, which inherits from ApplicationRecord (which didn't finish loading yet), so it causes the circular dependency.
How to avoid this circular dependency paradox, knowing that many models will invoke this class macro, and those classes might be loaded before the User class itself, so I can't even count on defining the some_class_instance_variable class method in the User model itself?
After giving it some extra thought, I decided to simply store the some_class_instance_variable in the concern itself, and since the model User also called the some_class_macro_all_models_must_have, I decided to include the UserModule when it was invoked, effectively eliminating both the circular dependency and the load order issue.
The real code is much more complex than this contrived example, but the end result was something like this:
module TestConcern
def self.some_class_instance_variable
#some_class_instance_variable ||= Set.new
end
extend ActiveSupport::Concern
class_methods do
def some_class_macro_all_models_must_have
User.include(UserModule) if self == User
TestConcern.some_class_instance_variable << self
end
end
included do
end
module UserModule
def self.included(base)
base.class_eval do
# Class macro invocations, class method and instance method definitions
end
end
end
end

Injecting custom callback in rails 3

I have a custom module which sets up a hash to be stored in my sql. As part of this it rolls a its own _changed accessor.
module MyAwesomeCustomModule
extend ActiveSupport::Concern
included do
after_save: wipe_preferences_changed
end
module ClassMethods
def blah
end
etc
end
end
and then in my model:
class MyModel < ActiveRecord::Base
include MyAwesomeCustomModule
after_save :something_that_expects_preferences_changed_to_be_available
blah
end
unfortunately, the after_save defined in the custom module runs before the one defined in the model. Is there a way to get the array of all callbacks and append to it? Is there a way to write a custom after_after_save callback? Is there a way to specify priority/ordering of after_save callbacks?
What would be a good way to resolve this race condition?
In spite of the order of model callbacks, the current design makes the module and the class very coupled.
To solve the current problem as well as improve design, you can define an expected callback in the module's method, and then the class who includes this module is free to respond it or not.
module MyAwesomeCustomModule
extend ActiveSupport::Concern
included do
after_save: wipe_preferences_changed
end
def wipe_preferences_changed
# previous logic to wipe
process_further if respond_to :process_further
end
end
class MyModel < ActiveRecord::Base
include MyAwesomeCustomModule
# Feel free to write this or not
# The content is the previous
# :something_that_expects_preferences_changed_to_be_available
def process_further
end
end
If you want to keep your original strategy (2 after_save callbacks) all you should need to do is move the include statement below the model after_save.
class MyModel < ActiveRecord::Base
after_save :something_that_expects_preferences_changed_to_be_available
include MyAwesomeCustomModule
blah
end
Callbacks are executed in the order they are defined. The include statement acts (very roughly) like you had copy and pasted the code from the module at that point, so by putting the include statement above the after_save in your model you were causing that callback to execute first.

How to Write Your Own attr_accessible Type Macro

I'm not sure if macro is even the correct term. Basically, I want to be able to configure ActiveRecord columns easily (using the familiar AR syntax) so that before_save they will always be formatted a certain way by calling an instance method.
I'd like to make all of this accessable from a mixin.
For example:
class MyClass < ActiveRecord::Base
happy_columns :col1, :col2 # I really want this type of convenient syntax
# dynamically created stuff below from a mixin.
before_save :make_col1_happy
before_save :make_col2_happy
def make_col1_happy; self.col1 += " is happy"; end
def make_col2_happy; self.col2 += " is happy"; end
end
try to extend ActiveRecord , a.e.
#in lib/happy_columns.rb
module HappyColumns
def happy_columns(cols)
cols.each do |c|
before_filter "make_#{c}_happy".to_sym
#here you could define your instance methot using define_method
define_method "make_#{c}_happy" do
#your code
end
end
include InstanceMethods
end
module InstanceMethods
#here you could define other your instancemethod
end
end
ActiveRecord::Base.extend HappyColumns
be sure of include the extensions in your load path , then you could use happy_cols in your model.
sorry if there is some mistake , for define_method look at this .
hope this could help.

Layer Supertype in ActiveRecord (Rails)

I'm developing a ruby on rails app and I want to be able to excecute a method on every AR object before each save.
I thought I'd create a layer-super-type like this:
MyObject << DomainObject << ActiveRecord::Base
and put in DomainObject a callback (before_save) with my special method (which basically strips all tags like "H1" from the string attributes of the object).
The catch is that rails is asking for the domain_object table, which I obviously don't have.
My second attempt was to monkeypatch active record, like this:
module ActiveRecord
class Base
def my_method .... end
end
end
And put that under the lib folder.
This doesnt work, it tells me that my_method is undefined.
Any ideas?
Try using an abstract class for your domain object.
class DomainObject < ActiveRecord::Base
self.abstract_class = true
# your stuff goes here
end
With an abstract class, you are creating a model which cannot have objects (cannot be instantiated) and don't have an associated table.
From reading Rails: Where to put the 'other' files from Strictly Untyped,
Files in lib are not loaded when Rails starts. Rails has overridden both Class.const_missing and Module.const_missing to dynamically load the file based on the class name. In fact, this is exactly how Rails loads your models and controllers.
so placing the file in the lib folder, it will not be run when Rails starts and won't monkey patch ActiveRecord::Base. You could place the file in config/initializers, but I think there are better alternatives.
Another method that I used at a previous job for stripping HTML tags from models is to create a plugin. We stripped a lot more than just HTML tags, but here is the HTML stripping portion:
The initializer (vendor/plugins/stripper/init.rb):
require 'active_record/stripper'
ActiveRecord::Base.class_eval do
include ActiveRecord::Stripper
end
The stripping code (vendor/plugins/stripper/lib/active_record/stripper.rb):
module ActiveRecord
module Stripper
module ClassMethods
def strip_html(*args)
opts = args.extract_options!
self.strip_html_fields = args
before_validation :strip_html
end
end
module InstanceMethods
def strip_html
self.class.strip_html_fields.each{ |field| strip_html_field(field) }
end
private
def strip_html_field(field)
clean_attribute(field, /<\/?[^>]*>/, "")
end
def clean_attribute(field, regex, replacement)
self[field].gsub!(regex, replacement) rescue nil
end
end
def self.included(receiver)
receiver.class_inheritable_accessor :strip_html_fields
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
end
Then in your MyObject class, you can selectively strip html from fields by calling:
class MyObject < ActiveRecord::Base
strip_html :first_attr, :second_attr, :etc
end
The HTML stripping plugin code already given would handle the specific use mentioned in the question. In general, to add the same code to a number of classes, including a module will do this easily without requiring everything to inherit from some common base, or adding any methods to ActiveRecord itself.
module MyBeforeSave
def self.included(base)
base.before_save :before_save_tasks
end
def before_save_tasks
puts "in module before_save tasks"
end
end
class MyModel < ActiveRecord::Base
include MyBeforeSave
end
>> m = MyModel.new
=> #<MyModel id: nil>
>> m.save
in module before_save tasks
=> true
I'd monkeypatch ActiveRecord::Base and put the file in config/initializers:
class ActiveRecord::Base
before_create :some_method
def some_method
end
end

Resources