Serializing Conditionally on Active Model Serializers - ruby-on-rails

invoice_serializer.rb
class InvoiceSerializer < ActiveModel::Serializer
attributes :id, :document_no, :customer_id, :currency_id, :date,
:due_date, :notes, :invoice_status_id, :total, :tax_total, :grand_total
# This is not working, associated objects are still rendered
unless #object.respond_to? :count
has_many :invoice_lines
end
has_many :invoice_payments
has_one :customer
has_one :invoice_status
end
This is my serializer class. I have two 'has_many' associations and I don't want them in a collection. I only want them in singular form. Just to be more clear, I don't want has_many association in invoices#index, it affects performance in a bad way but I want them in invoices#show, invoices#edit actions.
How do I achieve that? How can I associate models conditionally?

My gut reaction would be to create another serializer and extend this one, adding has_many :invoice_payments to it.
Then simply use the extended controller when you want the associations, and the original one when you don't.
def InvoiceIndexSerializer < InvoiceSerializer
has_many :invoice_payments
end
(NB: untested)
This way your serializer stays ignorant of an outside state like which action you're using.

Related

Rails 4 Accept nested attributes with has_one association

I have a question about Rails Nested Attributes.
I'm using Rails 4 and have this model:
model Location
has_one parking_photo
has_many cod_photos
accepts_nested_attributes_for :parking_photo
accepts_nested_attributes_for :cod_photos
end
When I use for example:
Location.find(100).update(cod_photo_ids: [1,2,3]) it works.
But Location.find(100).update(parking_photo_id: 1) doesn't works.
I don't know what difference between nested attributes has_one and has_many.
Or do we have any solution for my case, when I already have child object and want to link the parent to the child and don't want to use child update.
Thank you.
The problem has nothing to do with nested attributes. In fact you're not even using nested attributes at all in these examples.
In this example:
Location.find(100).update(cod_photo_ids: [1,2,3])
This will work even if you comment out accepts_nested_attributes_for :cod_photos as the cod_photo_ids= setter is created by has_many :cod_photos.
In the other example you're using has_one where you should be using belongs_to or are just generally confused about how you should be modeling the association. has_one places the foreign key on the parking_photos table.
If you want to place the parking_photo_id on the locations table you would use belongs_to:
class Location < ActiveRecord::Base
belongs_to :parking_photo
# ...
end
class ParkingPhoto < ActiveRecord::Base
has_one :location # references locations.parking_photo_id
end
Of course you also need a migration to actually add the locations.parking_photo_id column. I would really suggest you forget about nested attributes for the moment and just figure out the basics of how assocations work in Rails.
If you really want to have the inverse relationship and put location_id on parking_photos you would set it up like so:
class Location < ActiveRecord::Base
has_one :parking_photo
# ...
end
class ParkingPhoto < ActiveRecord::Base
belongs_to :location
validates_uniqueness_of :location_id
end
And you could reassign a photo by:
Location.find(100).parking_photo.update(location_id: 1)

How to validate a limit on each instance of the association, but not the association itself in Rails

I have a Product model and each product can have many options, such, as size and color. Each Option can also have many Choices. So the "Size" Option might have "Small," "Medium," and "Large" as Choices and the "Color" option might have "Red" and "Blue" as choices.
Using Simple Form I'm essentially trying to do something like this on the Product form:
The problem is that if the user has multiple product options (Such as size and Color), it only gives them one radio button across each set of Options. So they could select "Blue" but not "Blue" and "XL," for instance.
The other thing I could do is use as: :check_boxes instead of as: :radio_buttons but then the user could select more than one color (e.g. red and blue), when only one choice should be allowed for each option.
So what is the best "Rails" way to validate a limit on each instance of the association, but not the association itself? I could do this in javascript on the client side if I have to, but that seems less safe than having the validation on the server side.
Plus the Product should be able to have many Choices. So it's not really a validation on the association between Products and Choices, but rather a validation on limiting to 1 Choice for each set of choices that are available through the Options model.
For instance, a T-Shirt might be Red and XL, but it shouldn't be allowed to be Red & Blue + Small & XL?
Here are my models:
class Product < ApplicationRecord
has_many :choices, through: :options
end
class Option < ApplicationRecord
belongs_to :product
has_many :choices
end
class Choice < ApplicationRecord
belongs_to :option
end
If a customer is suppose to order/select a product with specifications, you may actually need a joining model (Selection/Order) instead of applying validations to the Product model. The Product model seems like it's there just for you to setup the options and choices that the user can select for that product.
If that's the actual case here, you would just create the joining model and just set it up with a polymorphic "feature." Something like this:
class Order
belongs_to :product
belongs_to :featurable, polymorphic: true
belongs_to :user
validates_inclusion_of :featurable_type, in: %w[Option Choice]
end
Newer Rails versions will validate that the belongs_to fields are present.
I say polymorphic because I'm assuming that the option may not have choices and sometimes you could just select the option itself. If all options will have choices, then just change the belongs_to :featurable to belongs_to :choice and remove the inclusions validation. The belongs_to :user is there since I assume a specific user would put in this order/selection.
If you may have multiple option choices selected for your product, then you may structure it more like this:
class ProductOrder
belongs_to :product
belongs_to :user
has_many :choice_selections
end
class ChoiceSelection
belongs_to :product_order
belongs_to :featurable, polymorphic: true
validates_inclusion_of :featurable_type, in: %w[Option Choice]
validate :unique_option
def option
featurable.is_a?(Option) ? featurable : featurable.option
end
def unique_option
return unless product_order.choice_selections.find_by(option: option).present?
errors.add(:base, 'choice option must be unique')
end
end
If all options will have choices:
You wouldn't need a polymorphic association.
class ProductOrder
belongs_to :product
belongs_to :user
has_many :choice_selections
end
class ChoiceSelection
belongs_to :product_order
belongs_to :choice
belongs_to :option
validates_uniqueness_of :option, scope: :product_order
end
However, to answer the question you've posted above:
The first thing I'd do is create a custom validation in the Product model.
Be sure to add the has_many :options line in the Product model so it looks more like this:
class Product < ApplicationRecord
has_many :options
has_many :choices, through: :options
end
Otherwise, that through may not work.
Then, add the validation like so:
class Product < ApplicationRecord
# ...
validate :one_choice_per_option
private
def one_choice_per_option
if options.any? { |option| option.choices.count > 1 }
errors.add(:options, 'can only have one choice')
end
end
# ...
end
Please note that this validation will prevent you from creating more than one choice for your product options. I do hope it gives you a better idea on how to create custom validations. I would highly recommend reevaluating your database structure to separate product/option/choice setup and user selections.
If this validation is something that you may use in other models, you may want to consider making is a validator.

Creating new object with relations without the related id

I have a rails app with the following models:
class Product < ActiveRecord::Base
has_many :stores, through: :product_store
attr_accessible :name, :global_uuid
end
class ProductStore < ActiveRecord::Base
attr_accessible :deleted, :product_id, :store_id, :global_uuid
belongs_to :product
belongs_to :store
end
Since this model is for a REST API of a mobile app, I create the objets remotely, on the devices, and then sync with this model. As this happens, it may happen that I have to create a ProductStore before having an id set for Product. I know I could batch the API requests and find some workaround, but I've settled to have a global_uuid attribute that gets created in the mobile app and synced.
What I'd like to know is how can I make this code in my controller:
def create
#product_store = ProductStore.new(params[:product_store])
...
end
be aware that it will be receiving a product_global_uuid parameter instead of a product_id parameter and have it properly populate the model.
I figure I can override ProductStore#new but I'm not sure if there's any ramification when doing that.
Overriding .new is a dangerous business, you don't want to get involved in doing that. I would just go with:
class ProductStore < ActiveRecord::Base
attr_accessible :product_global_uuid
attr_accessor :product_global_uuid
belongs_to :product
before_validation :attach_product_using_global_uuid, on: :create
private
def attach_product_using_global_uuid
self.product = Product.find_by_global_uuid! #product_global_uuid
end
end
Having these kinds of artificial attr_accessors that are only used in model creation is kind of messy, and you want to avoid passing in anything that isn't a direct attribute of the model you are creating where possible. But as you say there are various considerations to balance, and it's not the worst thing in the world.

Rails 3 Polymorphic nested attributes

I am trying to build a polymorphic relationship from a nested form that's backwards to all the examples I've found. I am hoping someone to point out the error of my ways.
class Container < ActiveRecord::Base
belongs_to :content, :polymorphic => true
end
class Notice < ActiveRecord::Base
has_one :container, :as => :content
end
class Form < ActiveRecord::Base
has_one :container, :as => :content
end
It seems most people would build a Container from a Notice or Form, but in my case the notice or form contains a small amount of content (file location or a couple db fields) so it's much dry'er to build the Notice or Form from the Container.
I thought I could solve by adding accepts_nested_attributes_for :content but that gives me an unrecognized attribute :notice when I try to create a Container with a nested Notice (looking for content, not the polymorphic association)
I can do it manually and explicitly exclude the nested fields like
if params[:container].has_key('notice')
#c = Container.new(params[:container].except(:notice))
and then build, but isn't that a smell? Is there a better way?
Thank you for reading!
Nested attributes are designed to work from the parent down to the children, not the other way around. Moreover, in this scenario, how would nested attributes know whether you are trying to create a Notice or Form object?
If you find it DRYer to build the content from the container, you probably have your associations inside out - try changing your schema to:
class Container < ActiveRecord::Base
has_one :notice
has_one :form
end
class Notice < ActiveRecord::Base
belongs_to :container
end
class Form < ActiveRecord::Base
belongs_to :container
end
You can use validation to ensure only one child (:notice or :form) is actually associated if need be.

has_and_belongs_to_many and accepts_nested_attributes_for

class Trip
has_many :trip_places
has_many :places, through: :trip_places
accepts_nested_attributes_for :places
end
class Place
has_many :trip_places
has_many :trips, through: :trip_places
validates :name, uniqueness: true
end
class TripPlace
belongs_to :trip
belongs_to :place
end
So we got a trip which has many places through trip places, and accepts nested attributes for places. Also places must be unique by name.
I'd like to have the following functionality though, and can't find an elegant solution to it:
Let's say we create a trip T, with two places P1 = 'hawaii' and P2 = 'costa rica'
If I edit the trip, and change hawaii to bora bora, it will modify the Place.
The problem is that I'd like to create a new place called bora bora and modify the TripPlace model to update the place_id with the new one.
Same thing goes to destroy, if I destroy a place in the form, I'd like to remove only the reference from the TripPlace, and not the actual Place
And of course, the create functionality should be alike, if the place exists, just create the TripPlace reference.
Right now, I don't think that accepts_nested_attributes_for really helps, but can't think of a good solution for this

Resources