Create parent object in child create method - ruby-on-rails

There are many related question on SO. I went through all of them but still struggle with my situation.
I have two models User and Booking
#model/user.rb
has_many :bookings
#modle/booking.rb
belongs_to :user
I want to create a booking and a user at the same time. If the user already exist, just add the new booking to the existing user.
My form for creating booking:
<%= simple_form_for :booking, url: bookings_path, :method => :post, do |f| %>
<%= f.fields_for :user, do |ff| %>
<%= ff.input_field :first_name %>
<%= ff.input_field :last_name %>
<%= ff.input_field :email %>
<%= ff.input_field :phone %>
<% end %>
...
<% end %>
And in the booking controller
#control/bookings_controller.rb
def create
#user = User.find_by(:email => booking_params[:user][:email])
if #user == nil
#user = User.new(booking_params[:user])
#user.username = User.autousername(#user)
#user.password = Devise.firendly_token(8)
else
#user.update(booking_params[:user])
end
#user.save
b_params = booking_params
b_params.delete("user")
#booking = Booking.new(b_params)
#booking.user_id = #user.id
#booking.save
...
end
def booking_params
params.require(:booking).permit(:status, :house_id, :check_in, :check_out, :adult_guest, :children_guest, :temp_profit,
:note, :check_in_note, :user => [:first_name, :last_name, :email, :phone])
end
I didn't use nested_attributes because I want to generate username and password myself rather than collect them using form.
What I got is ActiveRecord::AssociationTypeMismatch in BookingsController#create
User(#70121505047640) expected, got ActionController::Parameters(#70121556154400)
Most SO related problems want to create the child object under parent controller. In my case, it's reversed.
UPDATE
After trying every possible way, I found out that the CanCanCan load_and_authorize_resource seems to be the trouble maker. If I comment out it in my bookings_controller, everything works fine. Could someone tell me why?
Solution
Finally, I found out the reason. I'm using CanCanCan load_and_authorize_resource in my booking_controller. In order to create nested user instance under it, I have to load user resources. Just add load_and_authorize_resource :user will solve the problem!
Thanks for all the answers. Hope this will help people with the same problem.

This is probably happening when you try to do the #booking.save. Because you have a user param, it will try to use the user= to set it. In this case, the user= will apply a params object.
Try to delete the user param from the booking_params when you create the booking.
b_params.delete(:user)
or
b_params[:user] = #user

Related

Getting my Ruby on Rails page to save the user's email without using devise

I am building a one page website where visitors will simply be able to submit their email address. The only goal in the database is to get an email (no name, etc). There is only one page visible at first, which is the homepage. If the user submits an email already in use, it sends the user to an error page. If the email is not in use, it sends the user to a success page.
I have asked a question about this previously, and after a lot of comments and trial and error, it appeared that it worked and then it stopped working. When I do Rails C, there is only one user in the system and that user doesnt have an email...
Here is what my user migration looks like :
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :email
t.timestamps
end
add_index :users, :email, unique: true
end
end
Here is what my user model looks like:
class User < ApplicationRecord
end
Here is what users/new.html.erb looks like:
<%= form_for #user, as: :post, url: users_path do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
<% end %>
Here is my user controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:email])
if #user.save
redirect_to '/users/success'
else
redirect_to '/users/error'
end
end
def show
end
end
Here are my routes:
Rails.application.routes.draw do
root "users#new"
resources :users
end
When i run the code, it renders the homepage but when i click on submit, it sends me on a page called show.html.erb with http://localhost:3000/users/error on my brownser. No users are being saved in the console.
EDIT:
My model is
class User < ApplicationRecord
validates_uniqueness_of :email
end
It is still not working....
change new.html.erb as
<%= form_with(model: #user, local: true) do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
your controller will be
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user), notice: "yeh!!!!"
else
redirect_to new_user_path, notice: "email already registered"
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:email)
end
end
add
<p id="notice"><%= notice %></p> to your application.html.erb in layouts
rest as your question
There are a couple things wrong here.
You're so close, but you're misusing the as: attribute of form_for. Perhaps you think that will send as a POST request, but instead that is actually wrapping your form params in an object called "post". I saw this in the comments on another thread.
Remove the as: attribute and the helper will again wrap your params in the user object. While we're at it, you should also be able to remove the url: attribute as well since Rails form helpers are smart enough to infer that this is a new resourceful record and output the create URL as well as the POST action accordingly.
You need your controller to expect a whole "user" object instead of just checking for the email param. ALSO, assuming you're on Rails 4 or higher, you need to permit the email attribute to be mass-assigned on your User object. See the code.
def create
#user = User.new(params.require(:user).permit(:email)) # Not params[:email]
if #user.save
redirect_to '/users/success'
else
redirect_to '/users/error'
end
end
Also be careful about duplicate emails with different cases. The default in Rails is case-sensitive validation which means "JIM#gmail.com" would not trigger a validation error against "jim#gmail.com". You can fix this with.
class User < ApplicationRecord
validates :email, uniqueness: { case_sensitive: false }
end
BONUS!
Nowadays, it's better to move over to form_with (instead of form_for). It's on its way to becoming the new Rails standard and also makes a few of these things easier. The one point you'll want to keep in mind is that with form_with (and general Rails assumptions), forms are remote by default. So if you want to trigger a full page submit/refresh, add local: true to your form_with helper.
<%= form_with model: #user, local: true do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
<% end %>
As you are using resources in routes so def show is called when route is /users/:id. That's why its calling show.html.erb file.
When you try to validate an email, then in model write the validation for it
class User < ApplicationRecord
validates_uniqueness_of :email
end
Hope this helps.
Try to add validates_uniqueness_of in your model
class User < ApplicationRecord::Base
attr_accessor :email
validates_uniqueness_of :email
end
And
def show
#user = User.find(email: params[:email])
end
And if you wanna check all
def show
#user = User.all
end
Please try this.
I hope that helpful

Create Deeply Nested Rails Associations in One Form

I have a user form on my welcome page. As the user form gets submitted, I want to create a website record that belongs to the user and also a page record that belongs to that website.
I'm trying to use fields_for but I'm not sure what I'm doing correctly or incorrectly.
# welcome.html.erb
<%= form_for #user do |f| %>
<div>
<%= f.text_field :name' %>
</div>
<div>
<%= f.email_field :email %>
</div>
<div>
<%= f.password_field :password %>
</div>
<div>
<%= f.fields_for :website do |website_fields| %>
<%= website_fields.text_field :name, value: 'Untitled website' %>
<% end %>
</div>
<div>
<%= f.fields_for :page do |page_fields| %>
<%= page_fields.text_field :name, value: 'Untitled page' %>
<%= page_fields.text_field :content, class: 'js-PageContentHiddenField', value: 'Page content' %>
<% end %>
</div>
<div>
<%= f.submit 'Create account' %>
</div>
<% end %>
The pages controller looks like this...
# pages_controller.rb
def welcome
#user = User.new
#website = #user.websites.new
#page = #user.websites.pages.new
end
My routes are as follows...
# routes.rb
resources :users do
resources :websites do
resources :pages
end
end
The models look like this...
# User model
has_many :websites, :dependent => :destroy
has_many :pages, through: :websites
accepts_nested_attributes_for :websites
accepts_nested_attributes_for :pages
# Website model
belongs_to :user
has_many :pages, :dependent => :destroy
# Page model
belongs_to :website
And finally my users controller...
# User controller
def create
#user = User.new(shared_params)
#website = #user.websites.new(website_params)
#page = #website.pages.new(page_params)
if #user.save
auto_login(#user)
#redirect_to user_website_page_path
else
# redirect to wherever
end
end
private
def shared_params
params.require(:user).permit(:email, :password, :name)
end
def website_params
params.require(:user).permit(:name, :user_id)
end
def page_params
params.require(:user).permit(:name, :website_id)
end
The problem I'm having now is that the user name is being saved as the page and website name etc. It's some problem with my params I think. Also, I'm not sure how to set the redirect to redirect to the page after it's been created.
I've been playing around with various configurations for weeks now and I can't crack this. I can't stress how little I know what I'm doing here, would really love some help!
Callbacks are methods that get called at certain moments of an object's life cycle. With callbacks it is possible to write code that will run whenever an Active Record object is created, saved, updated, deleted, validated, or loaded from the database.
So as you need to create a website and a page when the user is being created the you can use a after_create or before_create callback. after_create gets executed after the object is created and the before_create callback gets executed before the object is being created.
So in your User model what you can do is:
after_create :create_website_and_page
def create_website_and_page
website = websites.build(name: 'Untitled Website')
page = website.pages.build(name: 'Untitled Page', content: 'Page Content')
website.save # This will automatically save the associated page too
end
So as soon as your user is created the website and page will also be created.
To get the content for the page from the form you can do:
In User model:
attr_accessor :page_content
This will create a virtual attribute on the user object which will not exist in database. Now in your form:
<%= f.hidden_field :page_content, class: 'js-PageContentHiddenField' %>
So append the content from javascript in this field. Now in your controller permit this attribute too:
params.require(:user).permit(:email, :password, :name, :page_content)
And then finally in the method we wrote above create_website_and_page change the line to this:
page = website.pages.build(name: 'Untitled Page', content: page_content)
This should do the required.
If you still wish to use the nested_form for any reason then the mistake you are doing is the params. Just place a debugger on the top of your create action and check the params. So to permit the parameters of website and page you have to do:
def user_params
params.require(:user).permit(:email, :password, :name, website_attributes: [:name], page_attributes: [:name, :website_id, :content])
end
but you will never get the website_id in params as you are creating that also from the same form. And also the user_id will not be required to be permitted as it will automatically get associated in case of the nested forms.

Unpermitted Parameter in spite of being included in strong params

I have a situation where I am getting the error
Unpermitted parameter: incorporation
however I have it listed in the strong params:
def company_params
params.require(:company).permit(:id, :name, :employee_stock_options, :options_pool, :state_corp, :street, :city, :state, :zip, :_destroy,
incorporation_attributes: [:title, :trademark_search, :user_id, :employee_stock_options, :final_submit, :submit, :_destroy],
names_attributes: [:id, :name_string, :suffix, :approved, :snapshot, :company_id, :_destroy],
There's a bit of a catch that might be contributing to the issue:
The controller in question is actually the Incorporation controller. But, as you might notice, we are using it to create a parent model Company, which has_one :incorporation. I realize that this is a bit odd, but I have reasons for wanting my models to be structured this way AND for using the incorporations_controller for doing it.
Accordingly, I have my form structured in the following way:
<%= simple_form_for #company, url: url_for(action: #caction, controller: 'incorporations'), html: {id:"incorporationform"}, remote: false, update: { success: "response", failure: "error"} do |company| %>
<%= company.simple_fields_for #incorporation do |f| %>
<div class="padded-fields">
<div class="form_subsection">
<%= f.input :trademark_search, as: :radio_buttons, label: 'Would you like us to do a trademark search and provide advice regarding any issues we identify in relation to the name you have selected?', input_html: { class: 'form-control radio radio-false' } %>
</div>
</div>
<% end %>
....
<% end %>
Thanks in advance for any insight
Update: My new and create methods are as follows in incorporations_controller
def new
#user=current_user
#company = #user.companies.build
#incorporation = #company.build_incorporation
#action = "new"
#caction = "create"
end
def create
#snapshot="incorporation"
#company = current_user.companies.build(company_params)
#incorporation = #company.build_incorporation
if #company.save
current_user.companies << #company
if params[:final_submit]
redirect_to incorporations_index_path
else
redirect_to edit_incorporation_path(#incorporation), notice: "Successfuly saved incorporation info."
end
else
render 'new', notice: "Something went wrong; form unable to be saved."
# render :nothing => true
end
end
Update 2: In Case it helps, here are the parameters from the log:
"company"=>{"names_attributes"=>{"145\2853672570"=>{"name_string"=>"test19", "suffix"=>"INC", "_destroy"=>"false"}}, "fiscal_year_end_month"=>"", "fiscal_year_end_day"=>"", "street"=>"", "city"=>"", "state"=>"", "zip"\=>"", "issued_common_stock"=>"10,000,000", "employee_stock_options"=>"false", "options_pool"=>"0", "incorporation"=>{"submit"=>"0"}}, "commit"=>"Save"}
I noticed that (unlike other nested attributes) incorporation does not have the _attributes line after it. Might that be of some significance?
Update3: I also seem to be creating an incorporation entry in the incorporations table with the proper ownership assigned. However no other fields are filled out.
You shouldn't have incorporation in your submitted params anyway - it should be incorporation_attributes (as you've already got in your strong params).
--
If you're using fields_for, you should expect [association]_attributes to be passed as a parameter from your form.
Not having it means you've either not got accepts_nested_attributes_for in your parent model, or you have not built your child object:
#app/models/company.rb
class Company < ActiveRecord::Base
has_one :incorporation
accepts_nested_attributes_for :incorporation
end
--
#app/controllers/incorporations_controller.rb
class IncorporationsController < ApplicationController
def new
#company = Company.new
#company.build_incorporation #-> only needed if a new record
end
def create
#company = Company.new company_params
#company.save
end
end
Update
What a strange issue you have - you're passing names_attributes fine and yet incorporation doesn't work.
The one thing I would say, after looking at your params, is that your incorporation is only passing "submit" => "0". I don't see what that is; anyway there are numerous issues with your form:
def new
#company = current_user.companies.new
#company.build_incorporation
...
end
def create
#company = current_user.companies.new company_params
#company.save #-> don't need to "build" in create
end
This will allow you to...
<%= simple_form_for #company, url: url_for(action: #caction, controller: 'incorporations'), html: {id:"incorporationform"}, remote: false, update: { success: "response", failure: "error"} do |company| %>
<%= company.simple_fields_for :incorporation do |f| %>
<%= f.input ...
<% end %>
When using fields_for, you only need to pass the parent object (in your case #company). Building incorporation will automatically populate fields_for without explicitly declaring it.
The error indicates that we need to define this in company model:
accepts_nested_attributes_for :incorporation
attr_accessible :incorporation_attributes

Best practice in creating belongs_to object

Let's say we have the following situation:
class User < ActiveRecord::Base
has_many :tickets
end
class Ticket < ActiveRecord::Base
belongs_to :user
end
For simplicity let's say Ticket has only some text field description and integer user_id. If we open User's views/users/show.html.erb view and inside User controller we have this code which finds correct user which is selected:
def show
#user = User.find(params[:id])
end`
Now inside that show.html.erb view we also have small code snipped which creates user's ticket. Would this be a good practice in creating it?
views/users/show.html.erb
<%= simple_form_for Ticket.new do |f| %>
<%= f.hidden_field :user_id, :value => #user.id %>
<%= f.text_area :description %>
<%= f.submit "Add" %>
<% end %>
controller/tickets_controller.rb
def create
#ticket = Ticket.new(ticket_params)
#user = User.find(ticket_params[:user_id])
#ticket.save
end
def ticket_params
params.require(:ticket).permit(:user_id, :description)
end
So, when we create a ticket for user, ticket's description and his user_id (hidden field inside view) are passed to tickets_controller.rb where new Ticket is created.
Is this a good practice in creating a new object which belongs to some other object? I am still learning so I would like to make this clear :) Thank you.
You should be able to do something like this in your form:
<%= f.association :user, :as => :hidden, :value => #user.id %>
This will pass user_id through your controller to your model and automatically make an association. You no longer need the #user= line in your controller.
Don't forget that the user could modify the form on their end and send any id they want. :)
See https://github.com/plataformatec/simple_form#associations for more info.
How about getting the user from the controller using current_user so that you protect yourself from anyone that would manipulate the value of the user_id in the form. Also I think this way is much cleaner
views/users/show.html.erb
<%= simple_form_for Ticket.new do |f| %>
<%= f.text_area :description %>
<%= f.submit "Add" %>
<% end %>
controller/tickets_controller.rb
def create
#ticket = Ticket.new(ticket_params)
#ticket.user = current_user
#ticket.save
end
def ticket_params
params.require(:ticket).permit(:user_id, :description)
end

How to get Rails build and fields_for to create only a new record and not include existing?

I am using build, fields_for, and accepts_nested_attributes_for to create a new registration note on the same form as a new registration (has many registration notes). Great.
Problem: On the edit form for the existing registration, I want another new registration note to be created, but I don't want to see a field for each of the existing registration notes.
I have this
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
end
and this
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
and in the view I am doing this:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
and it is creating a blank text area for a new registration note (good) and each existing registration note for that registration (no thank you).
Is there a way to only create a new note for that registration and leave the existing ones alone?
EDIT: My previous answer (see below) was bugging me because it's not very nice (it still loops through all the other registration_notes needlessly). After reading the API a bit more, the best way to get the behaviour the OP wanted is to replace:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for :registration_notes, #registration.registration_notes.build do |n| %>
fields_for optionally takes a second parameter which is the specific object to pass to the builder (see the API), which is built inline. It's probably actually better to create and pass the new note in the controller instead of in the form though (just to move the logic out of the view).
Original answer (I was so close):
Just to clarify, you want your edit form to include a new nested registration note (and ignore any other existing ones)? I haven't tested this, but you should be able to do so by replacing:
<%= r.fields_for :registration_notes do |n| %>
with:
<%= r.fields_for #registration.registration_notes.build do |n| %>
EDIT: Okay, from a quick test of my own that doesn't work, but instead you can do:
<%= r.fields_for :registration_notes do |n| %>
<%= n.text_area :content if n.object.id.nil? %>
<% end %>
This will only add the text area if the id of the registration note is nil (ie. it hasn't been saved yet).
Also, I actually tested this first and it does work ;)
If you want to create a new registration form on your edit action, you can just instantiate a new registration_note object. Right now, your form is for the existing registration object.
I believe this is what you want:
class RegistrationsController < ApplicationController
def edit
#new_registration_note = RegistrationNote.new
#registration = Registration.find(params[:id])
#registration.registration_notes.build
end
end
In your view, you should pass a hidden param that references the registration record id:
<%= form_for #new_registration_note do |r| %>
<%= r.hidden_field :registration_id, :value => #registration.id %>
<%= r.text_area :content %>
<% end %>
Now, you can create your new registration note that belongs to #registration. Make sure you have a column in your registration_notes table to point to the registration. You can read more about associations here: http://guides.rubyonrails.org/association_basics.html
Thank you so much for your help as I said in my post the only problem with the approach from "Zaid Crouch"(I don't know how to make a reference to a user hehe) is that if the form has error fields the form will be clear and boom after the page reloading you'll have nothing filled in your form and can you imagine if you form is like 20 or 30 fields that would be a terrible user experience of course
Here is my solution that works with validation models:
class Registration < ActiveRecord::Base
attr_accessible :foo, :bar, :registration_notes_attributes
has_many :registration_notes
has_one :new_registration, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration
end
class RegistrationsController < ApplicationController
def edit
#registration = Registration.find(params[:id])
#registration.build_new_registration
end
end
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>
I'm using simple_form in my example if you want to see the same working with validations and transaction take a look at the complete post here:
http://elh.mx/ruby/using-simple_form-for-nested-attributes-models-in-a-has_many-relation-for-only-new-records/
As Heriberto Perez correctly pointed out the solution in the most upvoted answer will simply discard everything if there's a validation error on one of the fields.
My approach is similar to Heriberto's but nevertheless a bit different:
Model:
class Registration < ActiveRecord::Base
has_many :registration_notes
accepts_nested_attributes_for :registration_notes
# Because 0 is never 1 this association will never return any records.
# Above all this association don't return any existing persisted records.
has_many :new_registration_notes, -> { where('0 = 1') }
, class_name: 'RegistrationNote'
accepts_nested_attributes_for :new_registration_notes
end
Controller:
class RegistrationsController < ApplicationController
before_action :set_registration
def edit
#registration.new_registration_notes.build
end
private
def set_registration
#registration = Registration.find(params[:id])
end
def new_registration_params
params.require(:registration).permit(new_registrations_attributes: [:content])
end
end
View:
<%= form_for #registration do |r| %>
<%= r.text_field :foo %>
<%= r.text_field :bar %>
<%= r.fields_for :new_registration_notes do |n| %>
<%= n.text_area :content %>
<% end %>
<% end %>

Resources