say I have this serializer
class FooSerializer < ActiveModel::Serializer
attributes :this, :that, :the_other
def this
SomeThing.expensive(this)
end
def that
SomeThing.expensive(that)
end
def the_other
SomeThing.expensive(the_other)
end
end
Where the operations for the individual serialized values is somewhat expensive...
And then I have another serializer that whats to make use of that, but not return all of the members:
class BarSerializer < FooSerializr
attributes :the_other
end
This does not work... BarSerializer will still have this, that, and the_other...
How can I make use of inheritance but not automatically get the same attributes? I am looking for a solution other than module mixins.
Turns out the answer to this is to make use of the magic include_xxx? methods...
class BarSerializer < FooSerializer
def include_this?; false; end
def include_that?; false; end
end
This will make it only serialize "the_other"
Make the BarSerializer the parents class and put the method the_other in it. FooSerializer will inherits only the method and the attribute defined in BarSerializer.
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.
Using Rails 3.2.17. I have the following in my model:
class Shop < ActiveRecord::Base
before_create :set_next_position
private
def set_next_position
self.position = self.class.where(country_id: country_id).
maximum(:position).to_i + 1
end
end
self is a Shop object. Note the line self.class.where... which is equivalent to Shop.where.... In this case, I don't know what is the best practice - to use Shop.where... or self.class.where...? It is code smell to me.
I would say self.class.where is better than Shop.where inside the class body. This way, you won't have to change inside, if for some reason you want to rename the class and so on.
The only difference that I now about is in case of inheritance. When you have:
class Base
def self.example
42
end
def run_example
Base.example
end
end
class A < Base
def self.example
'not 42'
end
end
A.new.run_example
=> 42
So when don't have inheritance I prefer Base.example. In the other case self.class.example.
I'm trying to include a method from a helper module into an ActiveModel::Serializer subclass but for some reason the method is not showing up.
Here's my simple helper module:
module Helpers
module Serialisers
def enforce_zulu_time(attribute)
define_method(attribute) do
object.send(attribute).utc.iso8601 unless object.try(attribute).nil?
end
end
end
end
And here's my test serialiser
class TestSerialiser < ActiveModel::Serializer
include Helpers::Serialisers
attributes :updated_at
enforce_zulu_time :updated_at
end
and my simple object to serialise
class TestItem
include ActiveModel::SerializerSupport
attr_reader :updated_at
def initialize
#updated_at = Time.now.utc
end
end
and my test
describe Helpers::Serialisers do
let(:item) { TestItem.new }
let(:serialiser) { TestSerialiser.new(item) }
subject { serialiser.attributes }
it { expect(subject[:updated_at]).to be_zulu_time}
end
results in
`<class:TestSerialiser>': undefined method `enforce_zulu_time' for TestSerialiser:Class (NoMethodError)
However if I just do this in my TestSerialiser instead
class TestSerialiser < ActiveModel::Serializer
attributes :updated_at
['updated_at'].each do |attribute|
define_method(attribute) do
object.send(attribute).utc.iso8601 unless object.send(attribute).blank?
end
end
end
it all works fine.
Why is my enforce_zulu_time method not being included?
Replace include Helpers::Serialisers with extend Helpers::Serialisers since you expect class methods.
Another solution would be to use ActiveSupport::Concern, see doc
Sidenote
In order to have your code flexible for free, I recommend you to create your own base class for your serializers, like:
class BaseSerializer < ActiveModel::Serializer
end
Then have all your serializers inherit from it. This way you can add features easily.
I have a model base_table, and I have a extended_table which has extra properties to further extend my base_table. (I would have different extended_tables, to add different properties to my base_table, but that's non-related to the question I'm asking here).
The model definition for my base_table is like:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def self.included(base)
base.belongs_to :base_table, autosave:true, dependent: :destroy
# do something more when this module is included
end
end
end
And the model definition for my extended_table is like:
class TennisQuestionaire < ActiveRecord::Base
include BaseTable::BaseTableInclude
end
Now I what I want is the code below:
params = {base_table: {name:"Songyy",age:19},tennis_ball_num:3}
t = TennisQuestionaire.new(params)
When I created my t, I want the base_table to be instantiated as well.
One fix I can come up with, is to parse the params to create the base_table object, before TennisQuestionaire.new was called upon the params. It's something like having a "before_new" filter here. But I cannot find such kind of filter when I was reading the documentation.
Additionally, I think another way is to override the 'new' method. But this is not so clean.
NOTE: There's one method called accepts_nested_attributes_for, seems to do what I want, but it doesn't work upon a belongs_to relation.
Any suggestions would be appreciated. Thanks :)
After some trails&error, the solution is something like this:
class BaseTable < ActiveRecord::Base
module BaseTableInclude
def initialize(*args,&block)
handle_res = handle_param_args(args) { |params| params[:base_table] = BaseTable.new(params[:base_table]) }
super(*args,&block)
end
private
def handle_param_args(args)
return unless block_given?
if args.length > 0
params = args[0]
if (params.is_a? Hash) and params[:base_table].is_a? Hash
yield params
end
end
end
end
end
I have a (cut-down) Model which looks like this:
class MyModel < ActiveRecord::Base
def self.update_status
event_time = time_ago_in_words 30.minutes.from_now
Twitter.update("Event is starting in #{event_time}")
end
end
As expected, I am getting a NoMethodError exception due to trying to use a method 'time_ago_in_words' from DateHelper. How should I accomplish this, and maybe more importantly, am I going about this the correct way?
extend ActionView::Helpers::DateHelper in your model
Change 30.mins.from_now to 30.minutes.from_now
I just tried it myself and have no problem doing the following:
class MyModel < ActiveRecord::Base
extend ActionView::Helpers::DateHelper
def self.update_status
event_time = time_ago_in_words(30.minutes.from_now)
Twitter.update("Event is starting in #{event_time}")
end
end
You have to use extend instead of include. See this article for an explanation.