missing `#` on instance variable - ruby-on-rails

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

Related

Ho to access to the class-level instance variable in ruby

I want to access the attr declared in the class scope from the instance. I want to add some helper methods to the class that other classes can use it, like all methods in the active record for example validates and others. Is this possible to do ?
class SomeClass
def initialize()
end
def do_something
helper_methods
# hot ot acces here to the #helper_methods class-level instance variable
end
class << self
attr_accessor :helper_methods
def some_helper_method(name, &block)
add_helper_method(name, &block)
end
def add_helper_method(name, options = {}, &block)
#helper_methods ||= {}
#helper_methods[name] = {
attr_or_block: block_given? ? block : name,
options: options,
}
end
end
end
class SecondClass < SomeClass
some_helper_method :name
end
SecondClass.new.do_something
# should retunrs [:name]
I would expect
def do_something
self.class.helper_methods
end
to return the value from the class variable.

Can't access to attr_accessor in Concern Ruby on Rails

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

Use define_method in parent class with dynamic content

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.

How to access class method from instance method in ruby on rails non activerecord model

I have a non activerecord rails model:
class Document
attr_accessor :a, :b
include ActiveModel::Model
def find(id)
initialize_parameters(id)
end
def save
...
end
def update
...
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
end
In order to find the Document, I can use:
Document.new.find(3)
So, to get it directly I changed the find method to
def self.find(id)
initialize_parameters(id)
end
And I get the following error when I run
Document.find(3)
undefined method `initialize_parameters' for Document:Class
How can I make this work?
You can't access an instance method from a class method that way, to do it you should instantiate the class you're working in (self) and access that method, like:
def self.find(id)
self.new.initialize_parameters(id)
end
But as you're defining initialize_parameters as a private method, then the way to access to it is by using send, to reach that method and pass the id argument:
def self.find(id)
self.new.send(:initialize_parameters, id)
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
Or just by updating initialize_parameters as a class method, and removing the private keyword, that wouldn't be needed anymore.
This:
class Document
attr_accessor :a, :b
def self.find(id)
initialize_parameters(id)
end
end
Is not trying to "access class method from instance method" as your title states. It is trying to access a (non-existent) class method from a class method.
Everything Sebastian said is spot on.
However, I guess I would ask: 'What are you really trying to do?' Why do you have initialize_parameters when ruby already gives you initialize that you can override to your heart's content? IMO, it should look something more like:
class Document
attr_accessor :a, :b, :id
class << self
def find(id)
new(id).find
end
end
def initialize(id)
#a = 1
#b = 2
#id = id
end
def find
# if you want you can:
call_a_private_method
end
private
def call_a_private_method
puts id
end
end

Rails lib/module with 'class Class' and custom attr_accessor

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

Resources