I'm working on an association between two models:
class Person < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :person
end
Many person records exist in the system that don't necessarily correspond to a user, but when creating a user you need to either create a new person record or associate to an existing one.
What would be the best way to associate these two models when the person record already exists? Do I need to manually assign the user_id field, or is there a Rails way of doing that?
Where #user is a recently created user, and #person is an existing person.
#user.person = #person
#user.save
Alternately:
User.new :person => #person, ... #other attributes
or in params form:
User.new(params[:user].merge({person => #person}))
As far as forms go:
<% form_for #user do |f| %>
...
<% fields_for :person do |p| %>
<%= p.collection_select, :id, Person.all, :id, :name, :include_blank => "Use fields to create a person"%>
<%= p.label_for :name%>
<%= p.text_field :name %>
...
<% end %>
<% end %>
And in the user controller:
def create
#user = User.create(params[:user])
#person = nil
if params[:person][:id]
#person = Person.find(params[:person][:id])
else
#person = Person.create(params[:person])
end
#user.person = #person
...
end
If you don't want to create/alter a form for this, you can do
#person_instance.user = #user_instance
For has_many relationships, it would be:
#person_instance.users << #user_instance
You first have to do a nested form :
<% form_for #user do |user| %>
<%= user.text_field :name %>
<% user.fields_for user.person do |person| %>
<%= person.text_field :name %>
<% end %>
<%= submit_tag %>
<% end %>
In your User model :
class User < ActiveRecord::Base
accepts_nested_attributes_for :person
end
If you want the person deleted when the user is :
class User < ActiveRecord::Base
accepts_nested_attributes_for :person, :allow_destroy => true
end
And in your controller do nothing :
class UserController < ApplicationController
def new
#user = User.new
#find the person you need
#user.person = Person.find(:first)
end
def create
#user = User.new(params[:user])
#user.save ? redirect_to(user_path(#user)) : render(:action => :new)
end
end
Related
Problem
I'm trying to create a middle table called category_profiles, is a intermediate table to assign favorite categories to my profiles, but I can't access to the category_ids, that I put in my form, always I got the same validation, Category doesn't exist:
Code:
class CategoryProfile < ApplicationRecord
belongs_to :profile
belongs_to :category
end
class Category < ApplicationRecord
has_many :category_profiles
has_many :profiles, through: :category_profiles
class Profile < ApplicationRecord
has_many :category_profiles
has_many :categories, through: :category_profiles
When I'm doing the create action, my controller can't find my category. How do I fix it?
My create action never find the ids of my categories to assign to the category_profiles. It has many through relation:
Module Account
class FavoritesController < Account::ApplicationController
before_action :set_category_profile
def index
#favorites = #profile.categories
end
def new
#categories = Category.all
#category_profile = CategoryProfile.new
end
def create
#category_profile = #profile.category_profiles.new(category_profile_params)
if #category_profile.save
flash[:success] = t('controller.create.success',
resource: CategoryProfile.model_name.human)
redirect_to account_favorites_url
else
flash[:warning] = #category_profile.errors.full_messages.to_sentence
redirect_to account_favorites_url
end
end
def destroy
end
private
def set_category_profile
#category_profile = CategoryProfile.find_by(params[:id])
end
def category_profile_params
params.permit(:profile_id,
category_ids:[])
end
end
end
Form
<%= bootstrap_form_with(model: #category,method: :post , local: true, html: { novalidate: true, class: 'needs-validation' }) do |f| %>
<div class="form-group">
<%= collection_check_boxes(:category_ids, :id, Category.all.kept.children.order(name: :asc), :id, :name, {}, { :multiple => true} ) do |b| %>
<%= b.label class: 'w-1/6 mr-4' %>
<%= b.check_box class: 'w-1/7 mr-4' %>
<%end %>
</div>
<div class="md:flex justify-center">
<%= f.submit 'Guardar categorÃa favorita', class: 'btn btn-primary' %>
</div>
<% end %>
Seems like you just want to update intermediate table. So you can do it like this.
def create
begin
#profile.categories << Category.find(params[:category_ids])
Or
params[:category_ids].each do |category_id|
#profile.category_profiles.create(category_id: category_id)
end
flash[:success] = t('controller.create.success',
resource: CategoryProfile.model_name.human)
redirect_to account_favorites_url
rescue
flash[:warning] = #category_profile.errors.full_messages.to_sentence
redirect_to account_favorites_url
end
end
Need to find other better way for error handling using either transaction block or something.
I have a model "Student" and every student has_many parents (a mother and a father in the parents table). In my UI I want to be able to add the parents and the student on the same page. So when I click on "Add student" the view 'students/new' is rendered. In this view I have the regular stuff for adding a student (<% form_for #student....) so far so good. But now I also want to provide the form for adding a mother and a father for this student on the same page. I know I could place a link to 'parents/new' somewhere but that is not really user-friendly in my opinion.
What are my options and what would you recommend?
Your best bet would be using nested_forms with accepts_nested_attributes_for like below
#student.rb
Class Student < ActiveRecord::Base
has_many :parents
accepts_nested_attributes_for :parents
end
#students_controller.rb
def new
#student = Student.new
#student.parents.build
end
def create
#student = Student.new(student_params)
if #student.save
redirect_to #student
else
render 'new'
end
private
def student_params
params.require(:student).permit(:id, :student_attr_1, :student_attrr_2, parents_attributes: [:id, :father, :mother])
end
#students/new
<%= form_for #student do |f| %>
---student code here ---
<%= f.fields_for :parents do |p| %>
<%= p.label :father, :class => "control-label" %>
<%= p.text_field :father %>
<%= p.label :fmother, :class => "control-label" %>
<%= p.text_field :mother %>
<% end %>
Inside your form you can add fields_for helper
<%= fields_for #student.father do |father| %>
<% father.text_field :name %> # will be appropriate father name
....
<% end %>
Check also rails fields_for
I'd use the ObjectForm concept:
Here is one good article about this pattern.
Here's an introduction to the implementation:
Class Student < ActiveRecord::Base
has_many :parents
end
class CompleteStudentForm
include ActiveModel::Model
attr_acessor :name, :age #student attributes
attr_accessor :father_name, :mother_name #assuming that Parent model has only the :name attribute
validates_presence_of :name, :age
# simply add custom validation messages for fields
validates_presence_of :father_name, message: 'Fill your father name'
validates_presence_of :mother_name, message: 'Fill your mother name'
def save
persist! if valid?
end
private
def persist!
student = Student.new(name: #name, age: #age)
student.parents << Parent.new(name: #father_name)
student.parents << Parent.new(name: #mother_name)
student.save!
end
end
class StudentController
def create
#student = CompleteStudentForm.new(params[:complete_student_form])
if #student.save
redirect_to :show, #student
else
render :new
end
end
end
Im trying to make a sign up form where you can create a user and a company at the same time. I have a user table and a company table.
user model
belongs_to :companies
accepts_nested_attributes_for :companies
company model
has_many :users
users controller
def new
#users = User.new
#companies = Company.new
end
def create
#companies = Company.create(company_params)
#users = #companies.user.create(user_params)
#users.save
redirect_to :back
end
private
def user_params
params.require(:user).permit(:name)
end
def company_params
params.require(:user).permit(:name)
end
Routes
resources :users
resources :companies
new.html.erb
<%= form_for(#users) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for #companies do |build| %>
<%= build.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
So this create a company and a user at the same time. But i want the user to be assigned a company_id there belongs to the company they just created. I have the company_id field in my users table. Any ideas?
UPDATE
def create
#companies = Company.create(company_params)
c = Company.last
#users = c.users.create(user_params)
#users.save
redirect_to :back
end
I did add this to the user controller at it works, but it doesn't seems like the best solution. Please correct me if i'm wrong.
Thanks in advance
I would do in the following way:
Your User Model
belongs_to :companies,:inverse_of => :users
accepts_nested_attributes_for :companies
Your controller
def new
#users = User.new
#users.build_company
end
def create
#users = User.new(user_company_params)
#users.save
end
def user_company_params
params.require(:user).permit(:name, companies_attributes: name)
end
Your View
<%= f.fields_for :companies do |build| %>
<%= build.text_field :name %>
<% end %>
It works for me :)
Source:
http://guides.rubyonrails.org/association_basics.html
I have a 3 models: quote, customer, and item. Each quote has one customer and one item. I would like to create a new quote, a new customer, and a new item in their respective tables when I press the submit button. I have looked at other questions and railscasts and either they don't work for my situation or I don't know how to implement them.
quote.rb
class Quote < ActiveRecord::Base
attr_accessible :quote_number
has_one :customer
has_one :item
end
customer.rb
class Customer < ActiveRecord::Base
attr_accessible :firstname, :lastname
#unsure of what to put here
#a customer can have multiple quotes, so would i use has_many or belongs_to?
belongs_to :quote
end
item.rb
class Item < ActiveRecord::Base
attr_accessible :name, :description
#also unsure about this
#each item can also be in multiple quotes
belongs_to :quote
quotes_controller.rb
class QuotesController < ApplicationController
def index
#quote = Quote.new
#customer = Customer.new
#item = item.new
end
def create
#quote = Quote.new(params[:quote])
#quote.save
#customer = Customer.new(params[:customer])
#customer.save
#item = Item.new(params[:item])
#item.save
end
end
items_controller.rb
class ItemsController < ApplicationController
def index
end
def new
#item = Item.new
end
def create
#item = Item.new(params[:item])
#item.save
end
end
customers_controller.rb
class CustomersController < ApplicationController
def index
end
def new
#customer = Customer.new
end
def create
#customer = Customer.new(params[:customer])
#customer.save
end
end
my form for quotes/new.html.erb
<%= form_for #quote do |f| %>
<%= f.fields_for #customer do |builder| %>
<%= label_tag :firstname %>
<%= builder.text_field :firstname %>
<%= label_tag :lastname %>
<%= builder.text_field :lastname %>
<% end %>
<%= f.fields_for #item do |builder| %>
<%= label_tag :name %>
<%= builder.text_field :name %>
<%= label_tag :description %>
<%= builder.text_field :description %>
<% end %>
<%= label_tag :quote_number %>
<%= f.text_field :quote_number %>
<%= f.submit %>
<% end %>
When I try submitting that I get an error:
Can't mass-assign protected attributes: item, customer
So to try and fix it I updated the attr_accessible in quote.rb to include :item, :customer but then I get this error:
Item(#) expected, got ActiveSupport::HashWithIndifferentAccess(#)
Any help would be greatly appreciated.
To submit a form and it's associated children you need to use accepts_nested_attributes_for
To do this, you need to declare it at the model for the controller you are going to use (in your case, it looks like the Quote Controller.
class Quote < ActiveRecord::Base
attr_accessible :quote_number
has_one :customer
has_one :item
accepts_nested_attributes_for :customers, :items
end
Also, you need to make sure you declare which attributes are accessible so you avoid other mass assignment errors.
If you want add info for diferent models i suggest to apply nested_model_form like this reference: http://railscasts.com/episodes/196-nested-model-form-part-1?view=asciicast.
This solution is very simple and cleanest.
for now i've got followings:
model => User (name, email)
has_and_belongs_to_many :trips
model => Trip (dest1, dest2)
has_and_belongs_to_many :users
validates :dest1, :dest2, :presence => true
model => TripsUsers (user_id, trip_id) (id => false)
belongs_to :user
belongs_to :trip
As you see from the code, trip model has validation on dest1, and dest2, but it's not showing up an errors. Controller and view defined as follow:
trips_controller.rb
def new
#user = User.find(params[:user_id])
#trip = #user.trips.build
end
def create
#user = User.find(params[:user_id])
#trip = Trip.new(params[:trip])
if #trip.save
#trip.users << #user
redirect_to user_trips_path, notice: "Success"
else
render :new
end
end
_form.html.erb
<%= simple_form_for [#user, #trip] do |f| %>
<%= f.error_notification %>
<%= f.input :dest1 %>
<%= f.input :dest2 %>
<%= f.submit "Submit" %>
<% end %>
According to the rails guide on presence validation, it can't be used with associated objects. Try to use a custom validation:
validate :destinations_presence
def destinations_presence
if dest1.nil?
errors.add(:dest1, "missing dest1")
elsif dest2.nil?
errors.add(:dest1, "missing dest2")
end
end