I want to loop through attributes which are validated with custom validation method.
I have model Post which has_many :languages and a model Language which belongs_to :post. In the languages table I have columns - id, post_id, language.
Post model:
class Post < ApplicationRecord
has_many :languages
accepts_nested_attributes_for :languages, reject_if: :all_blank
validates_associated :languages
end
Language model:
class Language < ApplicationRecord
validate :unique_languages?
def unique_languages?
#LOOP ATTRIBUTES
end
end
IIn the Language model in the unique_languages? I want too loop through all language attributes of the post.
This is posts_controller with strong params and logic for creating a post:
class PostsController < ApplicationController
def new
#post = Post.new
#post.languages.build if #post.languages.empty?
end
def create
#post = Post.new(post_params)
#post.languages.build if #post.languages.empty?
if #post.save
redirect_to action: 'new'
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:title, languages_attributes: [:language])
end
end
## app/validators/nested_attributes_uniqueness_validator
class NestedAttributesUniquenessValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
raise ArgumentError if options[:parent].blank?
association = record.class.to_s.pluralize.underscore # :languages
items =
record # #language
.send(options[:parent]) # #language.post
.send(association) # #language.post.languages
.select(attribute) # #language.post.languages.select(:language)
unless items.distinct.size == items.size
record.errors.add attribute, :nested_attributes_uniqueness
# Don't forget to translate `errors.messages.nested_attributes_uniqueness`
end
end
end
## app/models/post.rb
class Post < ApplicationRecord
has_many :languages, inverse_of: :post # :inverse_of is important
accepts_nested_attributes_for :languages, reject_if: :all_blank
end
## app/models/language.rb
class Language < ApplicationRecord
belongs_to :post, inverse_of: :languages # :inverse_of is important
validates :language, nested_attributes_uniqueness: { parent: :post }
end
You can access a object attributes by:
class Language < ApplicationRecord
validate :unique_languages?
def unique_languages?
self.attributes.each do |attr, value|
#your code
end
end
end
your object.attributes return a hash where the keys are the attributes names, and the value is the value for that attribute. Don't know exactly what you're trying to do, but this may helps. Good luck!
Related
I'm trying to figure out how to automatically set up an invoice with invoice_rows, once a reservation is saved.
Attempts
Before even including the order_rows, I tried generating an invoice for order:
I tried including #order.invoices.create(order_contact_id: #order.order_contact_id) after saving the order in create, but this resulted in an empty array:
Order.last.invoice => []
Afterwards I probably should iterate over all products belonging to a order and include them as invoice_rows in invoice. But not sure how.
Note
The actual structure is more complex and consequently I need all my tables.
Code
models
class Order < ApplicationRecord
has_many :invoices
has_many :order_products, dependent: :destroy
end
class OrderProduct < ApplicationRecord
belongs_to :product
belongs_to :order
accepts_nested_attributes_for :product
end
class Product < ApplicationRecord
has_many :orders, through: :order_products
has_many :product_prices, dependent: :destroy, inverse_of: :product
accepts_nested_attributes_for :product_prices, allow_destroy: true
end
class ProductPrice < ApplicationRecord
belongs_to :product, inverse_of: :product_prices
end
orders_controller
class OrdersController < ApplicationController
def create
#order = #shop.orders.new(order_params)
authorize #order
if #order.save
authorize #order
# #order.invoices.create(order_contact_id: #order.order_contact_id)
redirect_to new_second_part_shop_order_path(#shop, #order)
end
end
private
def order_params
params.require(:order).permit(:order_contact_id,
order_products_attributes: [:id, :product_id, :product_quantity, :_destroy,
products_attributes: [:id, :name, :description]])
end
end
As suggested in the comments, I found the error message by using #order.invoices.create!.
Afterwards I iterated over each product and created an invoice_row for the created invoice.
#invoice = #order.invoices.create!(order_contact_id: #order.order_contact_id)
#order.order_products.each do |o_product|
#invoice.invoice_rows.create!(
description: o_product.product.name,
total_price: #reservation.total_product_price(#reservation, o_product)
)
end
So I have a has_many through association where between two tables posts and users:
class Post < ApplicationRecord
has_many :assignments
has_many :users, :through => :assignments
end
class User < ApplicationRecord
has_many :assignments
has_many :posts, :through => :assignments
end
class Assignment < ApplicationRecord
belongs_to :request
belongs_to :user
end
Now in my association table (assignment) there are additional attributes for creator:boolean and editor:boolean.
My question is what's the best way to set these secondary attributes from within the controller?
Having looked around I've got a current solution:
posts_controller.rb:
class PostsController < ApplicationController
def create
params.permit!
#post = Post.new(post_params)
if #post.save
Assignment.handle_post(#post.id, params[:creator], params[:editors])
redirect_to posts_path, notice: "The post #{#post.title} has been created."
else
render "new"
end
end
assignment.rb:
class Assignment < ApplicationRecord
belongs_to :request
belongs_to :user
def self.handle_post(post_id, creator, assignment)
Assignment.where(:post_id => post_id).delete_all
Assignment.create!(:post_id => post_id, :user_id => creator, :creator => true, :editor => false)
if editors.present?
editors.each do |e|
Assignment.create!(:post_id => post_id, :user_id => e, :creator => false, :editor => true)
end
end
end
end
So what is essentially happening is I'm getting the user_ids from the form via params (creator returns 1 id, editors returns an array), and AFTER creating the post I'm deleting all columns associated with the post and recreating them off the new attributes.
The issue I have here is I can't run post validations on these associations (e.g. check a creator is present).
My two questions are as follows:
Is this the correct way to handle secondary attributes?
Is there a way to set the association up and then save it all at once so validations can be performed?
This is a more Rails way to do this:
Use nested attributes
post.rb
class Post < ApplicationRecord
# Associations
has_many :assignments, inverse_of: :post
has_many :users, through: :assignments
accepts_nested_attributes_for :assignments
# Your logic
end
assignment.rb
class Assignment < ApplicationRecord
after_create :set_editors
belongs_to :request
belongs_to :user
belongs_to :post, inverse_of: :assignments
# I would create attribute accessors to handle the values passed to the model
attr_accessor :editors
# Your validations go here
validates :user_id, presence: true
# Your logic
private
def set_editors
# you can perform deeper vaidation here for the editors attribute
if editors.present?
editors.each do |e|
Assignment.create!(post_id: post_id, user_id: e, creator: false, editor: true)
end
end
end
end
And finally, add this to your PostsController
params.require(:post).permit(..., assignments_attributes: [...])
This allows you to create Assignments from the create Post action, will run validations on Post and Assignment and run callbacks for you.
I hope this helps!
I have three models Company, User and Division
User have many Division for different Companies
I need to determine in what company owns Divisions
So I build has_many :through association between Users and Divisions
Model UsersDivision have this fields id|user_id|division_id|company_id but when I update User model rails delete old records and create new without company_id field How i can update model UsersDivision and merge company_id ?
Callback?
class UsersDivision < ActiveRecord::Base
after_update :set_company
belongs_to :user
belongs_to :division
belongs_to :company
validates :user_id, :division_id, presence: true
private
def set_company(company)
self.company_id = company
end
end
or in the controller?
class UsersController < ApplicationController
def update
#company = Company.find(params[:company_id])
#user = User.find(params[:id])
if #user.update(user_params)
redirect_to :back
end
end
end
How to merge company_id when create UsersDivision record?
So I build has_many :through association between Users and Divisions
I would expect there to be a table for Divisions, and then a table for CompanyDivisions, and then we can associate users to that.
Here's how I would have it set up:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :user_divisions
has_many :divisions, through: :user_divisions
has_many :company_divisions, through: :user_divisions
has_many :companies, through: :company_divisions
end
#app/models/user_division.rb
class UserDivision < ActiveRecord::Base
belongs_to :user
belongs_to :company_division
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :company_divisions
has_many :divisions, through: :company_divisions
end
#app/models/company_division.rb
class CompanyDivision < ActiveRecord::Base
belongs_to :company
belongs_to :division
end
#app/models/division.rb
class Division < ActiveRecord::Base
has_many :company_divisions
has_many :companies, through: :company_divisions
end
This is very bloated, but should give you the ability to call:
#user.divisions
#user.divisions.each do |division|
division.companies.first
How to merge company_id when create UsersDivision record
This will depend on several factors:
params hash
How your associations are set up
I don't have your params hash, but I do have your current code:
class UsersController < ApplicationController
def update
#company = Company.find params[:company_id]
#user = User.find params[:id]
redirect_to :back if #user.update user_params
end
private
def user_params
params.require(___).permit(___).merge(company_id: #company.id)
end
end
In my rails app I have the following models
class Member < ActiveRecord::Base
has_many :trainings
end
class Student < ActiveRecord::Base
belongs_to :member
has_many :trainings #maybe a through relationship here
end
class Teacher < ActiveRecord::Base
belongs_to :member
end
######edited#################
class Training < ActiveRecord::Base
belongs_to :member #only member not student nor teacher
end
#############################
Now, how can I build the trainings in my student controller
class StudentsController < ApplicationController
def new
#student = Student.new
#student.trainings.build #### This is not working
end
end
Thanks
You have to write accepts_nested_attributes_for in the model and add them in strong parameters if you are using rails 4. Like this :
class Student < ActiveRecord::Base
belongs_to :member
has_many :trainings
accepts_nested_attributes_for :trainings
end
class StudentsController < ApplicationController
def new
#student = Student.new
#student.trainings.build
end
def create
#student = Student.create(student_params)
#student.trainings.build(params[:student][:trainings])
redirect_to student_path
end
#For rails 4
def student_params
params.require(:student).permit(:id, :name, trainings_attributes: [ :id, :your fields here ])
end
end
Here is a link that will help you:
Rails 4: accepts_nested_attributes_for and mass assignment
If you've properly defined your associations, then the code in your new controller action will work (I tested it). Check and make sure your Training model exists, or that you've used the correct association name (perhaps you meant :teachers?).
app/models/student.rb
class Student < ActiveRecord::Base
has_many :trainings
end
app/models/training.rb
class Training < ActiveRecord::Base
belongs_to :student
end
app/controllers/students_controller.rb
class StudentsController < ApplicationController
def new
#student = Student.new
#student.trainings.build
end
end
Update:
Assuming these are how your associations are defined, you could build a scoped instance of Training like so:
app/models/member.rb
class Member < ActiveRecord::Base
has_many :trainings
end
app/models/student.rb
class Student < ActiveRecord::Base
delegate :trainings, to: :member
belongs_to :member
end
app/models/training.rb
class Training < ActiveRecord::Base
belongs_to :member
end
app/controllers/students_controller.rb
class StudentsController < ApplicationController
def new
#student = Student.new
#student.build_member
#student.trainings.build
end
end
Hope that helps.
For example, let us say we have
class User < ActiveRecord::Base
has_many :networks, through: user_networks
has_many :user_networks
end
class Network< ActiveRecord::Base
has_many :users, through: user_networks
has_many :user_networks
end
class UserNetwork < ActiveRecord::Base
belongs_to :user
belongs_to :network
end
Is there a shortcut for doing the following in a controller:
#network = Network.create(params[:network])
UserNetwork.create(user_id: current_user.id, network_id: #network.id)
Just curious and I doubt it.
This should work:
current_user.networks.create(params[:network])
But your code implies you are not using strong_parameters, or checking the validation of your objects. Your controller should contain:
def create
#network = current_user.networks.build(network_params)
if #network.save
# good response
else
# bad response
end
end
private
def network_params
params.require(:network).permit(:list, :of, :safe, :attributes)
end