Adding to ActiveRecord::ConnectionAdapters::MysqlAdapter - ruby-on-rails

For general knowledge and entertainment purposes I am trying to add some behavoiur to Rails. What I am looking for is simply to run a Mysql "EXPLAIN" statement before every select statement that Rails runs. I think this should work ok but I am getting the error:
/usr/lib/ruby/gems/1.8/gems/activesupport-2.1.0/lib/active_support/core_ext/module/aliasing.rb:32:in alias_method': undefined methodselect_with_explain' for class `ActiveRecord::ConnectionAdapters::MysqlAdapter' (NameError)
This class is located in the initializers dir. Here is the code:
module Explanifier
def self.included(base)
base.class_eval do
extend ClassMethods
alias_method_chain :select, :explain
end
end
module ClassMethods
def select_with_explain(sql, name = nil)
puts "testing!!!"
execute('EXPLAIN ' + sql, name)
select_without_explain(sql, name)
end
end
end
class ActiveRecord::ConnectionAdapters::MysqlAdapter
include Explanifier
end
Can someone explain what I am missing here?

put your alias_method_chain in your ClassMethods module. because you define the method like classMethod and alias a InstanceMethod
module Explanifier
def self.included(base)
base.class_eval do
extend ClassMethods
end
end
module ClassMethods
def select_with_explain(sql, name = nil)
puts "testing!!!"
execute('EXPLAIN ' + sql, name)
select_without_explain(sql, name)
end
alias_method_chain :select, :explain
end
end

Just stumbled upon this.
You can't call it from within base.class_eval and you obviously can't put it inside the ClassMethods module. (Since there is no select method in ClassMethods module, is there?)
The way to go is to do this:
def self.included(base)
base.extend ClassMethods
class << base
alias_method_chain :select, :explain
end
end
You just need to access it through a ghost class over there. Hope it helps.

Related

how to find out in which class i currently include my module?

how do we find out in which class we are currently including a module? (coming from rails-background where we do the has_* style modules)
class Foo
has_likes
end
module HasLikes
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def has_likes(options = {})
end
end
module ContentMethods
def self.included(base)
base.extend ClassMethods
end
# ?????
# how do we get Foo here?
end
end
First of all, as #Jordan said in comments, it smells as a design flaw.
Secondary, inside ContentMethods declaration, it’s definitely impossible, since it is always included after been declared.
In general, one might do it afterwards with ObjectSpace#each_object:
module HasLikes
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def has_likes(options = {})
end
end
module ContentMethods
def self.included(base)
base.extend ClassMethods
end
end
end
class Foo
include ::HasLikes::ContentMethods
has_likes
end
ObjectSpace.each_object(Class) do |x|
p(x) if x <= ::HasLikes::ContentMethods
end
#⇒ Foo
But please do not do that at home or school.

Accessing other methods in a Ruby module

I am writing my first Rails gem, which adds a method to ActiveRecord. I can't seem to figure out a simple way to call other methods from within the method I am adding to ActiveRecord. Is there a pattern for this I should be using?
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
some_utility_method
end
end
def some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)
Once you've included MyModule, ActiveRecord::Base will have my_class_method as a class method (equivalently, an instance method of the Class object ActiveRecord::Base), and some_utility_method as an instance method.
So, inside my_class_method, self is the Class ActiveRecord::Base, not an instance of that class; it does not have some_utility_method as an available method
Edit:
If you want a utility method private to the Module, you could do it like this:
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def my_class_method
# This doesn't work
MyModule::some_utility_method
end
end
def self.some_utility_method
# Do something useful
end
end
ActiveRecord::Base.send(:include, MyModule)

How do I include an instance method inside a before_save callback in a plugin?

I'm creating a plugin and am having a hard time defining a before_save filter that calls an instance method I've just defined. Here's a quick sample:
module ValidatesAndFormatsPhones
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def validates_and_formats_phones(field_names = [:phone])
send :include, InstanceMethods
# the following variations on calls to :format_phone_fields fail
before_save send(:format_phone_fields, field_names)
before_save format_phone_fields(field_names)
before_save lambda { send(:format_phone_fields, field_names) }
# EACH OF THE ABOVE RETURNS 'undefined_method :format_phone_fields'
end
end
module InstanceMethods
def format_phone_fields(fields = [:phone], *args)
do stuff...
end
end
end
ActiveRecord::Base.send :include, ValidatesAndFormatsPhones
I guess the question is, how do I change the context to the instance, instead of the class?
I'd prefer to call the instance method because the class shouldn't really have a method called 'format_phone_fields' but the instance should.
Thanks!
Include your method at the right moment: when you're extending the base class:
module ValidatesAndFormatsPhones
def self.included(base)
base.send :extend, ClassMethods
base.send :include, InstanceMethods
end
module ClassMethods
def validates_and_formats_phones(field_names = [:phone])
before_save {|r| r.format_phone_fields(field_names)}
end
end
module InstanceMethods
def format_phone_fields(fields = [:phone], *args)
# do stuff...
end
end
end
ActiveRecord::Base.send :include, ValidatesAndFormatsPhones
I haven't run the code, but it should work. I've done similar things often enough.
as you are using callback macros, you can only pass a symbol for the method you want to run, passing arguments is not possible. the 'workaround' from the rails documentation is to use a 'method string' that gets evaluated in the right context:
before_save 'self.format_phone_fields(....)'
another possibility: store your field names as a class variable and access that one in your instance, then you can use before_save :format_phone_fields

including Modules in controller

I have done a module in lib directory in ruby on rails application
its like
module Select
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def select_for(object_name, options={})
#does some operation
self.send(:include, Selector::InstanceMethods)
end
end
I called this in a controller like
include Selector
select_for :organization, :submenu => :general
but I want to call this in a function
i.e
def select
#Call the module here
end
Let's clarify: You have a method defined in a module, and you want that method to be used in an instance method.
class MyController < ApplicationController
include Select
# You used to call this in the class scope, we're going to move it to
# An instance scope.
#
# select_for :organization, :submenu => :general
def show # Or any action
# Now we're using this inside an instance method.
#
select_for :organization, :submenu => :general
end
end
I'm going to change your module slightly. This uses include instead of extend. extend is for adding class methods, and include it for adding instance methods:
module Select
def self.included(base)
base.class_eval do
include InstanceMethods
end
end
module InstanceMethods
def select_for(object_name, options={})
# Does some operation
self.send(:include, Selector::InstanceMethods)
end
end
end
That will give you an instance method. If you want both instance and class methods, you just add the ClassMethods module, and use extend instead of include:
module Select
def self.included(base)
base.class_eval do
include InstanceMethods
extend ClassMethods
end
end
module InstanceMethods
def select_for(object_name, options={})
# Does some operation
self.send(:include, Selector::InstanceMethods)
end
end
module ClassMethods
def a_class_method
end
end
end
Does that clear things up? In your example you defined a module as Select but included Selector in your controller...I just used Select in my code.

Rails - alias_method_chain with a 'attribute=' method

I'd like to 'add on' some code on a model's method via a module, when it is included. I think I should use alias_method_chain, but I don't know how to use it, since my 'aliased method' is one of those methods ending on the '=' sign:
class MyModel < ActiveRecord::Base
def foo=(value)
... do stuff with value
end
end
So this is what my module looks right now:
module MyModule
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain 'foo=', :bar
end
end
module InstanceMethods
def foo=_with_bar(value) # ERROR HERE
... do more stuff with value
end
end
end
I get an error on the function definition. How do get around this?
alias_method_chain is a simple, two-line method:
def alias_method_chain( target, feature )
alias_method "#{target}_without_#{feature}", target
alias_method target, "#{target}_with_#{feature}"
end
I think the answer you want is to simply make the two alias_method calls yourself in this case:
alias_method :foo_without_bar=, :foo=
alias_method :foo=, :foo_with_bar=
And you would define your method like so:
def foo_with_bar=(value)
...
end
Ruby symbols process the trailing = and ? of method names without a problem.

Resources