I have a block of code. It was:
class User < ActiveRecord::Base
def configuration_with_cache
Rails.cache.fetch("user_#{id}_configuration") do
configuration_without_cache
end
end
alias_method_chain :configuration, :cache
end
I want to remove the notorious alias_method_chain, so I decided to refactor it. Here is my version:
class User < ActiveRecord::Base
def configuration
Rails.cache.fetch("#{id}_agency_configuration") do
super
end
end
end
But it doesn't work. The super enters a new scope. How can I make it work? I got TypeError: can't cast Class, and I misunderstood it.
To start off, calling super in blocks does behave the way you want. Must be your console is in a corrupted state (or something).
class User
def professional?
Rails.cache.fetch("user_professional") do
puts 'running super'
super
end
end
end
User.new.professional?
# >> running super
# => false
User.new.professional?
# => false
Next, this looks like something Module#prepend was made to help with.
module Cacher
def with_rails_cache(method)
mod = Module.new do
define_method method do
cache_key = "my_cache_for_#{method}"
Rails.cache.fetch(cache_key) do
puts "filling the cache"
super()
end
end
end
prepend mod
end
end
class User
extend Cacher
with_rails_cache :professional?
end
User.new.professional?
# >> filling the cache
# => false
User.new.professional?
# => false
you can user Super in block.
please see this, any issues let me know.
Calling it as just 'super' will pass the block.
super(*args, &block)' will as well.
Related
I'm upgrading my rails application and I'm getting a warning saying alias_method_chain is deprecated. Please, use Module#prepend instead. But I'm not really understanding how to handle this. How can I change the code below?
def read_attribute_with_mapping(attr_name)
read_attribute_without_mapping(ADDRESS_MAPPING[attr_name] || attr_name)
end
alias_method_chain :read_attribute, :mapping
prepend is basically like importing a module, but it ends up "in front" of other code (so the module can call super to run the code it's in front of).
This is a runnable example with something close to your situation.
module MyModule
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end
class Example
prepend MyModule
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo
I defined read_attribute directly on Example, but it could just as well have been a method inherited from a superclass (such as ActiveRecord::Base).
This is a shorter but more cryptic version that uses an anonymous module:
class Example
prepend(Module.new do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end)
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo
UPDATE:
Just for fun and to address a question below, here's how it could be done without having to explicitly make any modules yourself. I don't think I'd choose to do it this way myself, since it obscures a common pattern.
# You'd do this once somewhere, e.g. config/initializers/prepend_block.rb in a Rails app.
class Module
def prepend_block(&block)
prepend Module.new.tap { |m| m.module_eval(&block) }
end
end
# Now you can do:
class Example
prepend_block do
def read_attribute(attr_name)
super("modified_#{attr_name}")
end
end
def read_attribute(attr_name)
puts "Reading #{attr_name}"
end
end
Example.new.read_attribute(:foo)
# Outputs: Reading modified_foo
I would like to create something similar to ActiveRecord validation: before_validate do ... end. I am not sure how could I reference attributes of class instance from the block given. Any idea?
class Something
attr_accessor :x
def self.before_validate(&block)
#before_validate_block = block
end
before_validate do
self.x.downcase
end
def validate!
# how should this method look like?
# I would like that block would be able to access instance attributes
end
end
#3limin4t0r's answer covers mimicing the behavior in plain ruby very well. But if your are working in Rails you don't need to reinvent the wheel just because you're not using ActiveRecord.
You can use ActiveModel::Callbacks to define callbacks in any plain old ruby object:
class Something
extend ActiveModel::Callbacks
define_model_callbacks :validate, scope: :name
before_validate do
self.x.downcase
end
def validate!
run_callbacks :validate do
# do validations here
end
end
end
Featurewise it blows the socks off any of the answers you'll get here. It lets define callbacks before, after and around the event and handles multiple callbacks per event.
If validations are what you really are after though you can just include ActiveModel::Validations which gives you all the validations except of course validates_uniqueness_of which is defined by ActiveRecord.
ActiveModel::Model includes all the modules that make up the rails models API and is a good choice if your are declaring a virtual model.
This can be achieved by using instance_eval or instance_exec.
class Something
attr_accessor :x
# You need a way to retrieve the block when working with the
# instance of the class. So I've changed the method so it
# returns the +#before_validate_block+ when no block is given.
# You could also add a new method to do this.
def self.before_validate(&block)
if block
#before_validate_block = block
else
#before_validate_block
end
end
before_validate do
self.x.downcase
end
def validate!
block = self.class.before_validate # retrieve the block
instance_eval(&block) # execute it in instance context
end
end
How about this?
class Something
attr_accessor :x
class << self
attr_reader :before_validate_blocks
def before_validate(&block)
#before_validate_blocks ||= []
#before_validate_blocks << block
end
end
def validate!
blocks = self.class.before_validate_blocks
blocks.each {|b| instance_eval(&b)}
end
end
Something.before_validate do
puts x.downcase
end
Something.before_validate do
puts x.size
end
something = Something.new
something.x = 'FOO'
something.validate! # => "foo\n3\n"
This version allows us to define multiple validations.
I'm trying to understand some Ruby metaprogramming concepts.
I think I understand classes, objects, and metaclasses. Unfortunately, I'm very unclear on exactly what happens with included Modules with respect to their instance/'class' variables.
Here's a contrived question whose solution will answer my questions:
Suppose I'm writing my own crappy Rails "validates" method, but I want it to come from a mixed-in module, not a base class:
module MyMixin
# Somehow validates_wordiness_of() is defined/injected here.
def valid?
# Run through all of the fields enumerated in a class that uses
# "validate_wordiness_of" and make sure they .match(/\A\w+\z/)
end
end
class MyClass
include MyMixin
# Now I can call this method in my class definition and it will
# validate the word-ness of my string fields.
validate_wordiness_of :string_field1, :string_field2, :string_field3
# Insert rest of class here...
end
# This should work.
MyMixin.new.valid?
Ok, so how would you store that list of fields from the validate_wordiness_of invocation (in MyClass) in such a way that it can be used in the valid? method (from MyMixin)?
Or am I coming at this all wrong? Any info would be super appreciated!
So here are two alternative ways of doing it:
With "direct" access
module MyMixin
def self.included(base)
base.extend(ClassMethods)
end
def wordy?(value)
value.length > 2
end
module ClassMethods
def validates_wordiness_of(*attrs)
define_method(:valid?) do
attrs.all? do |attr|
wordy?(send(attr))
end
end
end
end
end
class MyClass
include MyMixin
validates_wordiness_of :foo, :bar
def foo
"a"
end
def bar
"asrtioenarst"
end
end
puts MyClass.new.valid?
The downside to this approach is that several consecutive calls to validates_wordiness_of will overwrite each other.
So you can't do this:
validates_wordiness_of :foo
validates_wordiness_of :bar
Saving validated attribute names in the class
You could also do this:
require 'set'
module MyMixin
def self.included(base)
base.extend(ClassMethods)
end
module Validation
def valid?
self.class.wordy_attributes.all? do |attr|
wordy?(self.send(attr))
end
end
def wordy?(value)
value.length > 2
end
end
module ClassMethods
def wordy_attributes
#wordy_attributes ||= Set.new
end
def validates_wordiness_of(*attrs)
include(Validation) unless validation_included?
wordy_attributes.merge(attrs)
end
def validation_included?
ancestors.include?(Validation)
end
end
end
class MyClass
include MyMixin
validates_wordiness_of :foo, :bar
def foo
"aastrarst"
end
def bar
"asrtioenarst"
end
end
MyClass.new.valid?
# => true
I chose to make the valid? method unavailable until you actually add a validation. This may be unwise. You could probably just have it return true if there are no validations.
This solution will quickly become unwieldy if you introduce other kinds of validations. In that case I would start wrapping validations in validator objects.
I'm creating a module that extends the functionality of an ActiveRecord model.
Here's my initial setup.
My class:
class MyClass < ActiveRecord::Base
is_my_modiable
end
And Module:
module MyMod
def self.is_my_modiable
class_eval do
def new_method
self.mod = true
self.save!
end
end
end
end
ActiveRecord::Base(extend,MyMod)
What I would like to do now is extend the functionality of the new_method by passing in a block. Something like this:
class MyClass < ActiveRecord::Base
is_my_modiable do
self.something_special
end
end
module MyMod
def self.is_my_modiable
class_eval do
def new_method
yield if block_given?
self.mod = true
self.save!
end
end
end
end
This doesn't work though, and it makes sense. In the class_eval, the new_method isn't being executed, just defined, and thus the yield statement wouldn't get executed until the method actually gets called.
I've tried to assign the block to a class variable within the class_eval, and then call that class variable within the method, but the block was being called on all is_my_modiable models, even if they didn't pass a block into the method.
I might just override the method to get the same effect, but I'm hoping there is a more elegant way.
If I understood you correctly, you can solve this by saving passed block to an instance variable on class object and then evaling that in instance methods.
bl.call won't do here, because it will execute in the original context (that of a class) and you need to execute it in scope of this current instance.
module MyMod
def is_my_modiable(&block)
class_eval do
#stored_block = block # back up block
def new_method
bl = self.class.instance_variable_get(:#stored_block) # get from class and execute
instance_eval(&bl) if bl
self.mod = true
self.save!
end
end
end
end
class MyClass
extend MyMod
is_my_modiable do
puts "in my modiable block"
self.something_special
end
def something_special
puts "in something special"
end
attr_accessor :mod
def save!; end
end
MyClass.new.new_method
# >> in my modiable block
# >> in something special
You can do this by assigning the block as a method parameter:
module MyMod
def self.is_my_modiable
class_eval do
def new_method(&block)
block.call if block
self.mod = true
self.save!
end
end
end
end
I'm building a gem and I want part of its functionality to extend ActiveRecord::Associations::Builder::BelongsTo but I cannot figure out how to do it
so basically user should be able to specify:
class Event < ActiveRecord::Base
belongs_to :users, foo: true
end
anyone know how to do it ??
This wont work:
module Mygem
module BelongsToFoo
def valid_options
super + [:foo]
end
#... other functionality
end
end
class ActiveRecord::Associations::Builder::BelongsTo
extend MyGem::BelongsToFoo
end
console
ActiveRecord::Associations::Builder::BelongsTo.valid_options.include? :foo
#=> false ... :(
Event
ArgumentError: Unknown key: foo
belongs_to source code
=============================================================================
Update
flowing delwyns answer I tried to have a another look on my code and he is right it should be included however ActiveRecord::Associations::Builder::BelongsTo has a variable valid_options as well.
so I can do
ActiveRecord::Associations::Builder::BelongsTo.new(:a, :b, :c).valid_options.include? :foo
# => true
but also
ActiveRecord::Associations::Builder::BelongsTo.valid_options.include? :foo
# => true
so it should really look like this
module MyGem
module BelongsToFoo
extend ActiveSupport::Concern
included do
self.valid_options += [:foo]
end
def valid_options
super + [:foo]
end
def define_callbacks(model, reflection)
# this wont get executed
add_foo_callbacks(model, reflection)# if options[:foo]
super
end
def add_foo_callbacks(model, reflection)
# therefore this wont either
end
end
end
Even if I try this
module MyGem
module BelongsToFoo
def define_callbacks(model, reflection)
raise "dobugging"
end
end
end
nothing will happen, Rails completely ignore my method override
So yes I can define my own option, however they do nothing :( any suggestions ?
There is a built in approach for extending association proxies, see http://guides.rubyonrails.org/association_basics.html#association-extensions
class Event < ActiveRecord::Base
belongs_to :users, :extend => MyGem::SpecialTouch
end
module MyGem
module SpecialTouch
def touch
# do the magic
end
end
end
Then you could of course override or alias chain belongs_to so that it pops your :foo option from options hash, converts it to proper :extend => ... and (really or effectively) calls belongs_to.
valid_options is an instance method so you need to use include instead of extend.
module MyGem
def valid_options
super + [:foo]
end
end
class ActiveRecord::Associations::Builder::BelongsTo
include ::MyGem
end
relation = ActiveRecord::Associations::Builder::BelongsTo.new(:a, :b, :c)
relation.valid_options.include? :foo
#=> true
Hope that helps.