How can I use or reference a lambda that has been defined in base/super class?
Super class code
module Api
class ApplicationSerializer
include JSONAPI::Serializer
# I Also tried it this way
# serializer_for_proc = -> (object) { SerializerLookup.new.(object) }
def serializer_for_proc
-> (object) { SerializerLookup.new.(object) }
end
end
end
Child class code
module Api
class AnswerSerializer < ApplicationSerializer
attributes :values, :created_at, :updated_at, :usage
belongs_to :question, serializer: serializer_for_proc # NameError (undefined local variable or method `serializer_for_proc'
end
end
As presented, serializer_for_proc is an instance method of Api::ApplicationSerializer, but you're trying to invoke it directly on the class (or rather, directly on the subclass). In other words, all of the following fail for more-or-less the same reason:
belongs_to :question, serializer: serializer_for_proc
belongs_to :question, serializer: ApplicationSerializer.serializer_for_proc
belongs_to :question, serializer: AnswerSerializer.serializer_for_proc
You've never actually defined ApplicationSerializer.serializer_for_proc, you've defined what is traditionally written as ApplicationSerializer#serializer_for_proc, or in other words, ApplicationSerailizer.new.serializer_for_proc.
The quickest way to fix this would be to make the original method definition a class method:
module Api
class ApplicationSerializer
include JSONAPI::Serializer
def self.serializer_for_proc
-> (object) { SerializerLookup.new.(object) }
end
end
end
You wrote in a comment that it did work when you copied it into the AnswerSerializer file, but this is unlikely (basically impossible). What would work would be copying the serializer_for_proc = -> (object) { SerializerLookup.new.(object) } version into the AnswerSerializer file, because then serializer_for_proc isn't a method name at all, it's just a local variable within the AnswerSerializer file, same as x = 5. But that's not inheritable.
Another solution (although one I like significantly less) would be making serializer_for_proc a constant, e.g.:
# One file
module Api
class ApplicationSerializer
include JSONAPI::Serializer
SERIALIZER_FOR_PROC = -> (object) { SerializerLookup.new.(object) }
end
end
# Another file
module Api
class AnswerSerializer < ApplicationSerializer
attributes :values, :created_at, :updated_at, :usage
belongs_to :question, serializer: SERIALIZER_FOR_PROC
end
end
That works, but I don't like to rely on this because inheritance-based constant lookup in Ruby has some pretty weird edge cases. I'd personally just use Api::ApplicationSerializer::SERIALIZER_FOR_PROC if I wanted to use a constant.
Related
I have a class with attr_accessor set like this:
class Human
ATTRIBUTES = [:name]
attr_accessor *ATTRIBUTES
end
it works like a charm, allows me to keep attributes inside ATTRIBUTES constant. Problem is I would like to have a class Student inheriting from a Human class, without the need to put attr_accessor every time.
Basically what i would like to have is this:
class Student < Human
ATTRIBUTES = [:name, :school]
end
unfortunately when i do
Student.new.school
i get no method error, because attr_accessor is loaded from Human and not a Student. What construction should i use to accomplish my goal?
I personally agree with #lcguida's answer, but I came up with a little experiment if you insist on following the pattern you proposed. The other answers already covered why your solution didn't work, so I'm not getting into that here.
The first thing that came to mind was to call attr_accessor on the self.inherited callback on the parent class, but unfortunately the child's body is not loaded until later. Even so, where there's a will, there's a way. If you're using Ruby 2.0 or later, the following implementation will work.
module LazyAttrAccessorizer
def self.extended(obj)
TracePoint.trace(:end) do |t|
if obj == t.self
obj.send :attr_accessor, *obj::ATTRIBUTES
t.disable
end
end
end
end
class Human
extend LazyAttrAccessorizer
ATTRIBUTES = [:name]
def self.inherited(subclass)
subclass.extend LazyAttrAccessorizer
end
end
class Student < Human
ATTRIBUTES = [:name, :school]
# ATTRIBUTES = [:school] would also work as expected, but I think you'd like to be literal there.
end
> Student.new.respond_to?(:name)
=> true
> Student.new.respond_to?(:school)
=> true
Well, while I don't get the need to keep the attributes in a array, Student class will already inherit the attr_accessor's defined in it's parent class.
For example:
class Human
attr_accessor :name, :gender
end
class Student < Human
attr_accessor :school
end
Student class now has :name, :gender and :school attr_accessor's:
> Student.new.respond_to?(:name)
=> true
> Student.new.respond_to?(:name=)
=> true
> Student.new.respond_to?(:school)
=> true
> Student.new.respond_to?(:school=)
=> true
Human also responds to :name and :gender
> Human.new.respond_to?(:name)
=> true
> Human.new.respond_to?(:gender)
=> true
But not to school
> Human.new.respond_to?(:school)
=> false
It's cleaner, it's the ruby way, easier to understand.
Just a simple example.
class Base
def self.inherited(child)
p 'Base.inherited'
end
end
class User < Base
p 'User'
end
This produces me
"Base.inherited"
"User"
This works fine but how can I patch the inherited hook of Base class?
Let's say I want my result to be
"Base.inherited"
"Something inherited"
"User"
and still have my User class inherit the Base.
Any ideas, workarounds?
Thanks!
Updating question to be more specific.
I need to run some code exactly at the time when class User inherits the Base without modifying User class.
Let's say I have Base class with it's defined inherited method. From one hand I don't know what other classes will inherit Base. From another hand I cannot modify the original inherited method of Base class.
So how can I patch that method?
Thanks!
module Foo
def self.included(child)
p "Something inherited"
end
end
class Base
def self.inherited(child)
p 'Base.inherited'
end
end
class User < Base
include Foo
p 'User'
end
# >> "Base.inherited"
# >> "Something inherited"
# >> "User"
Found the answer.
Alias chaining works fine in this case. For some reason I thought it works with common methods but not with ruby callbacks.
class Base
def self.inherited(child)
p 'Base.inherited'
end
end
Base.class_eval do
class << self
alias_method :chained_inherited, :inherited
def inherited(child)
chained_inherited(child)
p 'Inherited'
end
end
end
class User < Base
p 'User'
end
# => "Base.inherited"
# => "Inherited"
# => "User"
I'm working on a extended search feature for my webpage.
I looked at ransack, however it's lacking some functionalities I need, makes the url-query string very long and has some bugs (reported).
Thus I started to implement my own hack.
First I want to present my idea, afterwards I want to ask kindly how to fix my issue and in the end if there are other ways to improve this.
The idea:
A model defines something like this (additionally, the model is inside an engine):
module EngineName
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
:name is to define the query-param to use e.g. this would be ?q[name]=something
I know that this is not fully generic like ransack, but well...
:as is to build up the correct form-tag. :string would be for text_field, :integer for number_field and so on. I want to extend it further to implement auto-generating of collections for associations etc.
Now the block is a simple scope to use.
I run into several shortcomings with ransack when building up complex queries (like with count() etc.). Now I can specify my own optimized query in squeel.
I extended ActiveRecord::Base to set up the logic (the global one, not inside the engine. I want to use it everywhere).
I defined a scope :search so I can use Model.search(param[q]) like in ransack.
Also I tried to keep a list of keys which are "searchable" defined by the search_for calls.
class ActiveRecord::Base
##searchable_attributes = Hash.new({})
def self.search_for(name, *opts, &search_scope)
return unless search_scope
##searchable_attributes[name] = {
:type => opts[:as],
:condition => search_scope
}
unless ##searchable_attributes.has_key? :nil
##searchable_attributes[:nil] = Proc.new { scoped }
end
end
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = ##searchable_attributes[:nil].call
q.each do |key, search|
next unless base.class.searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
end
Now the issues:
It has mostly to do with inheritance of the classes. But even after reading and trying 3, 4 it does not worked.
Please take a look at the second line in the scope :search.
There I'm calling the simple Proc I definied above which only includes "scoped"
This is to get arround the issue that self returns "ActiveRecord::Base" and not the model itself like "Post" or "Comment".
It's because the scope is called on the Base class on inheritance, however I did not find anything to fix this.
As search_for is called on the model itself (e.g. Post) the scope-model returned there is "the right one".
Does anyone know how to circumvent this?
The next question would be, how to store the list of "searchable" scopes. I used ##variables. But as they are shared within every subclass, this would be a no-go.
However, it needs to be static as the search_for is called without initialize a instance (isn't it?)
Last but not least, it is somekind horrible to always specify the base-model to use on every scope so that I can chain them together.
Is there any other possibilities to improve this?
Ok, it seems I got it finally myself my putting several other answers from other questions together.
Model:
module EngineName
class Post < ActiveRecord::Base
searchable
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
My "Plugin" currently as an initializer:
class ActiveRecord::Base
def self.searchable
include Searchable
end
end
module Searchable
def self.included(base)
base.class_eval {
##searchable_attributes = Hash.new({})
def self.search_for(name, opts)
return unless block_given?
##searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
# Named scopes
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = self.scoped
q.each do |key, search|
key = key.to_sym
next unless ##searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
}
end
end
Hope it'll help some others working on the same problem.
Rails provides a helper for class_attribute. This provides inheritable class attributes, but allows subclassess to "change their own value and it will not impact parent class". However a hash which is mutated using []= for example would effect the parent, so you can ensure that a new copy is made when subclassing using rubys inherited method
Therefore you could declare and initialise on the base class like so:
module Searchable
extend ActiveSupport::Concern
included do
class_attribute :searchable_attributes
end
module ClassMethods
def inherited(subclass)
subclass.searchable_attributes = Hash.new({})
end
def search_for(name,opts)
return unless block_given?
searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
end
end
Note that I used ActiveSupport::Concern to gain the neater syntax for defining stuff directly on the class and also mixing in class methods. Then you can simply add this to active record base:
ActiveRecord::Base.send(:include, Searchable)
now any classes get their own attributes hash:
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
I'm using Virtus (basically the Property API from DataMapper) to build around a remote API, however I don't think Virtus is the problem here, I think it's my lack of understanding of what Ruby is doing.
I want to allow attributes that coerce to a given type, by using the Syntax:
Node[AnotherClass]
Which simply generates on-the-fly a subclass of Node and returns that new class. That part is working. But for some reason, it has an undesirable side-effect. All other objects that descend from Virtus::Attribute::Object are actually also Node subclasses themselves. I can't explain it, but I think it must be an expected ruby behaviour related to the inheritance model. Can anybody point me in the right direction?
(Note the following code runs without modification if you gem install virtus).
require "virtus"
class JsonModel
include Virtus
end
class Node < Virtus::Attribute::Object
attr_reader :type
class << self
def [](type)
raise ArgumentError, "Child nodes may only be other JsonModel classes" unless type <= JsonModel
#generated_class_map ||= {}
#generated_class_map[type] ||= Class.new(self) do
default lambda { |m, a| type.new }
define_method :type do
type
end
define_method :coerce do |value|
value.kind_of?(Hash) ? type.new(value) : value
end
end
end
end
end
class ChildModel < JsonModel
end
class ParentModel < JsonModel
attribute :child, Node[ChildModel]
attribute :string, String
end
# This should be String, but it's a descendant of Node??
puts ParentModel.attributes[:string].class.ancestors.inspect
I've reduced your code down to the following, and it still behaves the same.
The presence of Class.new(Node) is what causes the :string attribute to have Node in its ancestry.
require "virtus"
class JsonModel
include Virtus
end
class Node < Virtus::Attribute::Object
end
# this does it...
Class.new(Node)
class ParentModel < JsonModel
attribute :string, String
end
# This should be String, but it's a descendant of Node??
puts ParentModel.attributes[:string].class.ancestors.inspect
I'm not familiar with Virtus, but I'm guessing it's to do with the way Virtus implements attribute types.
I'm trying to implement the chain of responsibility pattern in Ruby and ActiveRecord for a polymorphic object. I'm having a few problems.
Sometimes I get an error that a method is not defined when I try to alias_method it, I think this is because the class isn't loaded or something so I explicity do a send to get the method
I get a bunch of infinite chains where the aliased function (original_method) calls method which calls original_method. I'm wondering if this is because when you alias a method that's already been overwritten, you're in essence making "original_method" a copy of the aliased method.
I'm currently working around this by having a function like "chained" return a sub-class of Setting with all the defined methods but curious why there were so many problems with alias_method right in the class.
Here's an example:
class Hospital
has_one :setting, :as => :settable
belongs_to :administrative_area
def next_link
adminstrative_area
end
def usable_setting
setting ? setting : next_link.usable_setting
end
end
Then, I have a Setting object:
class Setting < ActiveRecord::Base
belongs_to :settable, :polymorphic => true
def chained
%w(api_key active_days).each do |method|
# this is here because otherwise the method isn't defined,
# it's almost as while it's going up, the metaclass doesn't have the columns
# until it loads, probably will be fixed if we cache classes
self.send method.to_sym
(class << self; self; end).class_eval do
define_method method do |*args|
alias_method "original_#{method}", method
my_setting = send("original_#{method}")
if my_setting.nil? or my_setting.empty?
settable.next_link.usable_setting.chained.send(method)
else
return my_setting
end
end
end
end
self
end
end
You seem to be overcomplicating. Seems that you're trying to see if api_key and active_days exists, and if not, get it from somewhere else.
Here's the right way to do it, assuming that api_key and active_days are columns in your table:
class Setting < ActiveRecord::Base
belongs_to :settable, :polymorphic => true
def api_key
super || settable.next_link.usable_setting.api_key
end
def active_days
super || settable.next_link.usable_setting.active_days
end
end
You can refactor it a bit to keep clarity and remove duplication.
class Setting < ActiveRecord::Base
belongs_to :settable, :polymorphic => true
def api_key
super || next_usable_setting.api_key
end
def active_days
super || next_usable_setting.active_days
end
private
def next_usable_setting
settable.next_link.usable_setting
end
end
So in this case notice — if you have api_key/active_days available, it will get returned. Otehrwise, it will go fetch usable_setting from next_link. If that one has api_key/active_days, it will get returned, otherwise it will fetch usable_setting from next_link. Etc.