To become thread-safe, I need to refactor the Class instance variables in my code.
original
# frozen_string_literal: true
class Score < ApplicationRecord
def self.init_score
return #init_score if #init_score.present?
#init_score = new(multiplier: 4.0)
#init_score.readonly!
#init_score
end
end
Score.init_score
refactored
# frozen_string_literal: true
class Score < ApplicationRecord
INIT_SCORE = self.init_score.freeze
def self.init_score
init_score = new(multiplier: 4.0)
init_score.readonly!
end
end
Score::INIT_SCORE
So this approach makes sense, is this a good solution?
I want to know if a const can be used as a cache.
Related
I am looking for a solution to automatically initialize a class variable through inheritance (make it available as an accessor and initialize it to some value). But I do NOT want to inherit the value, just start with a new fresh object each time on each class.
I have been looking at class_attributes and thought I had found a workaround but it does not seem to be working as I thought (and even if it worked, it would most likely not do the thing I want since the same array would be used everywhere so it would behave like a ## variable)
class AbstractClass
class_attribute :metadata
#metadata = [] # initialize metadata to an empty array
def self.add_metadata(metadata)
#metadata << metadata
end
end
def ChildClass < AbstractClass
add_metadata(:child_class1)
end
def ChildClass2 < AbstractClass
add_metadata(:child_class2)
end
I'd like to have the following :
AbstractClass.metadata # Don't really care about this one
ChildClass1.metadata # => [:child_class1]
ChildClass2.metadata # => [:child_class2]
I can think of a way to do this using modules with AS::Support
module InitializeClassInstanceVars
extend ActiveSupport::Concern
included do
class_attribute :metadata
self.metadata = []
end
end
...and include this module in every nested class (and I believe this is what mongoid actually does for instance)
but I was hoping I could do this directly via inheritance
You don't have to initialize the class variable when it is being inherited. The Ruby style is to return and assign default value when the variable has not been set and is being accessed for the first time.
Just create another class method for that:
class AbstractClass
def self.metadata
#metadata ||= []
end
def self.add_metadata(metadata)
self.metadata << metadata
end
end
class ChildClass1 < AbstractClass
add_metadata(:child_class1)
end
class ChildClass2 < AbstractClass
add_metadata(:child_class2)
end
AbstractClass.metadata # => []
ChildClass1.metadata # => [:child_class1]
ChildClass2.metadata # => [:child_class2]
Hooks are a great idea, you're just working off of the wrong one :) If you want to run code every time something inherits your class, then inherited is the one to use:
class AbstractClass
class << self
attr_accessor :metadata
def inherited(child)
child.instance_variable_set(:#metadata, [child.name])
end
end
end
class ChildClass1 < AbstractClass; end
class ChildClass2 < AbstractClass; end
ChildClass1.metadata
# => ["ChildClass1"]
ChildClass2.metadata
# => ["ChildClass2"]
Given that the question is tagged rails, you should also have String#underscore available; replace child.name with child.name.underscore.to_s to get [:child_class1].
EDIT: I might have misunderstood the question. If you just want to start with an empty array that you can add to, chumakoff's answer is simpler.
How can I set a model to be read-only every time that it is accessed if an attribute within the same model is set to true?
I have looked everywhere and the model read only seems to have very little documentation and even web results.
Edit (Additional Info):
I have two methods in my Model (application.rb) - not in private
def lock()
self.locked = true
save(validate: false)
end
def unlock()
self.locked = false
save(validate: false)
end
I call them from my applications controller on update with:
if params[:application][:locked] == false
#application.unlock
return
elsif params[:application][:locked] == true
#application.lock
return
end
and in the Model (application.rb) I have - not in private:
def readonly?
locked == true
end
Updated:
# app/models/application.rb
# I highly suggest renaming `Application` into something else because, Rails
# already has a same defined constant name `Application` which is defined in your
# app/config/application.rb
class Application < ApplicationRecord
def lock!
# depending on your use-case I'll do an `update` below instead
# self.lock = true
update!(locked: true)
end
def unlock!
# self.lock = false
update!(locked: false)
end
end
# app/models/user.rb
class User < ApplicationRecord
belongs_to :application
def readonly?
# this is still subject to race-condition even though already `reloaded`
application.reload.locked || some_user_attribute == 'HELLO WORLD!'
end
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :application
def readonly?
# this is still subject to race-condition even though already `reloaded`
application.reload.locked || some_comment_attribute_like_is_disabled?
end
end
Notice that I added a belongs_to association there because you'll most likely need this because your Application as you said is actually already a normal model anyway. If you do not have this association, and are setting the locked internally as a class instance variable of your Application class (i.e. you have #locked class instance variable), then (depending on your requirements), you'll have problems with 1) persistency because each request (per different process/server) will default to locked = nil (which might or might not be a problem to you), and also 2) concurrency because threads share the value of this class instance variable, which means that simultaneous requests would need this #locked value be evaluated independently; which becomes potentially dangerous if #locked is set to true in one thread, while on another #locked is overidden and is set to false. But if these are not a problem, I can still update my answer to not use belongs_to :application; let me know.
I have class defined like this, and I been told this could cause problems with Rails autoload. What is the reason behind this? and when should we use Class.new?
class Integration < ActiveRecord::Base
MYobIdentifier = Class.new(Integration)
end
If a constant (in this case your class name) cannot be find then Ruby will attempt to find it using a filename based on the constant name. In this case it will search for MYobIdentifier in a file called m_yob_identifier (There are a few different places it would look for this file)
As long as you can be sure that the Integration class is loaded before any attempt is made to ys MYobIdentifier then it will always be defined and you do not need to worry.
Also in your case, I assume you will access it as Integration::MYobIdentifier in which case Integration will always be loaded before trying to resolve the constant.
Note: I think you would be better off capitalizing the class as MyObIdentifier
As the stackoverflow question linked in a comment covers,
class A
class B
end
end
becomes this under the hood:
A = Class.new do
B = Class.new
end
One additional detail of your example is that another class Integration is being passed as an argument to Class.new. This sets up inheritance. You can check whether one class inherits from another using ClassA < ClassB; this returns true or nil:
class A
class B
end
end
A::B < A
# => nil
class A
B = Class.new(A)
end
A::B < A
# => true
this second example is the same as this:
class A
class B < A
end
end
A::B < A
# => true
I am trying to DRY my code by implementing modules. However, I have constants stored in models (not the module) that I am trying to access with self.class.
Here are (I hope) the relevant snippets:
module Conversion
def constant(name_str)
self.class.const_get(name_str.upcase)
end
end
module DarkElixir
def dark_elixir(th_level)
structure.map { |name_str| structure_dark_elixir(name_str, th_level) if constant(name_str)[0][:dark_elixir_cost] }.compact.reduce(:+)
end
end
class Army < ActiveRecord::Base
include Conversion, DarkElixir
TH_LEVEL = [...]
end
def structure_dark_elixir(name_str, th_level)
name_sym = name_str.to_sym
Array(0..send(name_sym)).map { |level| constant(name_str)[level][:dark_elixir_cost] }.reduce(:+) * TH_LEVEL[th_level][sym_qty(name)]
end
When I place the structure_dark_elixir method inside the DarkElixir module, I get an error, "uninitialized constant DarkElixir::TH_LEVEL"
While if I place it inside the Army class, it finds the appropriate constant.
I believe it is because I am not scoping the self.constant_get correctly. I would like to keep the method in question in the module as other models need to run the method referencing their own TH_LEVEL constants.
How might I accomplish this?
Why not just use class methods?
module DarkElixir
def dark_elixir(th_level)
# simplified example
th_level * self.class.my_th_level
end
end
class Army < ActiveRecord::Base
include DarkElixir
def self.my_th_level
5
end
end
Ugh. Method in question uses two constants. It was the second constant that was tripping up, not the first. Added "self.class::" prior to the second constant--back in business.
def structure_dark_elixir(name_str, th_lvl)
name_sym = name_str.to_sym
Array(0..send(name_sym)).map { |level| constant(name_str)[level][:dark_elixir_cost] }.reduce(:+) * self.class::TH_LEVEL[th_lvl][sym_qty(name_str)]
end
I want to be able to have methods in a module that are not accessible by the class that includes the module. Given the following example:
class Foo
include Bar
def do_stuff
common_method_name
end
end
module Bar
def do_stuff
common_method_name
end
private
def common_method_name
#blah blah
end
end
I want Foo.new.do_stuff to blow up because it is trying to access a method that the module is trying to hide from it. In the code above, though, Foo.new.do_stuff will work fine :(
Is there a way to achieve what I want to do in Ruby?
UPDATE - The real code
class Place < ActiveRecord::Base
include RecursiveTreeQueries
belongs_to :parent, {:class_name => "Place"}
has_many :children, {:class_name => 'Place', :foreign_key => "parent_id"}
end
module RecursiveTreeQueries
def self_and_descendants
model_table = self.class.arel_table
temp_table = Arel::Table.new :temp
r = Arel::SelectManager.new(self.class.arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(model_table[:parent_id].eq(temp_table[:id]))
nr = Place.scoped.where(:id => id)
q = Arel::SelectManager.new(self.class.arel_engine)
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(self.class.arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
def self_and_ascendants
model_table = self.class.arel_table
temp_table = Arel::Table.new :temp
r = Arel::SelectManager.new(self.class.arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(temp_table[:parent_id].eq(model_table[:id]))
nr = Place.scoped.where(:id => id)
q = Arel::SelectManager.new(self.class.arel_engine)
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(self.class.arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
end
Clearly this code is hacked out and due some serious refactoring, and the purpose of my question is to find out if there is a way I can refactor this module with impunity from accidentally overwriting some method on ActiveRecord::Base or any other module included in Place.rb.
I don't believe there's any straightforward way to do this, and that's by design. If you need encapsulation of behavior, you probably need classes, not modules.
In Ruby, the primary distinction between private and public methods is that private methods can only be called without an explicit receiver. Calling MyObject.new.my_private_method will result in an error, but calling my_private_method within a method definition in MyObject will work fine.
When you mix a module into a class, the methods of that module are "copied" into the class:
[I]f we include a module in a class definition, its methods are effectively appended, or "mixed in", to the class. — Ruby User's Guide
As far as the class is concerned, the module ceases to exist as an external entity (but see Marc Talbot's comment below). You can call any of the module's methods from within the class without specifying a receiver, so they're effectively no longer "private" methods of the module, only private methods of the class.
This is quite an old question, but I feel compelled to answer it since the accepted answer is missing a key feature of Ruby.
The feature is called Module Builders, and here is how you would define the module to achieve it:
class RecursiveTreeQueries < Module
def included(model_class)
model_table = model_class.arel_table
temp_table = Arel::Table.new :temp
nr = Place.scoped.where(:id => id)
q = Arel::SelectManager.new(model_class.arel_engine)
arel_engine = model_class.arel_engine
define_method :self_and_descendants do
r = Arel::SelectManager.new(arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(model_table[:parent_id].eq(temp_table[:id]))
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
define_method :self_and_ascendants do
r = Arel::SelectManager.new(arel_engine).from(model_table).project(model_table.columns).join(temp_table).on('true').where(temp_table[:parent_id].eq(model_table[:id]))
as = Arel::Nodes::As.new temp_table, nr.union(r)
arel = Arel::SelectManager.new(arel_engine).with(:recursive,as).from(temp_table).project(temp_table[:id])
self.class.where(model_table[:id].in(arel))
end
end
end
Now you can include the module with:
class Foo
include RecursiveTreeQueries.new
end
You need to actually instantiate the module here since RecursiveTreeQueries is not a module itself but a class (a subclass of the Module class). You could refactor this further to reduce a lot of duplication between methods, I just took what you had to demonstrate the concept.
Mark the method private when the module is included.
module Bar
def do_stuff
common_method_name
end
def common_method_name
#blah blah
end
def self.included(klass)
klass.send(:private, :common_method_name)
end
end