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

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.

Related

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

missing `#` on instance variable

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

How to declare a class instance variable in Ruby?

I need a class variable which does not get inherited, so I decided to use a class instance variable. Currently I have this code:
class A
def self.symbols
history_symbols
end
private
def self.history_tables
##history_tables ||= ActiveRecord::Base.connection.tables.select do |x|
x.starts_with?(SOME_PREFIX)
end
end
def self.history_symbols
Rails.cache.fetch('history_symbols', expires_in: 10.minutes) do
history_tables.map { |x| x.sub(SOME_PREFIX, '') }
end
end
end
Can I safely transform ##history_tables to #history_tables without braking anything? Currently all my tests pass, but I am still not sure if this could be done just like that.
Since you wish to use the instance variable, you shall use instance of the class, instead of the singleton methods:
class A
def symbols
history_symbols
end
private
def history_tables
#history_tables ||= ActiveRecord::Base.connection.tables.select do |x|
x.starts_with?(SOME_PREFIX)
end
end
def history_symbols
Rails.cache.fetch('history_symbols', expires_in: 10.minutes) do
history_tables.map { |x| x.sub(SOME_PREFIX, '') }
end
end
end
A.new.symbols
instead of:
A.symbols

Initialize a Ruby class depending on what modules are included

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

how to set an instance variable from a class method?

I am trying to do a custom active record macro. But it right now seems impossible set an instance variable from within it's block.. here is what i am trying to do.
module ActiveRecord
class Base
def self.included(base)
base.class.send(:define_method, :my_macro) do |args|
# instance_variable_set for the model instance that has called this
# macro using args
end
end
end
end
i have tried class_eval, instance_eval.. but nothing seems to work or i don't how to use them.
Thanks in advance.
Edit: Let me try to explain better. I have a class method. An instance of the class calls this method. Now, this class method should instruct the instance to set an instance variable for itself.
Edit- this is how i want o use the macro
class MyModel < ActiveRecord::Base
my_macro(*args)
def after_initialize
# use the value set in the macro as #instance variable
end
end
Is this what you are thinking of:
class DynamicAdd
def add_method
self.class_eval do
attr_accessor :some_method
end
end
end
You can then do the following:
k = DynamicAdd.new
k.some_method = "hi"
should result in an undefined method error.
But,
k = DynamicAdd.new
k.add_method
k.some_method = "hi"
should work.
You can use this same format to define other types of methods besides attr_accessors as well:
class DynamicAdd
def add_method
self.class_eval do
def some_method
return "hi"
end
end
end
end
Hm.. Isn't included() a Module method? I don't think you can use that in a class like you have written. If you want to create a class method you can do
class Base
def self.my_method
end
or
class Base
class << self
def my_method
end
end
If all you want to do is to add an instance variable to an existing object, then you can use #instance_variable_set
class Base
class << self
def my_method(instance_of_base, value)
instance_of_base.instance_variable_set "#x", value
end
end
end
a = Base.new
a.class.send(:my_method, *[a,4])

Resources