How to validate has_one relationship? - ruby-on-rails

I have these two models
User.rb
has_one :bike
Bike.rb
belongs_to :user
If a user tries to create multiple bikes, then the has_one relation doesn't make sense.
How can I add validation for this condition in the model level?
How can I make sure that the user will be always having one bike?

Create a before_create callback in bike.rb file. Check if the current_user.bike has any record or not. If record exists then add an error and return.
class Bike < ApplicationRecord
# Associations
has_one :user
before_create :user_exists
private
def user_exists
if self.user.bike.present?
errors[:base] << "Add your validation message here"
return false
end
end
end

You can simply add a validates_uniqueness_of call on your Bike model.
class Bike < ApplicationRecord
belongs_to :user
validates_uniqueness_of :user_id
end

Related

Representing a has_many relationship in Rails with an abstract class

I have a relationship which I am having a hard time modelling.
I have a Subscription class which is a regular ActiveRecord model and it can have one or more PaymentSources. The problem however is that a payment source could refer to either a CreditCard or a BankAccount.
Given that these models have very different data associated with them I don't feel as though STI is a good option here. So I was wondering if there is an established or recommended approach for a situation in Rails where a model has_many of another model which is actually an abstraction for 2 or more classes which don't share the same data layout.
Ideally, in this particular example I could say something like subscription.payment_source.default and have it refer to either a CreditCard or a BankAccount depending on what the user had selected as their preferred billing method.
TLDR:
[Updated] After some pondering, I will do Option 2 (the more complete solution) which is future-proof flexible, but if you don't need all of this complexity, I'll do just Option 1.
Option 1:
class Subscription < ApplicationRecord
belongs_to :credit_card
belongs_to :bank_account
def payment_sources
[credit_card, bank_account].compact
end
def default_payment_source
case user.preferred_billing_method # assuming you have an integer column in users table called `preferred_billing_method`
when 0 then credit_card # asssuming 0 means "Credit Card"
when 1 then bank_account # assuming 1 means "Bank Account"
else NotImplementedError
end
end
end
Usage
Subscription.first.default_payment_source
# => returns either `CreditCard` or `BankAccount`, or `nil`
Subscription.first.payment_sources.first
# => returns either `CreditCard` or `BankAccount`, or `nil`
Option 2:
class User < ApplicationRecord
belongs_to :default_payment_source, class_name: 'PaymentSource'
has_many :subscriptions
end
class Subscription < ApplicationRecord
belongs_to :user
has_many :payment_sources_subscriptions
has_many :payment_sources, through: :payment_sources_subscriptions
end
# This is just a join-model
class PaymentSourcesSubscription < ApplicationRecord
belongs_to :subscription
belongs_to :payment_source
validates :subscription, uniqueness: { scope: :payment_source }
end
# this is your "abstract" model for "payment sources"
class PaymentSource < ApplicationRecord
belongs_to :payment_sourceable, polymorphic: true
has_many :payment_sources_subscriptions
has_many :subscriptions, through: :payment_sources_subscriptions
validates :payment_sourceable, uniqueness: true
end
class CreditCard < ApplicationRecord
has_one :payment_source, as: :payment_sourceable
end
class BankAccount < ApplicationRecord
has_one :payment_source, as: :payment_sourceable
end
Usage:
User.first.default_payment_source.payment_sourceable
# => returns either `CreditCard` or `BankAccount`, or `nil`
Subscription.first.payment_sources.first.payment_sourceable
# => returns either `CreditCard` or `BankAccount`, or `nil`

How can I validate an associated model?

Is my first time building a custom validation , since trying to the regular ORM validations did not work. I have a model called AdGroup which belongs to another model called Car. I want to send prevent that a user creates a new Ad Group if they have not selected a car. Also the Car is a file.
class AdGroup < ActiveRecord::Base
belongs_to :car
validate :validate_car_id
def validate_car_id
car = Car.find_by(id: params[:id])
if car.nil?
errors.add(:car, "Select a car image")
end
end
end
class Car < ActiveRecord::Base
validates :make, :model, :year, presence: true
validates :file, presence: true
belongs_to :make
has_many :ad_groups
...
end
Is an image that I am trying to select .
Your Ad Group model needs to be associated with the Car model. AdGroup models should have belongs_to :car line and the Car model needs has_many :ad_groups.
To validate associated models you could use ActiveRecord's validates_associated. Be sure to read the docs by the link for gotchas.
class AdGroup < ActiveRecord::Base
belongs_to :car
validates :car, presence: true
validates_associated :car
end
When using validates_associated you don't have to do custom validation.

ActiveRecord polymorphic association with unique constraint

I have a site that allows users to log in via multiple services (LinkedIn, Email, Twitter, etc..).
I have the below structure set up to model a User and their multiple identities. Basically a user can have multiple identieis, but only one of a given type (e.g. can't have 2 Twitter identiteis).
I decided to set it up as a polymorphic relationship, as drawn below. Basically there's a middle table identities that maps a User entry to multiple *_identity tables.
The associations are as follows (shown only for LinkedInIdentity, but can be extrapolated)
# /app/models/user.rb
class User < ActiveRecord::Base
has_many :identities
has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity"
...
end
# /app/models/identity
class Identity < ActiveRecord::Base
belongs_to :user
belongs_to :identity, polymorphic: true
...
end
# /app/models/linkedin_identity.rb
class LinkedinIdentity < ActiveRecord::Base
has_one :identity, as: :identity
has_one :user, through: :identity
...
end
The problem I'm running into is with the User model. Since it can have multiple identities, I use has_many :identities. However, for a given identity type (e.g. LinkedIn), I used has_one :linkedin_identity ....
The problem is that the has_one statement is through: :identity, and there's no singular association called :identity. There's only a plural :identities
> User.first.linkedin_identity
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :identity in model User
Any way around this?
I would do it like so - i've changed the relationship name between Identity and the others to external_identity, since saying identity.identity is just confusing, especially when you don't get an Identity record back. I'd also put a uniqueness validation on Identity, which will prevent the creation of a second identity of the same type for any user.
class User < ActiveRecord::Base
has_many :identities
has_one :linkedin_identity, through: :identity, source: :identity, source_type: "LinkedinIdentity"
end
# /app/models/identity
class Identity < ActiveRecord::Base
#fields: user_id, external_identity_id
belongs_to :user
belongs_to :external_identity, polymorphic: true
validates_uniqueness_of :external_identity_type, :scope => :user_id
...
end
# /app/models/linkedin_identity.rb
class LinkedinIdentity < ActiveRecord::Base
# Force the table name to be singular
self.table_name = "linkedin_identity"
has_one :identity
has_one :user, through: :identity
...
end
EDIT - rather than make the association for linkedin_identity, you could always just have a getter and setter method.
#User
def linkedin_identity
(identity = self.identities.where(external_identity_type: "LinkedinIdentity").includes(:external_identity)) && identity.external_identity
end
def linkedin_identity_id
(li = self.linkedin_identity) && li.id
end
def linkedin_identity=(linkedin_identity)
self.identities.build(external_identity: linkedin_identity)
end
def linkedin_identity_id=(li_id)
self.identities.build(external_identity_id: li_id)
end
EDIT2 - refactored the above to be more form-friendly: you can use the linkedin_identity_id= method as a "virtual attribute", eg if you have a form field like "user[linkedin_identity_id]", with the id of a LinkedinIdentity, you can then do #user.update_attributes(params[:user]) in the controller in the usual way.
Here is an idea that has worked wonderfully over here for such as case. (My case is a tad diffferent since all identites are in the same table, subclasses of the same base type).
class EmailIdentity < ActiveRecord::Base
def self.unique_for_user
false
end
def self.to_relation
'emails'
end
end
class LinkedinIdentity < ActiveRecord::Base
def self.unique_for_user
true
end
def self.to_relation
'linkedin'
end
end
class User < ActiveRecord::Base
has_many :identities do
[LinkedinIdentity EmailIdentity].each do |klass|
define_method klass.to_relation do
res = proxy_association.select{ |identity| identity.is_a? klass }
res = res.first if klass.unique_for_user
res
end
end
end
end
You can then
#user.identities.emails
#user.identities.linkedin

How to validate a table in Rails using another table

Using Ruby associations, I have two models that are associated to each other
class Event < ActiveRecord::Base
belongs_to :post
and
class Post < ActiveRecord::Base
belongs_to :event
The Post table has a boolean column titled :anon. I want to create a validation when trying to create an event. If the anon field is true then the associated event is valid, otherwise an event is not valid.
Is there a way I can do this using Rails associations?
You can use before_create callback here, for this purpose, something like following:
class Post < ActiveRecord::Base
belongs_to :event
before_save :validate_event
def validate_event
anon ? event.valid : event.invalid
end
Try This:
Post.rb
class Post < ActiveRecord::Base
belongs_to :event
before_save :validate_event
def validate_event
if self.anon == true
validates :event, presence: true
end
end
I didn't test this but hope it will work ..
First, your association needs to be corrected.
You have belongs_to on both sides of relationship; while in contrary, you should use has_one or has_many at one side depending on the type of association you wish to achieve.
Read more about association basics.
You can then use validates_associated in Event to validate the associated post, or create a custom before_save hook to do that.
class Post < ActiveRecord::Base
has_many :events
end
class Event < ActiveRecord::Base
belongs_to :post
before_create :validate_post
private
def validate_post
#since it is boolean, it will return `true` or `false`,
#If false, validation will fail, else pass
post.anon
end
end

more than one entry saved on has_one association rails

I am trying to create a has_one association among two model.
class User < ActiveRecord::Base
has_one :emergency_contact
end
class EmergencyContact < ActiveRecord::Base
belongs_to :user
end
when i try to test it through rails console more than one entries are saved for the emergency contact model for a single user. Although when i retrieve it using User.emergency_contact only the first entry is returned. When saving how can i make it to rollback for more than one entry
You can simply validate uniqueness of user_id column in EmergencyContact:
class EmergencyContact < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :user_id, allow_nil: true
end

Resources