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
Related
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.
I want to use an instance variable throughout a class's methods. Unfortunately, the methods are all class (not instance) methods, so the classic #my_data ||= [1,2,3] doesn't work, since every call to the class's class-methods will reuse the same data forever:
class MyThing
class << self
def do_something
do_something_else
end
private
def my_data
#my_data ||= Time.now
end
def do_something_else
puts my_data
end
end
end
MyThing.do_something # puts the current time
MyThing.do_something # puts the same time as above (not what I want)
One way around it is to declare the variable, then set it to nil at the end of the method like this:
class MyThing
class << self
def do_something
#my_data = my_data
do_something_else
#my_data = nil
end
private
def my_data
Time.now
end
def do_something_else
puts #my_data || my_data
end
end
end
MyThing.do_something # puts the current time
MyThing.do_something # puts the new current time (yay)
However, setting the data and destroying it at the start and end of the method seems messy.
Is there a cleaner way to say "set (#my_data = Time.now) until the end of this method"?
It's soluiton, if I understand you correctly.
class MyThing
class << self
def do_something
do_something_else
end
private
def my_data
#my_data = Time.now
end
def do_something_else
puts my_data
end
end
end
The name says it all "instance variable" is available as long as the instance lives. In your case that instance is the MyThing class, meaning it doesn't reset automatically. Classes stay loaded as long as your program runs, so it will never reset.
The simplest solution for the given scenario is to use method arguments.
class MyThing
class << self
def do_something
do_something_else(my_data)
end
private
def do_something_else(my_data)
puts my_data || Time.now
end
end
end
The other option you already discovered, that is, setting an instance variable and then resetting it.
Another alternative is moving the thing your doing into an class instance. The disadvantage here is that this solution produces more overhead code. However it does keep the MyThing class cleaner, since you can move helper methods into this new class.
class MyThing
class DataObject
attr_reader :data
def initialize(data)
#data = data || Time.now
end
def do_something_else
puts data
end
end
class << self
def do_something
DataObject.new(my_data).do_something_else
end
end
end
If possible you could also create a MyThing instance instead of an DataObject instance, but I assumed the MyThing class already has another purpose.
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
class Callbacks
def self.before_actions
#before_actions ||= []
end
def self.before_action(callback)
before_actions << callback
end
def self.inherited(child_class)
before_actions.each { |f| child_class.before_actions << f }
after_actions.each { |f| child_class.after_actions << f }
end
def execute(action)
self.class.before_actions.each { |callback| send(callback) }
send(action)
self.class.after_actions.each { |callback| send(callback) }
end
end
class Test < Callbacks
before_action :hello
def heraks
puts "heraks"
end
private
def hello
puts "Hello"
end
end
Test.new.execute(:heraks)
This works, but if I write #before_actions = [], without ||, then it doesn't work.
If I change how callbacks are stored to this:
##callbacks = []
def self.before_actions(action)
##callbacks << action
end
it works.
I used byebug and checked that Test.before_actions == [] and before_action :hello don't add to the array.
What is the difference between them? Is this a bug?
When you use a class instance variable (#before_actions) and self.before_actions uses ||= your code works; fine.
When self.before_actions uses = instead of ||= your code fails because every time you call before_actions it resets #before_actions to []. No callback will stay defined long enough to do anything.
Your version of your code that uses a class variable (##callbacks) sort of works because you're initializing ##callbacks only once outside the accessor. However, you'll have problems as soon as you have two subclasses of Callbacks: Callbacks and its subclasses will all share the same ##callbacks, so you won't be able to have different subclasses with different callbacks.
I have a lots of call to something like this :
User.active[0..5]
Which call :
class User
def active
(an ActiveRelation)
end
end
I am trying to do something like this for performance reasons :
class User
def active[limit]
(an ActiveRelation).limit(limit.to_a.size)
end
end
Unfortunately it doesn't work, any ideas to implement this ?
== EDIT
More cleaner :
class RelationWithLimit < ActiveRecord::Relation
def [] selector
case selector
when Integer
self.offset(selector).limit(1)
when Range
self.offset(selector.to_a[0]).limit(selector.to_a.size)
end
end
end
class ActiveRecord::Base
private
def self.relation #:nodoc:
#relation ||= RelationWithLimit.new(self, arel_table)
finder_needs_type_condition? ? #relation.where(type_condition) : #relation
end
end
You could have your own special subclass of ActiveRelation
class UserReturnRelation < ActiveRecord::Relation
def [] lim
self.limit lim
end
end
class User
def active
# Without knowing exactly what relation you are using
# One way to instantiate the UserReturnRelation for just this call
UserReturnRelation.new(self, arel_table).where("state = active")
end
end
Then User.active[5] should work as expected.
EDIT: Added instantiation info. You may want to look at Base#scoped and Base#relation for more info
Can you try it as params instead of array-indices? eg:
class User
def active(the_limit)
(an ActiveRelation).limit(the_limit)
end
end
User.active(5)
(note: not tested on any actual ActiveRelations...)
You can do it like this:
class User
def active
Limiter.new((an ActiveRelation))
end
class Limiter
def initialize(relation)
#relation = relation
end
def method_missing(method, *arguments, &block)
#relation.send(method, *arguments, &block)
end
def respond_to?(method, include_private = false)
#relation.respond_to?(method, include_private) || super
end
def [](value)
offset = value.to_a.first
limit = value.to_a.last - offset
#relation.offset(offset).limit(limit)
end
end
end
Well, you are defining the method in the wrong class. User.active[0..5] calls the class method active in User and the method [] in whatever class User.active is returning, I'll assume that it is returning an array of users, and Array has already defined the method [] so no worries about that.
You may be getting confused thinking that brackets are some kind of parenthesis for passing arguments to a function while they're not. Try this:
class User
class << self
def [](values)
self.find(values)
end
end
end
So, if you wanna use find with an arrays of ids, you may just use User[1,2,3].