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
Related
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
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`
I have something like this:
class Post < ActiveRecord::Base
has_many :tag_members
has_many :tags, through: :tag_member
end
class Tag < ActiveRecord::Base
has_many :tag_members
has_many :posts, through: :tag_member
end
class TagMember < ActiveRecord::Base
belongs_to :tag
belongs_to :image
end
I want to track the edits on the post object. The easiest way to do this appears to be something like this:
class Post < ActiveRecord::Base
before_update :save_edits
def save_edits
# Assuming save_edit takes in a hash and persists it somehow
save_edit(self.changes)
end
end
However, from testing I've done, adding a new Tag to the has_many association on a Post does not run the before_update callback, and does not store anything in the hash returned by .changes.
What is the best way to track these types of edit as well? Should I simply overload the .tags= method to do my own storage, or is there a better way?
You could do something like this:
class TagMember < ActiveRecord::Base
after_save { |t| t.post.save }
# ^^^^
belongs_to :tag
belongs_to :image
belongs_to :post
# ^^^^
end
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
How can I delete nested objects in a form? I found out that I need to add :allow_destroy in the parent model at the accepts_nested_attributes_for directive.
Further, I want to restrict the deletion. A nested object only should be deleted, if the parent object is the only one that retains the association.
Example:
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships
end
Explanation: A company can host many internships. Therefore, I do not want to delete the company record as long as there is at least one other internship associated with it.
You could use dependent => :destroy
class Internship < ActiveRecord::Base
belongs_to :company
accepts_nested_attributes_for :company, allow_destroy => true
end
class Company < ActiveRecord::Base
has_many :internships, :dependent => :destroy
end
If you return false in a before_destroy filter, then the destroy action will be blocked. So we can check to see if there are any internships associated to the company, and block it if so. This is done in the company model.
class Company < ActiveRecord::Base
has_many :internships
before_destroy :ensure_no_internships
private
def ensure_no_internships
return false if self.internships.count > 0
end
end