How to validate that a dependent row belongs to a user - ruby-on-rails

I have a model called Category;
class Category < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :name, presence: true
validates :user_id, presence: true
end
And, I have a model called Group:
class Group < ActiveRecord::Base
belongs_to :user
has_many :categories
validates :name, presence: true
validates :user_id, presence: true
end
As you can see, a group can have many categories. When a user adds a category or updates it's group_id value, I want to check that the group belongs to that user. I don't want users adding to and update categories to another user's group. What is best practise to validate this prior to saving? Thanks

validate :ownership_of_group
def ownership_of_group
ids = []
ids << self.group_id
ids << self.group_id_was if self.group_id_was.present?
if(Group.find(ids).reject {|group| group.user_id == self.user_id}.present?)
# if all of them is owned by the user the array will return an empty array
errors.add(:user_id, 'You cant edit a category that is not yours')
end
end
If we say group_id we get the current value that is being set by the use.
If we say group_id_was it get the old value before the update.
In the update we need to handle both in the create we have no previous value.

Related

Rails: Ensure phone number is unique checking several columns

I have two columns, phone_1 and phone_2.
I want to ensure that once a phone number is entered in any of these fields, the same number cannot be saved on the same record or on any other future record in either one of these two columns.
How to achieve this on a database level and on a model level?
class Brand < ApplicationRecord
before_save :normalize_phones
validates :phone_1, phone: true, allow_blank: true
validates :phone_2, phone: true, allow_blank: true
private
# https://justincypret.com/blog/validating-normalizing-and-formatting-phone-numbers-in-rails
def normalize_phones
self.phone_1 = normalize_phone(self.phone_1)
self.phone_2 = normalize_phone(self.phone_2)
end
# https://justincypret.com/blog/validating-normalizing-and-formatting-phone-numbers-in-rails
def normalize_phone(phone)
self.phone_1 = Phonelib.parse(phone_1).full_e164.presence
self.phone_2 = Phonelib.parse(phone_2).full_e164.presence
end
end
I would just go a completly different direction:
class Brand < ApplicationRecord
has_many :phone_numbers
accepts_nested_attributes_for :phone_numbers
end
# rails g model phone_number number:uniq description
class PhoneNumber < ApplicationRecord
belongs_to :brand
validates :number,
presence: true,
uniqueness: true
end
This ensures the uniqueness both on the db and application level while allowing you to attach any arbitrary number of phone numbers.

One Association between 3 models in Rails

I am trying to create an association between 3 models where 1 of the models has an association with the other 2 but they have no association with each other. I thought I was one the right track and can create data for the 2 models (breweries and restaurants) but cannot get the third model(beers) to correctly save when used in localhost and will not associate with any other models.
The models are
class Beer < ActiveRecord::Base
belongs_to :brewery
belongs_to :restaurant
validates :brewery, presence: true
validates :restaurant, presence: true
validates :name, presence: true
end
class Brewery < ActiveRecord::Base
has_many :beers
validates :name, presence: true
end
class Restaurant < ActiveRecord::Base
has_many :beers
validates :name, presence: true
end
I also get an error whenever I try to create a new beer through the local host
This is referring to the beers_controllers method.
def create
#brewery = Brewery.find(params[:brewery_id])
#beer = #brewery.beers.create(beer_params)
redirect_to #beer
end
I've tried everything I can think of to no avail and am worried it's just a syntax error that I'm over looking. Would love any advice.
You are getting that error because params[:brewery_id] is nil. Without further information I can only guess but I am thinking that you don't want a beer to belong to both brewery and restaurant. Doing so would require that a beer has both a brewery and a restaurant every time. You probably want something more like a beer has_many beer_venues. A beer has many venues through beer_venues. A venue has many beer_venues. and a venue has many beers through beer_venues. Then You can use single table inheritance on your venues class to give a type column that is either restaurant or brewery.

Validates uniqueness from foreign key of foreign key

I have 3 models in my rails application, User, Course, and CourseTemplate.
A Course belongs to a User and a CourseTemplate belongs to a Course.
What I want to do is to validate the uniqueness between the CourseTemplate name and the User id.
Is this possible?
Without denormalization of data
class CourseTemplate < ActiveRecord::Base
belongs_to :course
has_one :user, through: :course
validate :unique_course_template_for_user
private
def unique_course_template_for_user
errors.add(:name, 'Must be unique') if CourseTemplate.find_by(user: user.id, name: self.name).count > 0
end
end
With denormalization of data
If you're ok with some denormalization of your data, you could add user_id to CourseTemplate, and then simply use the scope feature of validates uniqueness.
Below I show how to use callbacks to maintain the user_id in the CourseTemplate. Note that it assumes a course cannot be moved to a different user.
class CourseTemplate < ActiveRecord::Base
before_create :copy_user_id_from_course
validates :name, uniqueness: { scope: :user_id, message: 'Must be unique for the same user'}
private
def copy_user_id_from_course
self.user_id = course.user_id
true
end
end
If the course can be moved to a different user, you should add a callback on Course:
class Course < ActiveRecord::Base
after_save :set_course_templates_user, if: :user_id_changed?
private
def set_course_templates_user
course_templates.update_all user_id: self.user_id
end
end

Update a single attribute in a model using Rails 3

I have two models, Rules and Ruleset that both have a has_to_and_belong_to_many relationship. Rules are individual rules, and Rulesets are a specific collection of rules.
The user's dashboard shows all the rules the user created. I have a button for each rule to "Add Rule to Ruleset". By clicking the button, it should load a select form where the user can select their existing rulesets and hit submit, and voila, its added to the ruleset.
I just don't know how to make this work, as I'm pretty new to rails. If I call the update action on rules, it loads the entire update form, which I don't want. I just want to have the ability for a user to select a ruleset and then add that rule to the ruleset. Here are my models:
class Rule < ActiveRecord::Base
attr_accessible :description, :user_id, :game_id, :ruleset_id
has_and_belongs_to_many :rulesets
belongs_to :user
belongs_to :game
validates :description, presence: true
validates :user_id, presence: true
validates :game_id, presence: true
end
class Ruleset < ActiveRecord::Base
attr_accessible :title, :game_id, :user_id, :rule_id
validates :game_id, presence: true
validates :user_id, presence: true
validates :title, presence: true
belongs_to :user
belongs_to :game
has_and_belongs_to_many :rules
end
You should declare a specific action in the controller for adding rules to rulesets.
When a person selects a ruleset, it will be passed as a parameter and you can catch it in your newly declared action. Also, use a hidden_field_tag where you will store your rule_id.
In the newly declared action, create something like this:
def add_rule_to_ruleset
#ruleset = Ruleset.find(params[:ruleset_id])
#rule = Rule.find(params[:rule_id])
#ruleset.rules << #rule
redirect_to current_user.rules
end
Also fetch all the rulesets in the select box with current_user.rulesets, but i guess you will have to check if that ruleset has that rule already (you don't want the same rule twice or more times in the same ruleset, do you? ), so you should modify it.
Check that with something like current_user.rulesets.include?(rule)
Routes.rb:
resources :rules do
member do
put 'add_rule_to_ruleset'
end
end

Rails 3: validates_presence_of validation errors on default value and in associated model

I have a basic invoice setup with models: Invoice, Item, LineItems.
# invoice.rb
class Invoice < ActiveRecord::Base
has_many :line_items, :dependent => :destroy
validates_presence_of :status
before_save :default_values
def default_values
self.status = 'sent' unless self.status
end
end
# item.rb
class Item < ActiveRecord::Base
has_many :line_items
validates_presence_of :name, :price
end
# line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :item
belongs_to :invoice
before_save :default_values
validates_presence_of :invoice_id
validates :item_id, :presence => true
end
There is more in the model but I only presented the above for simplicity.
I get the following errors:
2 errors prohibited this invoice from being saved:
Line items invoice can't be blank
Status can't be blank
So two problems:
If I remove validates :invoice_id, :presence => true I don't get the Line items invoice can't be blank error message anymore, but why? I do want to validate the invoice_id on line_items, ALL line_items are supposed to have an invoice_id. How can I validate the invoice_id on line_items without getting an error?
Why am I getting the Status can't be blank error if I set it as a default value? I can probably set it up on the invoices_controller but I think the default value should be set in the model, right? How can I validate the presence of status and still have a default value in the model for it?
Both of these validation errors are occurring because validations get called before save (and before the before_save callback).
I'm assuming that you're using a nested_form to create the invoice and it's line items at the same time. If this is the case, you don't want to validates :invoice_id, :presence => true on the line items - the invoice and the line items are coming in at the same time, and the invoice hasn't been saved yet, so it doesn't have an id. If you leave the validation in, you'll need to create and save an empty invoice first, and then create the line items later so the invoice_id is available. If you only want to make sure invoice_id is still set after any edits, you can enforce this via validates :invoice_id, :presence => true, :on => :update this will skip the validation when the line item is being created (and the invoice_id isn't available yet).
You're running into problems with validates :status, :presence => true for similar reasons - the values coming in via the request are being validated against, and the "status" value isn't there. The before_save callback runs after validation. You can set the default value in the before_validation or after_initialization callback and the values will be there when validations are run.
Check out the Callbacks documentation for Rails for more info.
I'll start with 2:
before save is being executed only before save, meaning, after the object passed validation and is about to be saved. If the validation fails - it won't be executed.
as for 1:
Can you give an example of how you're trying to create an invoice?
Problem 1
Try validates_associated which checks that the associated models are all valid
Problem 2
Like most of the answers say before_save gets called after validations. The magic you're looking for is after_initialize which gets run after an object's initialize method is called.
class Invoice < ActiveRecord::Base
after_initialize :default_values
validates :status, presence: true
private
def default_values
self.status ||= 'sent'
end
end

Resources