Rails 4 - Create record with has_many through - ruby-on-rails

In my project i have something like this:
class User < ActiveRecord::Base
has_many :roles
has_many :websites, through: :roles
end
class Website < ActiveRecord::Base
validates :name, presence: true
has_many :roles
has_many :users, through: :roles
end
class Role < ActiveRecord::Base
validates :name, presence: true
belongs_to :user
belongs_to :website
end
So when I try to do:
User.first.websites.create(name: "First")
I have this error
ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
How can i create a new User Website in one line?

The validation error is actually coming from the Role model, which also has validation for the name attribute.
You can do it in one line by creating the website through the role, using accepts_nested_attributes_for:
class Role < ActiveRecord::Base
validates :name, presence: true
belongs_to :user
belongs_to :website
accepts_nested_attributes_for :website
end
User.first.roles.create(name: "Role name", website_attributes: { name: "Website name" })

I think if you remove validates :name, presence: true from role model then it will work.

Related

How to add has_many associations to record on create?

I have the following Rails models:
class Project < ApplicationRecord
validates :title, presence: true
validates :project_managers, length: { minimum: 1, message: "Please assign at least one PM" }
has_many :project_assignments, dependent: :destroy
has_many :project_managers, through: :project_assignments, source: :user
end
class User < ApplicationRecord
has_many :project_assignments, dependent: :destroy
has_many :projects, through: :project_assignments
end
class ProjectAssignment < ApplicationRecord
belongs_to :project
belongs_to :user
end
Now I'm trying to add project managers while creating the record:
Project.create!(title: "foobar", project_manager_ids: [1])
But this leads too ActiveRecord::RecordInvalid: Validation failed: Project assignments is invalid
Is there a way to add project managers directly on create?
In your project model, add the following
accepts_nested_attributes_for :project_assignments, allow_destroy: true
In your Project controller, in strong params add the following
def params
params.require(:project).permit(
:id,
:title,
project_assignments_attributes: [
:id,
:user_id,
_destroy
]
)
end

Creating a record using has_many :through?

I have the following models:
class Department < ApplicationRecord
has_many :department_job_titles
has_many :job_titles, through: :department_job_titles
end
class JobTitle < ApplicationRecord
has_and_belongs_to_many :departments
end
class DepartmentJobTitle < ApplicationRecord
belongs_to :department
belongs_to :job_title
validates :department_id, uniqueness: { scope: :job_title_id }
end
This is erring w PG::UndefinedColumn: ERROR: column department_job_titles.title does not exist
LINE 1: ... "department_job_titles"."department_id" = $1 AND "departmen...
Department.first.department_job_titles.find_or_create_by(title: title)
DepartmentJobTitle has the following fields: id, department_id, job_title_id
What am I doing wrong here?
Try this:
job_title = JobTitle.find_or_create_by(title: title)
Department.first.job_titles << job_title unless job_title.in? Department.first.job_titles
Or that second line could be:
Department.first.job_titles = (Department.first.job_titles + [job_title]).uniq
Also:
class JobTitle < ApplicationRecord
has_many :department_job_titles
has_many :departments, through: :department_job_titles
end
... and ...
class DepartmentJobTitle < ApplicationRecord
belongs_to :department
belongs_to :job_title
validates :department, presence: true, uniqueness: { scope: :job_title }
validates :job_title, presence: true
end
... and think about what behaviour you want if someone destroys a JobTitle or Department -- either you want the DepartmentJobTitle destroyed also, or you want the destroy to be prevented, I expect.

Rails 4 - Validates uniqueness with has_many through

I have these three models:
User:
class User < ActiveRecord::Base
validates :name, presence: true
validates :surname, presence: true
validates :email, presence: true, format: { with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
has_many :permissions, dependent: :destroy
has_many :stores, through: :permissions
end
Store:
class Store < ActiveRecord::Base
validates :name, presence: true
validates :description, presence: true
has_many :permissions
has_many :users, through: :permissions
end
Permission:
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :store
end
How can i validate the uniqueness of the user.email based on the store.id?
You don't.
You should validate the uniqueness of the users email in User. And validate the uniqueness of the user_id and store_id in Permission.
class User < ApplicationRecord
# ...
validates_uniqueness_of :email
end
class Permission < ApplicationRecord
validates_uniqueness_of :user_id, scope: 'store_id'
end
This allows a user to have permissions for multiple stores - but does not allow duplicates. In general when linking records together use id's - not something like emails.

Failing validations in join model when using has_many :through

My full code can be seen at https://github.com/andyw8/simpleform_examples
I have a join model ProductCategory with the following validations:
validates :product, presence: true
validates :category, presence: true
My Product model has the following associations:
has_many :product_categories
has_many :categories, through: :product_categories
When I try to create a new product with a category, the call to #product.save! in the controller fails with:
Validation failed: Product categories is invalid
When I remove the validations, everything works and the join models are saved correctly.
I'm using strong_parameters but I don't think that should be related to this issue.
This is a "racing condition" in the callback chain.
When you create a product it doesn't have any id before it is saved, therefore there is no product in the scope of ProductCategory.
Product.new(name: "modern times", category_ids:[1, 2]) #=> #<Product id: nil >
At that stage of validation (before saving), ProductCatgory cannot assign any id to it's foreign key product_id.
That's the reason you have association validations : so that the validation happens in the scope of the whole transaction
UPDATE: As said in the comment you still can't ensure presence of a product/category. There's many ways around depending on why you want do this (e.g direct access to ProductCategory through some form)
You can create a flag to have validates :product, presence: true, if: :direct_access?
or if you can only update them: validates :product, presence: true, on: "update"
create your product first (in the products_controller) and add the categories after
... But indeed these are all compromises or workarounds from the simple #product.create(params)
Specifying inverse_of on your joining models has been documented to fix this issue:
https://github.com/rails/rails/issues/6161#issuecomment-6330795
https://github.com/rails/rails/pull/7661#issuecomment-8614206
Simplified Example:
class Product < ActiveRecord::Base
has_many :product_categories, :inverse_of => :product
has_many :categories, through: :product_categories
end
class Category < ActiveRecord::Base
has_many :product_categories, inverse_of: :category
has_many :products, through: :product_categories
end
class ProductCategory < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates :product, presence: true
validates :category, presence: true
end
Product.new(:categories => [Category.new]).valid? # complains that the ProductCategory is invalid without inverse_of specified
Adapted from: https://github.com/rails/rails/issues/8269#issuecomment-12032536
Pretty sure you just need to define your relationships better. I still might have missed some, but hopefully you get the idea.
class Product < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
validates :name, presence: true
validates :description, presence: true
validates :color_scheme, presence: true
belongs_to :color_scheme
has_many :product_categories, inverse_of: :product
has_many :categories, through: :product_categories
end
class ProductCategory < ActiveRecord::Base
belongs_to :product
belongs_to :category
validates_associated :product
validates_associated :category
# TODO work out why this causes ProductsController#create to fail
# validates :product, presence: true
# validates :category, presence: true
end
class Category < ActiveRecord::Base
has_many :product_categories, inverse_of: :category
has_many :products, through: :product_categories
end

Join model not getting created properly using accepts_nested_attributes_for

I have a User model and a Project model joined with the Ownership model using has_many :through. I am trying to set an Ownership attribute's value as I create an association between a User and a Project by using accepts_nested_attributes_for.
Here is my User model:
class User < ActiveRecord::Base
attr_accessible :name, :email, :admin, :projects
has_many :ownerships
has_many :projects, :through => :ownerships
accepts_nested_attributes_for :projects
Here is my ownership model:
class Ownership < ActiveRecord::Base
attr_accessible :owner_type
belongs_to :project
belongs_to :user
validates :user_id, :presence => true
validates :project_id, :presence => true
validates :owner_type, :presence => true
end
and my project model:
class Project < ActiveRecord::Base
attr_accessible :name, :description
has_many :ownerships
has_many :users, :through => :ownerships
and this is how I'm trying to set the owner_type value of the ownership model as its being created:
current_user.ownerships.create(:owner_type => 'designer', :project => #project)
for some reason this isn't creating the ownership join model or (obviously) setting the owner_type value of the ownership model. What can I do to fix this?

Resources