I have three serializers, nested within each other. Like this:
class PersonSerializer < ActiveModel::Serializer
attributes :id :name
has_many: companies
class Company < ActiveModel::Serializer
has_many :products
class ProductSerializer < ActiveModel::Serializer
has_many :product_items do
unless person.id != object.company.user_id
object.product_items
end
end
end
end
end
My issue is the line: unless person.id != object.company.user_id.
person is undefined here. How do I get access to the current person instance within the ProductSerializer?
I am unsure why would you need that structure, but general class definition declares a new scope. Whether you need to capture variables from the parent scope, use closure with Class#new instead:
class PersonSerializer < ActiveModel::Serializer
attributes :id :name
has_many: companies
Company = Class.new(ActiveModel::Serializer) do
has_many :products
ProductSerializer = Class.new(ActiveModel::Serializer) do
has_many :product_items do
unless person.id != object.company.user_id
object.product_items
end
end
end
end
end
Related
Presenting a hypothetical, simplified version of my problem. Imagine I have a website where users can create a Shop with their own branding and choose from a catalog of Products to show in their Shop. Shops & Products have a has-and-belongs-to-many (HABTM) relationship. Each Product has its own Shop-specific route.
Rails.application.routes.draw do
resources :shops do
resources :products
end
end
class ShopSerializer < ActiveModel::Serializer
has_many :products
end
class ProductSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attribute :url do
shop_product_url(NEED SHOP ID, product_id: object.id)
end
end
When a Shop is serialized, and as a result, so is the collection of its Products, I want the Product serializer to be aware of the shop that is serializing it and use that to include the route in the serialized output. How is this possible? I've tried all manner of passing instance_options from the ShopSerializer but it doesn't work as expected.
# this works except is apparently not threadsafe as multiple
# concurrent requests lead to the wrong shop_id being used
# in some of the serialized data
has_many :products do
ActiveModelSerializers::SerializableResource.new(shop_id: object.id).serializable_hash
end
# shop_id isn't actually available in instance_options
has_many :products do
ProductSerializer.new(shop_id: object.id)
end
Unfortunately serializer associations do not seem to provide a clean way to pass in custom attributes to the child serializers. There are a few not-so-pretty solutions though.
1. Invoke ProductSerializer manually, add URL in ShopSerializer
class ProductSerializer < ActiveModel::Serializer
end
class ShopSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attribute :products do
object.products.map do |product|
ProductSerializer.new(product).serializable_hash.merge(
url: shop_product_url(object.id, product_id: product.id)
)
end
end
end
2. Add shop ID to the Product instances before they are fed to ProductSerializer
class ProductSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attribute :url do
shop_product_url(object.shop_id, product_id: object.id)
end
end
class ShopSerializer < ActiveModel::Serializer
has_many :products, serializer: ProductSerializer do
shop = object
shop.products.map do |product|
product.dup.tap do |instance|
instance.singleton_class.send :define_method, :shop_id do
shop.id
end
end
end
end
end
Both solutions should be thread safe, but the first solution seems like a better idea to me, as the second one makes ProductSerializer unusable on its own — i.e. when just a single Product is serialized without knowing the particular shop it should belong to.
I have an hierarchical structure in my app:
Environment has Containers
Container has Items
Item has expression
so my Model code looks like:
class Environment < ActiveRecord::Base
has_many :containers, :dependent => :destroy
def as_json(options = {})
super(options.merge(include: :containers))
end
end
class Container < ActiveRecord::Base
has_many :items, :dependent => :destroy
belongs_to :environment
def as_json(options = {})
super(options.merge(include: :items))
end
end
class Item < ActiveRecord::Base
has_many :expressions, :dependent => :destroy
belongs_to :container
def as_json(options = {})
super(options.merge(include: :expressions))
end
end
class Expression < ActiveRecord::Base
belongs_to :item
def as_json(options = {})
super()
end
end
In a regular get of a record I usually need only one hierarchy below the desired record, that's why in the as_json I merge only one hierarchy down (get Environment will return a collection of containers but those containers will not have Items)
My Question:
Now what I need is to add a method to the controller that allows full hierarchy response i.e. GET /environment/getFullHierarchy/3 will return: environment with id=3 with all its containers and for every container all it's Items & for every Item all it's expressions. without breaking the current as_json
I'm kinda new to Rails, wirking with Rails 4.2.6 & don't know where to start - can anyone help?
Sure it goes something like this hopefully you get the idea.
EnvironmentSerializer.new(environment) to get the hierarchy json.
lets say environments table has columns environment_attr1 , environment_attr2
class EnvironmentSerializer < ActiveModel::Serializer
attributes :environment_attr1, :environment_attr2 , :containers
# This method is called if you have defined a
# attribute above which is not a direct value like for
# a rectancle serializer will have attributes length and width
# but you can add a attribute area as a symbol and define a method
# area which returns object.length * object.width
def containers
ActiveModel::ArraySerializer.new(object.containers,
each_serializer: ContainerSerializer)
end
end
class ContainerSerializer < ActiveModel::Serializer
attributes :container_attr1, :container_attr2 , :items
def items
ActiveModel::ArraySerializer.new(object.items,
each_serializer: ItemSerializer)
end
end
class ItemSerializer < ActiveModel::Serializer
...
end
class ExpressionSerializer < ActiveModel::Serializer
...
end
I am currently making API with RoR, and I need to create an object with virtual attributes and associated object.
The problem is that serializer does not kick in when I return an object with virtual attribute.
Here is the returned object from foo_controller
{
:id=>280,
:virtual=>"y8st07ef7u"
:user_id=>280
}
:virtual is a virtual attribute and user_id is an id of associated table - User.
My goal is to make this
{
:id=>280,
:virtual=>"y8st07ef7u",
:user=>{
:id=>280,
:name=>'foo'
}
}
Foo_controller setting
class Api::V1::FoosController < ApplicationController
foos = Foo.all
foos.each do |foo|
foo.set_attribute('y8st07ef7u')
end
render json: foos.to_json(:methods => :virtual), status: 200
end
Foo_model setting
class Foo < ActiveRecord::Base
belongs_to :user
attr_accessor:virtual
def set_attribute(path)
self.virtual = path
end
end
Foo_serializer setting
class FooSerializer < ActiveModel::Serializer
attributes :id, :virtual
has_one :user
end
Foo migration setting
class CreateFoos < ActiveRecord::Migration
def change
create_table :foo do |t|
t.references :user
end
end
end
user model
class User < ActiveRecord::Base
has_many :foos
end
user serializer
class UserSerializer < ActiveModel::Serializer
attributes :id, :name
belongs_to :foo
end
When I replace "foo.to_json(:methods => :virtual)" in foo_controller with "foos", serializer kicks in and I get a user object inside the returned json instead of user_id, but :virtual is not in the json.
Are there any ways I can get an object with both virtual attributes and associated object using active model serializer.
Thank you in advance for your help!
I figured out. It was very simple.
I just had to add ":virtual" to attributes in the foo_serializer and replace "foo.to_json(:methods =>:virtual)" with just "foos"
My problem:
I have Three models: Company, Parent, and Child.
Child belongs_to Parent which belongs_to Company
I need to get all children where a certain attribute is set to false, within one company. (The Parent model has a company_id while the Child model does not.)
What I'm trying:
I have the following join:
#objects = Parent.joins(:childs).where('parents.company_id' => current_user.company_id, 'childs.foo' => false)
In my view:
<!-- This should be a list of child objects -->
<% #objects.each do |obj| %>
<%= obj.foo %>
<% end %>
(foo being the attribute of the child object)
Models:
class Company < ActiveRecord::Base
has_many :parents, :dependent => :destroy
...
end
class Parent < ActiveRecord::Base
has_many :childs, :dependent => :destroy
belongs_to :company
...
end
class Child < ActiveRecord::Base
belongs_to :parent
...
end
However, writing the Parent.joins(:childs)... returns an ActiveRecord relation of Parent objects. (Throwing an error when I try to access the child attributes) I need the end list to be of child objects. But I am finding it difficult to do so.
A good answer to this question would be one that:
Solved this problem in another way that made more sense while not being too computationally intensive.
Or something that shows how to get a list/relation of child objects instead of the parent objects.
Simple, start with the Child class:
Child.joins(:parent).where(parents: {company_id: current_user.company_id}, foo: false)
I would probably advise using scopes/class methods to accomplish this in a cleaner fashion:
class Parent < ActiveRecord::Base
def self.for_user(user)
where(company_id: user.company_id)
end
end
class Child < ActiveRecord::Base
scope :fooless, ->{where foo: false}
end
Now you can do this:
Child.joins(:parent).merge(Parent.for_user(current_user)).fooless
Using Rails 3.2, I have the following:
# shop.rb
class Shop < ActiveRecord::Base
has_many :nearby_shops
after_update :find_nearby_shops
def find_nearby_shops
NearbyShop.create(
:shop_id => self.id,
:shop_type => "test",
)
end
end
# nearby_shop.rb
class NearbyShop < ActiveRecord::Base
attr_accessible :shop_id, :shop_type
belongs_to :shop
end
I find it was necessary to declare shop_id in attr_accessible to get the shop_id saved, else it would be blank. Is this behavior correct?
Yes, it is. Otherwise you could use
def find_nearby_shops
self.nearby_shops.create(
shop_type: "test"
)
end
self.nearby_shops will scope to the nearby_shops with current shop it and bypasses the protected attributes because you are not using a mass assignment method (such as create) to assign the shop_id.