I want to make my own attr_accessor like this:
class Class
def attr_accessor_with_onchange_callback(*args, &block)
raise 'Callback block is required' unless block
args.each do |arg|
attr_name = arg.to_s
define_method(attr_name) do
self.instance_variable_get("##{attr_name}")
end
define_method("#{attr_name}=") do |argument|
old_value = self.instance_variable_get("##{attr_name}")
if argument != old_value
self.instance_variable_set("##{attr_name}", argument)
self.instance_exec(attr_name, argument, old_value, &block)
end
end
end
end
end
It works if I put this definition in config/enviroment.rb before app initialization.
class MyCLass < ActiveRecord::Base
attr_accessor_with_onchange_callback :some_attr do |attr_name, value, old_value|
end
But I think it should be inside lib/ folder. If I put this
module ModelHelpers
class Class
def attr_accessor_with_onchange_callback(*args, &block)
raise 'Callback block is required' unless block
args.each do |arg|
attr_name = arg.to_s
define_method(attr_name) do
self.instance_variable_get("##{attr_name}")
end
define_method("#{attr_name}=") do |argument|
old_value = self.instance_variable_get("##{attr_name}")
if argument != old_value
self.instance_variable_set("##{attr_name}", argument)
self.instance_exec(attr_name, argument, old_value, &block)
end
end
end
end
end
end
to lib/model_helpers.rb and this
require 'model_helpers'
class MyCLass < ActiveRecord::Base
include ModelHelpers
attr_accessor_with_onchange_callback :some_attr do |attr_name, value, old_value|
end
to my_class.rb then I get an error: undefined method attr_accessor_with_onchange_callback.
What am I doing wrong?
Try to define your method attr_accessor_with_onchange_callback directly in ModelHelpers, without class Class. And use extend keyword instead include inside class defenition. Like this:
module ModelHelpers
def attr_accessor_with_onchange_callback(*args, &block)
...
require 'model_helpers'
class MyCLass < ActiveRecord::Base
extend ModelHelpers
Here is my example:
module ModelHelpers
def my_method
puts 'ModelHelpers::my_method called.'
puts "self is #{self}"
end
end
class MyCLass
extend ModelHelpers
my_method
end
And output is:
> ruby custom_method_inside_class.rb
ModelHelpers::my_method called.
self is MyCLass
Related
I want to register the class method in concern and access to attr_accessor, but it doesn't work. This is my sample code. Please help me how can I do this. Thank you so much!
app/controllers/concerns/foobar_concern.rb
module FoobarConcern
extend ActiveSupport::Concern
included do
class << self
attr_accessor :foo
end
end
class_methods do
def test_method(bar)
self.foo = bar
end
end
end
app/controllers/foobar_controller.rb
class FoobarController < ApplicationController
include FoobarConcern
test_method 'Just test'
def index
self.foo => NoMethodError: undefined method "foo"
foo => NameError: undefined local variable or method "foo"
end
end
Just delegate required methods to the class like this
module FoobarConcern
extend ActiveSupport::Concern
included do
delegate :foo, :foo=, to: :class
class << self
attr_accessor :foo
end
end
end
The issue is that you're defining a method at the class level (FoobarController.foo) but calling it on an instance of the class (FoobarController.new.foo).
One option is to call the foo method on the class instead:
def index
self.class.foo
end
You can also define an accessor method for instances of the class like:
module FoobarConcern
extend ActiveSupport::Concern
included do
class << self
attr_accessor :foo
end
end
class_methods do
def test_method(bar)
self.foo = bar
end
end
# -- NEW ---
# This `foo` method is defined for instances of the class and calls the class method.
def foo
self.class.foo
end
end
I'm trying to achieve something that is for sure possible but I'm not able to put it into to find it from the docuemntation.
In a nutshell, I would like to define methods dynamically:
Initial point:
class Foo < Bar
def baz
RecordLoader.for(Baz).load(object.baz_id)
end
def qux
RecordLoader.for(Quz).load(object.qux_id)
end
end
class Bar
end
I would like to be able to change it to
class Foo < Bar
record_loader_for :baz
record_loader_for :qux
end
class Bar
def self.record_loader_for(attribute)
define_method attribute.to_s do
# What is missing here?
end
end
end
I'm trying to figure out how I can use the value of attribute to write something like
RecordLoader.for(attribute.to_s.classify.constantize). # <- attribute is local to the class
.load(object.send("#{attribute.to_s}_id")) # <- object is local to the instance
You can go with class_eval and generate your method into string:
def self.record_loader_for(attribute)
class_eval <<~RUBY, __FILE__ , __LINE__ + 1
def #{attribute}
RecordLoader.for(#{attribute.to_s.classify}).load(#{attribute}_id)
end
RUBY
end
but in fact, define_method should work too, ruby will save closure from the method call:
require 'active_support'
require 'active_support/core_ext'
require 'ostruct'
class RecordLoader
def self.for(cls)
new(cls)
end
def initialize(cls)
#cls = cls
end
def load(id)
puts "loading #{#cls} id #{id}"
end
end
class Baz; end
class Bar
def object
OpenStruct.new(baz_id: 123, qux_id:321)
end
def self.record_loader_for(attribute)
define_method attribute.to_s do
RecordLoader.for(attribute.to_s.classify.constantize).
load(object.send("#{attribute.to_s}_id"))
end
end
end
class Foo < Bar
record_loader_for :baz
record_loader_for :qux
end
Foo.new.baz
class_eval is slower to define method, but resulting method executes faster and does not keep references to original closure context, define_method is the opposite - defines faster, but method runs slower.
I have a Ruby class with an initialize method:
def initialize(params)
#foo = private_method(params || {})
end
Later in the same class, I see the following:
def new_method_for(user)
foo.each { |f| other_method(f) }
end
Why is the # missing from in front of foo in other_method? When I put a binding.pry in before foo.each..., both foo and #foo are defined.
Check for the class that contains the new_method_for(user) method, you should see an attr_reader, attr_writer or both represented by attr_accessor
So it should look like this:
class SomeClass
attr_accessor :foo
end
I'm creating a module that extends the functionality of an ActiveRecord model.
Here's my initial setup.
My class:
class MyClass < ActiveRecord::Base
is_my_modiable
end
And Module:
module MyMod
def self.is_my_modiable
class_eval do
def new_method
self.mod = true
self.save!
end
end
end
end
ActiveRecord::Base(extend,MyMod)
What I would like to do now is extend the functionality of the new_method by passing in a block. Something like this:
class MyClass < ActiveRecord::Base
is_my_modiable do
self.something_special
end
end
module MyMod
def self.is_my_modiable
class_eval do
def new_method
yield if block_given?
self.mod = true
self.save!
end
end
end
end
This doesn't work though, and it makes sense. In the class_eval, the new_method isn't being executed, just defined, and thus the yield statement wouldn't get executed until the method actually gets called.
I've tried to assign the block to a class variable within the class_eval, and then call that class variable within the method, but the block was being called on all is_my_modiable models, even if they didn't pass a block into the method.
I might just override the method to get the same effect, but I'm hoping there is a more elegant way.
If I understood you correctly, you can solve this by saving passed block to an instance variable on class object and then evaling that in instance methods.
bl.call won't do here, because it will execute in the original context (that of a class) and you need to execute it in scope of this current instance.
module MyMod
def is_my_modiable(&block)
class_eval do
#stored_block = block # back up block
def new_method
bl = self.class.instance_variable_get(:#stored_block) # get from class and execute
instance_eval(&bl) if bl
self.mod = true
self.save!
end
end
end
end
class MyClass
extend MyMod
is_my_modiable do
puts "in my modiable block"
self.something_special
end
def something_special
puts "in something special"
end
attr_accessor :mod
def save!; end
end
MyClass.new.new_method
# >> in my modiable block
# >> in something special
You can do this by assigning the block as a method parameter:
module MyMod
def self.is_my_modiable
class_eval do
def new_method(&block)
block.call if block
self.mod = true
self.save!
end
end
end
end
I'm wondering what is the best way to initialize a class in ruby depending on modules included. Let me give you an example:
class BaseSearch
def initialize query, options
#page = options[:page]
#...
end
end
class EventSearch < BaseSearch
include Search::Geolocalisable
def initialize query, options
end
end
class GroupSearch < BaseSearch
include Search::Geolocalisable
def initialize query, options
end
end
module Search::Geolocalisable
extend ActiveSupport::Concern
included do
attr_accessor :where, :user_location #...
end
end
What I don't want, is having to initialize the :where and :user_location variables on each class that include the geolocalisable module.
Currently, I just define methods like def geolocalisable?; true; end in my modules, and then, I initialize these attributes (added by the module) in the base class:
class BaseSearch
def initialize query, options
#page = options[:page]
#...
if geolocalisable?
#where = query[:where]
end
end
end
class EventSearch < BaseSearch
#...
def initialize query, options
#...
super query, options
end
end
Is there better solutions? I hope so!
Why not override initialize in the module? You could do
class BaseSearch
def initialize query
puts "base initialize"
end
end
module Geo
def initialize query
super
puts "module initialize"
end
end
class Subclass < BaseSearch
include Geo
def initialize query
super
puts "subclass initialize"
end
end
Subclass.new('foo') #=>
base initialize
module initialize
subclass initialize
Obviously this does require everything that includes your modules to have an initialize with a similar signature or weird stuff might happen
See this code :
module Search::Geolocalisable
def self.included(base)
base.class_eval do
attr_accessor :where, :user_location #...
end
end
end
class EventSearch < BaseSearch
include Search::Geolocalisable
end