I'm somewhat confused by my options for custom validations in Rails 3, and i'm hoping that someone can point me in the direction of a resource that can help with my current issue.
I currently have 3 models, vehicle, trim and model_year. They look as follows:
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
end
class ModelYear < ActiveRecord::Base
attr_accessible :value
has_many :model_year_trims
has_many :trims, :through => :model_year_trims
end
class Trim < ActiveRecord::Base
attr_accessible :value, :model_id
has_many :vehicles
has_many :model_year_trims
has_many :model_years, :through => :model_year_trims
end
My query is this - when I am creating a vehicle, how can I ensure that the model_year that is selected is valid for the trim (and vice versa)?
you can use custom validation method, as described here:
class Vehicle < ActiveRecord::Base
validate :model_year_valid_for_trim
def model_year_valid_for_trim
if #some validation code for model year and trim
errors.add(:model_years, "some error")
end
end
end
You can use the ActiveModel::Validator class like so:
class VehicleValidator < ActiveModel::Validator
def validate(record)
return true if # custom model_year and trip logic
record.errors[:base] << # error message
end
end
class Vehicle < ActiveRecord::Base
attr_accessible :make_id, :model_id, :trim_id, :model_year_id
belongs_to :trim
belongs_to :model_year
include ActiveModel::Validations
validates_with VehicleValidator
end
Related
I am using Rails 5.0.1, and am really confused about the following problem. I have few models with polymorphic associations.
class Container < ApplicationRecord
has_many :steps, as: 'parent', dependent: :destroy
end
class Step < ApplicationRecord
belongs_to :parent, polymorphic: true
belongs_to :implementation, polymorphic: true
end
class FirstStep < ApplicationRecord
has_one :step, as: 'implementation'
has_many :params, dependent: :destroy
end
class SecondStep < ApplicationRecord
has_one :step, as: 'implementation'
has_many :headers, dependent: :destroy
end
class Param < ApplicationRecord
belongs_to :first_step
end
class Header < ApplicationRecord
belongs_to :second_step
end
A step associates to an implementation (FirstStep, SecondStep). In addition to it, a container can also be a step's implementation. I'm using Active Model Serializers to serialize the model info to JSON. Following is the related code to serializers.
class StepSerializer < ActiveModel::Serializer
attributes :id, :implementation_type, :implementation_id, :active, :position
belongs_to :implementation
end
class FirstStepSerializer < ActiveModel::Serializer
attributes :name, :params_attributes
def params_attributes
object.params.map { |p| ParamSerializer.new(p).attributes }
end
end
class SecondStepSerializer < ActiveModel::Serializer
attributes :id, :title, :headers_attributes
def headers_attributes
object.headers.map { |p| HeaderSerializer.new(p).attributes }
end
end
class ParamSerializer < ActiveModel::Serializer
attributes :id
end
class HeaderSerializer < ActiveModel::Serializer
attributes :id
end
The implementations of step model can have different attributes, as specified in the model. The problem is, when I write
render json: container.steps
it fires N+1 queries to get the results. How do I optimize it?
Edit 1
Inspired by this answer, I tried to separate objects by their implementation_type, and it worked. What I did was:
# my controller action
def index
steps = []
steps += container.steps.where(implementation_type: 'FirstStep').includes(implementation: [:params])
steps += container.steps.where(implementation_type: 'SecondStep').includes(implementation: [:headers])
render json: steps
end
This prevented the N+1 queries for fetching params and headers, but it doesn't work if a step is a container.
Change your FirstStepSerializer and SecondStepSerializer serializer like following
class FirstStepSerializer < ActiveModel::Serializer
attributes :name
has_many :params, :serializer => ParamSerializer
end
class SecondStepSerializer < ActiveModel::Serializer
attributes :id, :title
has_many :headers, :serializer => HeaderSerializer
end
This might help
I have the following models:
class Property < ActiveRecord::Base
belongs_to :property_type
has_many :variant_properties
has_many :variants, through: :variant_properties
end
class PropertyType < ActiveRecord::Base
has_many :properties
end
class Variant < ActiveRecord::Base
has_many :variant_properties
has_many :properties, through: :variant_properties
end
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
validates_uniqueness_of :property, scope: :property_type
end
What I am trying to validate is that two Properties for the same Variant should never belong to the same Property_Type.
Is there any way to perform this validation in a Rails way?
EDIT:
Finally solved using a custom validator, as suggested by #qaisar-nadeem. A redundant column would be also ok, but I would consider it an optimization more than a solution.
class Property < ActiveRecord::Base
(...)
validate :property_type_uniqueness
private
def property_type_uniqueness
unless property_type_unique?
msg = 'You cannot have multiple property variants with same property type'
errors.add(:property_id, msg)
end
end
def property_type_unique?
VariantProperty
.where(variant: variant)
.select { |vp| vp.property.property_type == property.property_type }
.empty?
end
end
Validates scope cannot access joined table so you will need to have custom validation.
So there are two options.
Option 1 : Use a custom validator and have SQL check if there is any Property Variant with same Property Type.
Custom Validations guide can be found on http://guides.rubyonrails.org/active_record_validations.html#custom-validators
Option 2 : Add a redundant column property_type_id in variant_properties model and then add validates_uniqueness_of :property, scope: :property_type as you already have done
UPDATE
Here is the custom validator
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
validate :check_property_type_uniqueness
def check_property_type_uniqueness
errors.add(:property_id, "You cannot have multiple property variants with same property type") if VariantProperty.joins(:property).where(:property_id=>self.property_id,:variant_id=>self.variant_id,:properties=>{:property_type_id=>self.property.property_type_id}).count > 0
end
end
your should add relation with PropertyType to VariantProperty
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
has_one :property_type, through: :property
validates :property_type, uniqueness: { scope: :variant_id }
end
I have three models which have been defined as follows:
Answer Sheet
class AnswerSheet < ActiveRecord::Base
has_many :answer_sections
accepts_nested_attributes for :answer_sections
end
Answer Section
class AnswerSection < ActiveRecord::Base
belongs_to :answer_sheet
has_many :answers
accepts_nested_attributes_for :answers
end
Answers
class Answers < ActiveRecord::Base
belongs_to: answer_section
end
I also have the following method defined in the AnswerSheet model
def self.build_with_answer_sections
answer_sheet = new # new should be called on the class e.g. AnswerSheet.new
4.times do |n|
answer_sheet.answer_sections.build
end
answer_sheet
end
How would I go about making it so that when I make a new instance of the the AnswerSheet, I can also generate all it's dependent models as well?
You can use the after_initialize callback
class AnswerSheet < ActiveRecord::Base
has_many :answer_sections
accepts_nested_attributes for :answer_sections
after_initialize :add_answer_section
def add_answer_section
4.times {self.answer_sections.build }
end
end
class AnswerSection < ActiveRecord::Base
belongs_to :answer_sheet
has_many :answers
accepts_nested_attributes_for :answers
after_initialize :add_answer
def add_answer
2.times {self.answers.build}
end
end
This question pertains to AMS 0.8
I've got two models:
class Subject < ActiveRecord::Base
has_many :user_combinations
has_ancestry
end
class UserCombination < ActiveRecord::Base
belongs_to :stage
belongs_to :subject
belongs_to :user
end
And two serializers:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id
belongs_to :stage
belongs_to :subject
end
class SubjectSerializer < ActiveModel::Serializer
attributes :id, :name, :description, :subjects
def include_subjects?
object.is_root?
end
def subjects
object.subtree
end
end
When a UserCombination is serialized, I want to embed the whole subtree of subjects.
When I try to use this setup I get this error:
undefined method `belongs_to' for UserCombinationSerializer:Class
I tried changing the UserCombinationSerializer to this:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id, :subject, :stage
end
In this case I get no errors, but the subject is serialized in the wrong way - not using the SubjectSerializer.
My questions:
Shouldn't I be able to use a belongs_to relation in the serializer?
If not - how can I get the wanted behaviour - embedding the subject tree using the SubjectSerializer?
This is not really elegant but it seems to be working :
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id, :stage_id, :subject_id
has_one :subject
end
I don't really like calling has_one whereas it's actually a belongs_to association :/
EDIT: Disregard my comment about has_one/belongs_to ambiguity, the doc is actually pretty clear about it: http://www.rubydoc.info/github/rails-api/active_model_serializers/frames
In Active Model Serializer 0-10-stable, belongs_to is now available.
belongs_to :author, serializer: AuthorPreviewSerializer
belongs_to :author, key: :writer
belongs_to :post
belongs_to :blog
def blog
Blog.new(id: 999, name: 'Custom blog')
end
https://github.com/rails-api/active_model_serializers/blob/0-10-stable/docs/general/serializers.md#belongs_to
So you could do:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :id
belongs_to :stage, serializer: StageSerializer
belongs_to :subject, serializer: SubjectSerializer
end
What if you try with something like this:
class UserCombinationSerializer < ActiveModel::Serializer
attributes :subject,
:stage,
:id
def subject
SubjectSerializer.new(object.subject, { root: false } )
end
def stage
StageSerializer.new(object.stage, { root: false } )
end
end
I'm getting the error "uninitialized constant Assignment::AssignmentsCourse". Here are my models:
assignment.rb
class Assignment < ActiveRecord::Base
has_many :assignmentsCourses
has_many :courses, :through => :assignmentsCourses
attr_accessible :name, :dateAssigned, :dateDue, :description, :weight, :category_tokens
attr_reader :category_tokens
def category_tokens=(ids)
puts 'el ids: ', ids.split(",")
self.courseIds = ids.split(",")
end
end
course.rb
class Course < ActiveRecord::Base
has_and_belongs_to_many :assignments
end
AssignmentCourse.rb
class AssignmentCourse < ActiveRecord::Base
belongs_to :assignment
belongs_to :course
attr_accessible :assignment_id, :course_id
end
has_many :assignmentsCourses
This and all of your fields should not be camel cased it is not ruby style and it breaks the class loading. The end should only be pluralized too, not both words. Behind the scenes activerecord depluralizes the symbol you provide and does class loading similar to require. If you tried require 'activeRecord' that would not work for example. Ruby uses underscores to derive multi word class names.
It should be:
has_many :assignment_courses
Change the has many though too. Your accessors should not be camel cased either ruby_style_is_to_underscore.