I have a behavior for models.
I'd like to include like this:
class Building < ActiveRecord::Base
has_many :blocks
include Staticizable
check_children :blocks #here name of children model to check
...
end
The behavior looks like:
module Staticizable
extend ActiveSupport::Concern
module ClassMethods
def check_children child
self.child_model = child #where is to store model name to check?
end
end
def was_staticized
(DateTime.now - self.staticized_date.to_datetime).to_i
end
def staticized?
if #child_model.present?
ap self.send(#child_model)
else
if staticize_period > was_staticized
true
else
false
end
end
end
def staticized
self.staticized_date = DateTime.now
save
end
end
So I need to know where I can store model name to check children. And what is the right way to do things like this?
Finally, I got the answer, not sure that is the best way, if no, let me know, please:
module Staticizable
extend ActiveSupport::Concern
included do
class << self;
attr_accessor :has_child_custom
end
end
module ClassMethods
def check_children child
self.has_child_custom = child
end
end
def was_staticized
unless staticized_date
return false
end
(DateTime.now - self.staticized_date.to_datetime).to_i
end
def staticized?
if self.class.has_child_custom.present?
self.send(self.class.has_child_custom).each do |child|
unless child.staticized?
return false
end
end
else
if self.staticize_period > was_staticized
true
else
false
end
end
end
def staticized
self.staticized_date = DateTime.now
save
end
end
Also I've created a file in /config/initializers with
ActiveRecord::Base.send :include, Staticizable
And set it up in model with:
class Building < ActiveRecord::Base
check_children :blocks
...
end
Related
I need to access a class method (defined in ClassMethods) in an instance method inside a concern.
My brain is melted and I'm sure that is a simple thing that I'm doing wrong.
I need to access comparable_opts inside comparison. How can I do it?
Follow snippets below:
Concern
# app/models/concerns/compare.rb
module Compare
extend ActiveSupport::Concern
attr_accessor :comparable_opts
module ClassMethods
attr_reader :arguable_opts
def comparable_opts
##comparable_opts
end
private
def default_opts
#default_opts ||= {fields: [:answers_count,
:answers_correct_count,
:answers_correct_rate,
:users_count]}
end
def compare(opts={})
#comparable_opts = default_opts.merge(opts)
end
end
def comparison
end
end
Model
# app/models/mock_alternative.rb
class MockAlternative < ActiveRecord::Base
include Compare
belongs_to :mock, primary_key: :mock_id, foreign_key: :mock_id
compare fields: [:answers_count, :question_answers_count, :question_answers_rate],
with: :mock_aternative_school
def question_answers_rate
self[:answers_count].to_f/self[:question_answers_count].to_f
end
end
Solution:
I've just used cattr_accessor in my method compare. Thank everyone.
module Compare
extend ActiveSupport::Concern
module ClassMethods
attr_reader :arguable_opts
def comparison_klass
"ActiveRecord::#{comparable_opts[:with].to_s.classify}".constantize
end
private
def default_opts
#default_opts ||= {fields: [:answers_count,
:answers_correct_count,
:answers_correct_rate,
:users_count]}
end
def compare(opts={})
cattr_accessor :comparable_opts
self.comparable_opts = default_opts.merge(opts)
end
end
def comparison
comparable_opts
end
end
I have following code working in rails 3.2:
class Cart < ActiveRecord::Base
def self.get_details()
cart_obj = Cart.first
cart_obj["custom"] = 1 #Here *custom* is not the column in database
end
end
And i can access the column custom from the cart_obj object whenever we require.
But we are planning to upgrade to rails 4 and its not working there. Is there any work around for it except using attr_accessor??
It sounds like monkey patching is your way to go:
class ActiveRecord::Base
def [](key)
return super(key) if self.class.column_names.include?(key.to_sym)
self.class.send :attr_accessor, key.to_sym unless self.class.instance_variable_defined?("##{key}".to_sym)
self.instance_variable_get("##{key}".to_sym)
end
def []=(key, val)
return super(key, val) if self.class.column_names.include?(key.to_sym)
self.class.send :attr_accessor, key.to_sym unless self.class.instance_variable_defined?("##{key}".to_sym)
self.instance_variable_set("##{key}".to_sym, val)
end
end
Or if you'd like to have it as a concern:
module MemoryStorage
extend ActiveSupport::Concern
def [](key)
return super(key) if self.class.column_names.include?(key.to_sym)
self.class.send :attr_accessor, key.to_sym unless self.class.instance_variable_defined?("##{key}".to_sym)
self.instance_variable_get("##{key}".to_sym)
end
def []=(key, val)
return super(key, val) if self.class.column_names.include?(key.to_sym)
self.class.send :attr_accessor, key.to_sym unless self.class.instance_variable_defined?("##{key}".to_sym)
self.instance_variable_set("##{key}".to_sym, val)
end
end
class Cart < ActiveRecord::Base
include MemoryStorage
def self.get_details()
cart_obj = Cart.first
cart_obj.db_column = 'direct DB access'
cart_obj["custom"] = 'access to "in-memory" column'
end
end
In rails 4, use attr_accessor:
If you have additional instance data you don't need to persist (i.e. it's not a database column), you could then use attr_accessor to save yourself a few lines of code.
class cart < ActiveRecord::Base
attr_accessor :custom
def self.get_details
cart_obj = Cart.first
cart_obj.custom = whatever
end
end
I am using Ruby on Rails 3.2.9 and Ruby 1.9.3. I have many model classes implementing similar methods as-like the following:
class ClassName_1 < ActiveRecord::Base
def great_method
self.method_1
end
def method_1 ... end
end
class ClassName_2 < ActiveRecord::Base
def great_method
result_1 = self.method_1
result_2 = self.method_2
result_1 && result_2
end
def method_1 ... end
def method_2 ... end
end
...
class ClassName_N < ActiveRecord::Base
def great_method
result_1 = self.method_1
result_2 = self.method_2
...
result_N = self.method_N
result_1 && result_2 && ... && result_N
end
def method_1 ... end
def method_2 ... end
...
def method_N ... end
end
Those model classes behaves almost the same (not the same) since some of those has an interface with some less or more methods. All methods are differently named (for instance, method_1 could be named bar and method_2 could be named foo), all return true or false, are always the same in each class and there is no relation between them.
What is the proper way to refactor those classes?
Note: At this time I am thinking to refactor classes by including the following module in each one:
module MyModule
def great_method
result_1 = self.respond_to?(:method_1) ? self.method_1 : true
result_2 = self.respond_to?(:method_2) ? self.method_2 : true
...
result_N = self.respond_to?(:method_N) ? self.method_N : true
result_1 && result_2 && ... && result_N
end
end
But I don't know if it is the proper way to accomplish what I am looking for. Furthermore, I am not sure of related advantages and disadvantages...
Looks like you're on the right track. If the method_n methods are unique to your classes then just build the module that you already have into a superclass that each ClassNameN inherits from:
class SuperClassName < ActiveRecord::Base
def great_method
#... what you have in your module
end
end
class ClassNameN < SuperClassName
def method_1 ... end
def method_2 ... end
end
There may be additional ways for you to factor out code depending on what goes on in your method_n methods, but it's impossible to say without more detail.
I would use a metaprogramming solution to clean this up somewhat.
module BetterCode
extend ActiveSupport::Concern
module ClassMethods
def boolean_method(name, *components)
define_method name do
components.all? { |c| send c }
end
end
end
end
And in your models:
class MyModel < ActiveRecord::Base
include BetterCode
boolean_method :great_method, :foo, :bar, :baz, :quux
end
Instances of MyModel will then respond to great_method with a boolean value indicating whether or not foo, bar, baz and quux are all true.
You can abstract out the great_method with something like this:
require 'active_support/concern'
module Greatest
extend ActiveSupport::Concern
module ClassMethods
attr_accessor :num_great_methods
def has_great_methods(n)
#num_great_methods = n
end
end
def great_method
(1..self.class.num_great_methods).each do |n|
return false unless self.__send__("method_#{n}")
end
true
end
end
class ClassName_3
include Greatest
has_great_method 3
# stub out the "method_*" methods
(1..3).each do |n|
define_method "method_#{n}" do
puts "method_#{n}"
true
end
end
end
puts ClassName_1.new.greatest
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
We have the following sweeper in a rails application:
class AgencyEquipmentTypeSweeper < ActionController::Caching::Sweeper
observe AgencyEquipmentType
#include ExpireOptions
def after_update(agency_equipment_type)
expire_options(agency_equipment_type)
end
def after_delete(agency_equipment_type)
expire_options(agency_equipment_type)
end
def after_create(agency_equipment_type)
expire_options(agency_equipment_type)
end
def expire_options(agency_equipment_type)
Rails.cache.delete("agency_equipment_type_options/#{agency_equipment_type.agency_id}")
end
end
We'd like to extract the after_update, after_delete, and after_create callbacks to a module called "ExpireOptions"
The module should look like this (with the 'expire_options' method staying behind in the
original sweeper):
module ExpireOptions
def after_update(record)
expire_options(record)
end
def after_delete(record)
expire_options(record)
end
def after_create(record)
expire_options(record)
end
end
class AgencyEquipmentTypeSweeper < ActionController::Caching::Sweeper
observe AgencyEquipmentType
include ExpireOptions
def expire_options(agency_equipment_type)
Rails.cache.delete("agency_equipment_type_options/#{agency_equipment_type.agency_id}")
end
end
BUT the cache expirations only work if we define the methods explicitly inside the sweeper. Is there an easy way to extract those callback methods to a module, and still have them work?
Try with:
module ExpireOptions
def self.included(base)
base.class_eval do
after_update :custom_after_update
after_delete :custom_after_delete
after_create :custom_after_create
end
end
def custom_after_update(record)
expire_options(record)
end
def custom_after_delete(record)
expire_options(record)
end
def custom_after_create(record)
expire_options(record)
end
end
I would try something like:
module ExpireOptions
def after_update(record)
self.send(:expire_options, record)
end
def after_delete(record)
self.send(:expire_options, record)
end
def after_create(record)
self.send(:expire_options, record)
end
end
This should make sure it does not try to call those methods on the module, but on self which would hopefully be the calling object.
Does that help?