I created a module which is included in a class. In the module, I am trying to define a method that is the downcased version of a class name without Filter. So ShowFilter would have a method called show that returns the class Show. I get "NoMethodError:
undefined method `show' for ShowFilter:Class"
module Filters
module Base
module ClassMethods
##filters = {}
def filter name, &block
##filters[name] = block
end
def run query = {}
query.each do |name, value|
##filters[name.to_sym].call(value) unless ##filters[name.to_sym].nil?
end
self
end
def self.extended(base)
name = base.class.name.gsub(/filter/i, '')
define_method(name.downcase.to_sym) { Kernel.const_get name }
end
end
def self.included base
base.extend ClassMethods
end
end
end
class ShowFilter
include Filters::Base
filter :name do |name|
self.show.where(:name => name)
end
end
EDIT: Example of use
class ShowController < ApplicationController
def index
ShowFilter.run params[:query]
end
end
When you define Filters::Base::ClassMethods, it evaluates self in that context so the method you'll end up defining is ClassMethods.classmethods (since the gsub won't do anything).
Like the included hook you tapped into in Base, you want to use extended in ClassMethods:
module Filters
module Base
module ClassMethods
##filters = {}
def filter name, &block
##filters[name] = block
end
def run query = {}
query.each do |name, value|
##filters[name.to_sym].call(value) unless ##filters[name.to_sym].nil?
end
Object.const_get(self.to_s.gsub('Filter', ''))
end
def self.extended(base)
define_method(base.to_s.downcase.gsub('filter', '').to_sym) do
Object.const_get(self.to_s.gsub('Filter', ''))
end
end
end
def self.included base
base.extend ClassMethods
end
end
end
class ShowFilter
include Filters::Base
filter :title do |title|
self.show.where(:title => title)
end
end
Related
I want to create a DSL which stores various blocks, and then can call them. I want this to be a reusable module that I can include in multiple classes.
I figured out a way to do this using class variables, but rubocop complains and says that I should use class instance variables instead. I can't figure out a way to do this though. Is it possible?
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
def run_fixers
ClassMethods.class_variable_get(:##fixers).each(&:call)
end
module ClassMethods
def fix(_name, &block)
##fixers ||= []
##fixers << block
end
end
end
class MyClass
include MyModule
def initialize
run_fixers
end
fix 'test' do
puts 'testing'
end
end
Rubocop complains about ##class_variable style. Class variables usage can be refactored into using instance variable of a class:
class SomeClass
#some_variable ||= []
end
instead of
class SomeClass
##some_variable ||= []
end
You can define instance variable of a class in any place where self equals to the class object. For example, in a class method:
module MyModule
def self.included(base)
base.extend(ClassMethods)
end
def run_fixers
ClassMethods.fixers.each(&:call)
end
module ClassMethods
def self.fixers
#fixers ||= []
end
def fix(_name, &block)
ClassMethods.fixers << block
end
end
end
This my implementation to developing way to run code before all method in your model
The call "before_hook :months_used" method need to be on bottom of class to the ExecutionHooks can get the instance_method loaded in the module. I would like to load the instance methods on top
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_used
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end
You can do this with prepend. prepend is like include in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.
This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super if it wants to call the base class).
This allows you to write a hooks module like so:
module Hooks
def before(*method_names)
to_prepend = Module.new do
method_names.each do |name|
define_method(name) do |*args, &block|
puts "before #{name}"
super(*args,&block)
end
end
end
prepend to_prepend
end
end
class Example
extend Hooks
before :foo, :bar
def foo
puts "in foo"
end
def bar
puts "in bar"
end
end
In real use you would probably want to stash that module somewhere so that each call to before doesn't create a new module but that is just an inplementation detail
#rathrio This is my implementation using method_added that you talked. Thanks
module ExecutionHooks
def validation
p "works1"
end
def self.included(base)
base.send :extend, ClassMethods
end
end
module ClassMethods
attr_writer :hooked
def hooked
#hooked ||= []
end
def method_added(method)
return if #hooks.nil?
return unless #hooks.include?(method)
m = self.instance_method(method)
unless hooked.include?(method)
hooked << method
define_method(method) do |*args, &block|
validation
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
end
def hooks
#hooks ||= []
end
end
end
class BalanceChart < BalanceFind
include ExecutionHooks
before_hook :months_data, :months_used, :debits_amount, :test
def test
"test"
end
end
Instead of redefining the method when calling before_hook, you could override the method_added hook to prepend your before hooks to a method right after it was defined. This way your before_hook calls can be (actually, must be) placed at the top of the class definition.
I'm building a small script where I'd need to implement a Mongoid Document like class, where I include my base module and then I can build a class which looks like:
class MyClass
include MyBaseModule
field :some_field, :attr => 'attributes'
end
This is my last try:
module Model
def initialize(keys = {})
puts ##keys
end
def method_missing sym, *args
if sym =~ /^(\w+)=$/
if ##keys.has_key?($1)
##keys[$1.to_sym] = args[0]
else
nil
end
else
if ##keys.has_key?($1)
##keys[sym.to_sym]
else
nil
end
end
end
def inspect
puts "#<#{self.class} #keys=#{##keys.each {|k,v| "#{k} => #{v}"}}>"
end
def self.included(base)
base.extend(ClassMethods)
end
def save
##keys.each do |k, v|
SimpleMongo::connection.collection.insert({k => v})
end
end
module ClassMethods
def field(name, args)
if ##keys.nil?
##keys = {}
end
##keys[name.to_sym] = default_value
end
end
end
Mongoid documents look like this:
class StoredFile
include Mongoid::Document
field :name, type: String
field :description, type: String
field :password, type: String
field :public, type: Boolean
field :shortlink, type: String
mount_uploader :stored_file, StoredFileUploader
before_save :gen_shortlink
before_save :hash_password
belongs_to :user
def gen_shortlink
self.shortlink = rand(36**10).to_s(36)
end
def public?
self.public
end
def hash_password
require 'bcrypt'
self.password = BCrypt::Password.create(self.password).to_s
end
def check_pass(password)
BCrypt::Password.new(self.password) == password
end
end
It doesn't works because the ##keys variable inside ClassMethods isn't available anywhere outside that module. What would be the easiest way to implement this? Thanks!
The easiest way to implement it would be to have a class variable getter.
module Model
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def keys
#keys ||= {}
end
def field(name, opts)
#keys ||= {}
#keys[name] = opts
end
end
def initialize(attributes)
# stuff
puts self.class.keys
end
end
I currently have a superclass which has a function that I want all the subclass to call within each of its function. The function is supposed to behave like a before_filter function in rails but I am not sure on how to go about implementing before_filter. Here is an example
class Superclass
def before_each_method
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
This is one way to do it:
class Superclass
def before_each_method name
p [:before_method, name]
end
def self.method_added name
return if #__last_methods_added && #__last_methods_added.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
#__last_methods_added = [name, with, without]
define_method with do |*args, &block|
before_each_method name
send without, *args, &block
end
alias_method without, name
alias_method name, with
#__last_methods_added = nil
end
end
class SubclassA < Superclass
def my_new_method
p :my_new_method
end
def my_new_other_method
p :my_new_other_method
end
end
SubclassA.new.my_new_method
SubclassA.new.my_new_other_method
This will create a wrapper method using the alias_method_chaining method as soon as the method you'd like to wrap is defined in the subclass.
This is my solution:
require 'active_support/all'
module BeforeEach
extend ActiveSupport::Concern
module InstanceMethods
def before_each
raise NotImplementedError('Please define before_each method')
end
end
module ClassMethods
def method_added(method)
method = method.to_s.gsub(/_with(out)?_before$/, '')
with_method, without_method = "#{method}_with_before", "#{method}_without_before"
return if method == 'before_each' or method_defined?(with_method)
define_method(with_method) do |*args, &block|
before_each
send(without_method, *args, &block)
end
alias_method_chain(method, :before)
end
end
end
To use it, just include BeforeEach into your class like so:
class Superclass
include BeforeEach
def before_each
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
Subclass.new.my_method
# => Before Method
Hopefully this will work for you!
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_data, :months_used, :debits_amount
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end
I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class