how to find out in which class i currently include my module? - ruby-on-rails

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.

Related

Ruby Module's included hook instead of regular extending?

There is a general way of adding class methods from Module via its included hook, and following extending base class with ClassMethods submodule. This way is described in book "Metaprogramming Ruby 2: Program Like the Ruby Pros". Here is an example from there:
module CheckedAttributes
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def attr_checked(attribute, &validation)
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("##{attribute}", value)
end
define_method attribute do
instance_variable_get "##{attribute}"
end
end
end
end
class Person
include CheckedAttributes
attr_checked :age do |v|
v >= 18
end
end
But what is the reason of including the almost empty module first, and then extending its includer with one more module? Why not extend the class right the way with target module itself?
module CheckedAttributes
def attr_checked(attribute, &validation)
define_method "#{attribute}=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("##{attribute}", value)
end
define_method attribute do
instance_variable_get "##{attribute}"
end
end
end
class Person
extend CheckedAttributes
attr_checked :age do |v|
v >= 18
end
end
Is code above totally equal to initial example from this book? Or there are any pitfalls?
I have no idea where you took this code from, but this pattern involving ClassMethods is normally used in the cases when you want to alter both class and eigenclass to avoid the necessity to call both include Foo and extend Bar.
module Named
def self.included(base)
base.extend ClassMethods
end
def describe
"Person is: #{name}"
end
module ClassMethods
def name!
define_method "name=" do |value|
raise 'Invalid attribute' unless validation.call(value)
instance_variable_set("#name", value)
end
define_method "name" do
instance_variable_get "#name"
end
end
end
end
class Person
include Named
name!
end
p = Person.new
p.name = "Trump"
p.describe #⇒ "Person is: Trump"
In your example, it makes zero sense.

Ruby mixin best practice

New to Ruby\Rails, shame on me :(
I'm developing an engine for personal use (simple admin panel). What I want, is to be able to config my main app's models, like this:
class User < ActiveRecord::Base
include Entropy::Configurable
entropy_config do
form_caption 'Editing user'
end
end
And then in engine's templates do this:
<h1><%= #object.entropy_config :form_caption %></h1>
Engine's module:
module Entropy
module Configurable
def self.included(base)
## to call entropy_config in model class
base.send :extend, ClassMethods
end
def entropy_config(arg)
## ... I'm missing this part
end
module ClassMethods
##config = { ... }
def entropy_config (&block)
class_eval &block
end
def form_caption(arg)
// skipping class identification
##config[:user][:form_caption] = arg
end
end
end
end
The problem is that I can not get access to ##config from Configurable module, actually when I call entropy_config on #object. What I'm doing wrong?
First of all you've doing it wrong. Rails is on of the frameworks that pushed a lot on the MVC architecture. Having your model know about form captions is wrong. For that I would use the rails i18n gem. For the sake of the argument here's some untested code that will probably answer your question:
module Entropy
module Configurable
def self.included(base)
## to call entropy_config in model class
base.send :extend, ClassMethods
end
def entropy_config(key)
self.class.config[:user][key]
end
module ClassMethods
cattr_accessor :config
def entropy_config (&block)
self.config ||= {}
class_eval &block
end
def form_caption(arg)
// skipping class identification
self.config[:user][:form_caption] = arg
end
end
end
end
see http://apidock.com/rails/Class/cattr_accessor for more info

can I insert a cattr_accessor_with_default class method in a module

Currently I have in my Event model
class Event < ActiveRecord::Base
def self.cattr_accessor_with_default(name, value = nil)
cattr_accessor name
self.send("#{name}=", value) if value
end
cattr_accessor_with_default :fixed_start_end_times, false
...
As I defined a Scheduler module
class Event < ActiveRecord::Base
include Scheduler
It seems I cannot move the cattr_accessor_with_default method into this Scheduler module
module Scheduler
def self.included(base)
base.send :include, InstanceMethods
base.extend ClassMethods
end
module ClassMethods
def cattr_accessor_with_default(name, value = nil)
cattr_accessor name
self.send("#{name}=", value) if value
end
cattr_accessor_with_default :fixed_start_end_times, false
...
raising an error :
undefined method `cattr_accessor_with_default' for Scheduler::ClassMethods:Module
(NoMethodError)
I tried also to use a better Rails3 way
module Scheduler
def self.included(base)
base.send :include, InstanceMethods
base.extend ClassMethods
end
module ClassMethods
class_attribute :fixed_start_end_times
self.fixed_start_end_times = false
same error
undefined method `class_attribute' for Scheduler::ClassMethods:Module
I think you put cattr_accessor_with_default at wrong place. Try following code:
module Scheduler
def self.included(base)
base.send :include, InstanceMethods
base.extend ClassMethods
base.class_eval do
cattr_accessor_with_default :fixed_start_end_times, false
end
end
module ClassMethods
def cattr_accessor_with_default(name, value = nil)
cattr_accessor name
self.send("#{name}=", value) if value
end
...
This way cattr_accessor_with_default is sent to the including class, e.g. class Event, and cattr_accessor_with_default has been added into that class as a class method.
First I created :
#lib/scheduler.rb
module Scheduler
extend ActiveSupport::Concern
included do
class_attribute :fixed_start_end_times
self.fixed_start_end_times = false
end
# adding instance methods here
def foo
"foo"
end
# adding static(class) methods here
module ClassMethods
def starting_on(start_date)
where("CAST( starts_at AS TIME) = ?", Event.db_time(start_date))
end
end
end
# including the extension
ActiveRecord::Base.send(:include, Scheduler)
Then I added :
in #config/initializers/scheduler.rb
require "scheduler"
et voilà ...

Adding to ActiveRecord::ConnectionAdapters::MysqlAdapter

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.

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.

Resources