Undefined method for form edit text value ROR - ruby-on-rails

I am trying to send some data from view to controller. I am using form. I have one manufacturer controller and one user controller. What I want to do whenever I am creating new manufacturer I am also creating new user with certain values.So my form contains few values for manufacturer and few for user. I tried these things in following ways:
// inside ManufacturersController
def new
#manufacturer = Manufacturer.new
#user = User.new
end
def create
#manufacturer = Manufacturer.new(manufacturer_params)
#user = User.new(user_params)
#manufacturer.save
redirect_to #manufacturer
end
private
def manufacturer_params
params.permit(:name, :license_no, :contact_no, :address, :country, :city, :pincode,:email_id)
end
def user_params
params.permit(:email, :password, :confirm_password)
end
// inside form view
<div class="form-group">
<label class="col-sm-3 control-label">Email Id</label>
<div class="col-sm-3">
<input class="form-control" name="email_id" value="<%= #manufacturer.email_id %>">
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Password</label>
<div class="col-sm-6">
<input class="form-control" name="password" value="<%= #user.password %>">
</div>
Above code is not completed code. I getting error at my form field password. It shows error undefined method password for #<User:0x7a42580>
Am I doing anything wrong? Need some help. Thank you.

In Rails you pass the params for a resource in a hash:
user: { name: 'Max' }
That how the built in form helpers work and it's a good practice.
When creating forms you can use the form helpers to get the correct html name attribute for your inputs.
<%= form_for(#user) do %>
<%= f.text_field :email # user[name] %>
<%= f.password_field :password # user[password] %>
<% end %>
Rails will bind the value of #user.email to the email field.
When you want to create multiple records at once you can use accepts_nested_attributes_for:
class Manufacturer < ActiveRecord::Base
has_many :users
accepts_nested_attributes_for :users
validates_associatiated :users
end
class User < ActiveRecord::Base
belongs_to :manufacturer
end
this lets you do Manufacturer.new(name: 'Acme Corp', users_attibutes: [{ name: 'Max' }]) to create both a Manufacturer and a nested User at once.
This is how you would setup your controller
class ManufacturersController
# ...
def new
#manufacturer = Manufacturer.new
#manufacturer.users.build
end
def create
#manufacturer = Manufacturer.create(manufacturer_params)
# ...
end
private
def manufacturer_params
params.require(:manufacturer)
.permit(
:name, :license_no, :contact_no, :address, :country,
:city, :pincode, :email_id,
users_attributes: [:email, :password, :confirm_password]
)
end
end
And your form:
<%= form_for(#manufacturer) do |f| %>
<%= f.text_field :name # manufacturer[name] %>
<%= f.text_field :license_no %>
...
<%= f.fields_for(:users) do |user_fields| %>
<%= user_fields.text_field :email # manufacturer[users_attibutes][1][email] %>
...
<% end &>
<% end %>
As #Venkat ch already has pointed out storing plaintext passwords in the database is really insecure. Use has_secure_password instead.

Related

Rails 6: Unpermitted Parameter (many-to-many)

RAILS 6
Hey, I'm working on a class system that uses units and assignments as a many-to-many relationship. When I submit a new assignment form with a dropdown collection for units, the unit is not being received by the controller, but no error log is displayed. When I use byebug, the following error is displayed:
Unpermitted parameter: :unit_ids
Even though it has been permitted. Here's my controller for assignments.
class AssignmentsController < ApplicationController
def new
#assignment = Assignment.new
end
def create
debugger
#assignment = Assignment.new(assignment_params)
#assignment.save
if #assignment.save
flash[:success] = "The unit was successfully submitted."
redirect_to units_path
else
render 'new'
end
end
def show
end
private
def assignment_params
params.require(:assignment).permit(:name, :description, :duedate, user_ids: [])
end
end
Using byebug, I know the unit_id is being correctly received, from this form:
<%= form_for(#assignment, :html => {class: "form-horizontal", role: "form"}) do |f| %>
<div class="form-group">
<div>
<%= f.collection_select(:unit_ids, Unit.all, :id, :name, placeholder: "Units" )%>
</div>
<div>
<%= f.text_field :name, class:"form-control", placeholder: "Title of Assignment", autofocus: true %>
</div>
<div>
<%= f.text_area :description, class:"form-control materialize-textarea", placeholder: "Assignment Description", autofocus: true %>
</div>
<div>
<%= f.text_field :duedate, class: "datepicker", placeholder: "Due Date"%>
</div>
<div class="form-group" id="submitbutton">
<div align = "center">
<%= f.submit class: "btn waves-effect waves-light" %>
</div>
</div>
</div>
<%end%>
Here are the relevant models just to be safe. Note that I added the nested lines to both after I received this error because I saw it on another thread, but it doesn't seem to be fixing it.
class Unit < ApplicationRecord
has_and_belongs_to_many :users
has_and_belongs_to_many :assignments
accepts_nested_attributes_for :assignments
end
And the Assignment model:
class Assignment < ApplicationRecord
has_and_belongs_to_many :units
has_many :users, :through => :units
accepts_nested_attributes_for :units
end
The answer was a mix of a couple things, as Rockwell pointed out I was using User instead of Units, but that still didn't fix it. My collection had multiple choices set to false, so my controller wanted simply
params.require(:assignment).permit(:name, :description, :duedate, :unit_ids)
However, when I set multiple to true, that didn't work. Then, it wanted
params.require(:assignment).permit(:name, :description, :duedate, unit_ids[])
My solution was to leave multiple as true, and use the unit_ids[].
You have to update the permitted parameters
def assignment_params
params.require(:assignment).permit(:name, :description, :duedate, user_ids: [], unit_ids: [])
end
You mentioned that is was permitted, but I do not see unit_ids in the permitted params, I do see user_ids. Is there a spelling error? Or do you just need to include the unit_ids in there?
unit_ids is not a column name. You can use accept_nested_attribute or form object to solve this problem.

RoR How to access multiple model params posted through the same form

I am building a simple form with Ruby on Rails to submit an order.
My form needs to submit information from 3 different models: the user, the catalog_item and the order itself.
Here's my order model:
class Order < ApplicationRecord
after_initialize :default_values
validates :quantity, presence: true
belongs_to :user
belongs_to :catalog_item
validates :user_id, presence: true
validates :catalog_item_id, presence: true
accepts_nested_attributes_for :user
validates_associated :user
end
Here's my user model:
class User < ApplicationRecord
has_many :orders
end
Here's my controller:
class OrdersController < ApplicationController
def checkout
#order = Order.new(catalog_item: CatalogItem.find(params[:catalog_item_id]), user: User.new)
end
def create
#order = Order.new(order_params)
if #order.save
# redirect_to confirmation_path
else
# redirect_to error_path
end
end
private
def user_params
[:name, :email, :phone_number]
end
def order_params
params.require(:order).permit(:id, :catalog_item_id, user_attributes: user_params)
end
end
And here is my view form:
<%= form_for #order do |order_form| %>
<%= order_form.hidden_field :catalog_item_id %>
<%= order_form.fields_for :user do |user_fields| %>
<%= user_fields.label :name %>
<%= user_fields.text_field :name %>
<%= user_fields.label :email %>
<%= user_fields.text_field :email %>
<%= user_fields.label :phone_number %>
<%= user_fields.text_field :phone_number %>
<% end %>
<%= order_form.submit %>
<% end %>
This if the form HTML:
<form class="new_order" id="new_order" action="/orders" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="✓"><input type="hidden" name="authenticity_token" value="+z8JfieTzJrNgsr99C4jwBtXqIrpNtiEGPdVi73qJrpiGPpjYzbLwUng+e+yp8nIS/TLODWVFQtZqS/45SUoJQ==">
<input type="hidden" value="1" name="order[catalog_item_id]" id="order_catalog_item_id">
<label for="order_user_attributes_name">Name</label>
<input type="text" name="order[user_attributes][name]" id="order_user_attributes_name">
<label for="order_user_attributes_email">Email</label>
<input type="text" name="order[user_attributes][email]" id="order_user_attributes_email">
<label for="order_user_attributes_phone_number">Phone number</label>
<input type="text" name="order[user_attributes][phone_number]" id="order_user_attributes_phone_number">
<input type="submit" name="commit" value="Create Order" data-disable-with="Create Order">
Here are my routes:
get 'checkout/:catalog_item_id', to: 'orders#checkout', as: 'checkout'
post 'orders', to: 'orders#create'
When I try to save the #order inside the action create I get this error:
#<ActiveModel::Errors:0x007fe95d58b698 #base=#<Order id: nil, quantity: 1, created_at: nil, updated_at: nil, user_id: nil, catalog_item_id: 1>, #messages={:user_id=>["can't be blank"]}, #details={:user_id=>[{:error=>:blank}]}>
However it does work if I do this:
#catalog_item = CatalogItem.find(order_params[:catalog_item_id])
#user = User.new(order_params[:user_attributes])
#user.save
#order = Order.new(catalog_item: #catalog_item, user: #user)
This is what is being sent in the HTTP request when I post the form:
order[catalog_item_id]:1
order[user_attributes][name]:Ana
order[user_attributes][email]:ana#gmail.com
order[user_attributes][phone_number]:123123123
commit:Create Order
I am new to RoR and I don't understand why order_params doesn't have the user but it does have the catalog_item_id.
Any help will be truly appreciated. Thank you!
Assuming that your Order model belongs_to :user, My "suggested-rails-best-practice" solution is as follows:
See Rails Nested Attributes for more info. Basically what Nested Attributes does is it allows you to "create" also an associated record (in your example, the associated User) in just one command:
# example code:
Order.create(
catalog_item_id: 1,
user_attributes: {
name: 'Foo',
email: 'foo#bar.com'
}
)
# above will create two records (i.e.):
# 1) <Order id: 1 catalog_item_id: 1>
# 2) <User id: 1, order_id: 1, name: 'Foo', email: 'foo#bar.com'>
Now that you can also pass in user_attributes as part of the hash when creating an order, it's easy enough to just treat user_attributes as also part of the request params, see controller below.
Model:
# app/models/order.rb
belongs_to :user
accepts_nested_attributes_for :user
# from our discussion, the validation needs to be updated into:
validates :user, presence: true
validates :category_item, presence: true
Controller:
# app/controllers/orders_controller.rb
def create
#order = Order.new(order_params)
if #order.save
# DO SOMETHING WHEN SAVED SUCCESSFULLY
else
# DO SOMETHING WHEN SAVING FAILED (i.e. when validation errors)
render :checkout
end
end
private
# "Rails Strong Params" see for more info: http://api.rubyonrails.org/classes/ActionController/StrongParameters.html
def order_params
params.require(:order).permit(:id, :catalog_item_id, user_attributes: [:name, :email, :phone_number])
end
View;
<%= form_for #order do |order_form| %>
<!-- YOU NEED TO PASS IN catalog_item_id as a hidden field so that when the form is submitted the :catalog_item_id having the value pre-set on your `checkout` action, will be also submitted as part of the request -->
<%= order_form.hidden_field :catalog_item_id %>
<%= order_form.fields_for :user do |user_form| %>
<%= user_form.label :name %>
<%= user_form.text_field :name %>
<%= user_form.label :email %>
<%= user_form.text_field :email %>
<%= user_form.label :phone_number %>
<%= user_form.text_field :phone_number %>
<% end %>
<%= order_form.submit %>
<% end %>
User is a class so fields_for :user creates fields for a new user object.
Try calling order_form.fields_for instead of fields_for to scope the fields_for to your order object.
If you want the user to be able to create orders from the show view for an item you can setup a nested route instead:
resources :catalog_items do
resources :orders, only: [:create]
end
Make sure you have
class Order < ApplicationRecord
belongs_to :user
belongs_to :catalog_item_id
accepts_nested_attributes_for :user
validates_associated :user # triggers user validations
end
class CatalogItem
has_many :orders
end
Then you can do:
# /app/views/orders/_form.html.erb
<%= form_for [#catalog_item, #order || #catalog_item.orders.new] do |order_form| %>
<%= order_form.fields_for :user do |user_fields| %>
<%= user_fields.label :name %>
<%= user_fields.text_field :name %>
<%= user_fields.label :email %>
<%= user_fields.text_field :email %>
<%= user_fields.label :phone_number %>
<%= user_fields.text_field :phone_number %>
<% end %>
<%= order_form.submit %>
<% end %>
# /app/views/catalog_items/show
<%= render partial: 'orders/form' %>
This will set the form url to /catalog_items/:catalog_item_id/orders. which means that we pass catalog_item_id through the URL and not the form params -
- this is a better practice as it makes the route descriptive and RESTful.
Then setup the controller:
class OrderController
# POST /catalog_items/:catalog_item_id/orders
def create
#catalog_item = CatalogItem.find(params[:catalog_item_id])
#order = #catalog_item.orders.new(order_params)
# Uncomment the next line if you have some sort of authentication like Devise
# #order.user = current_user if user_signed_in?
if #order.save
redirect_to #catalog_item, success: 'Thank you for your order'
else
render 'catalog_items/show' # render show view with errors.
end
end
# ...
private
def user_params
[:name, :email, :phone_number]
end
def order_params
params.require(:order)
.permit(:id, user_attributes: user_params)
end
end

Rails - Nested form with complex params

I need to create a campaign with given prizes. My models already are related and accepting nested attributes.
View:
<%= form_for #campaign, remote: true do |f| %>
<% 5.times do |i| %>
<%= f.fields_for :prizes do |prize_form| %>
<div class="form-group">
<%= prize_form.label "prize #{i + 1}" %>
<%= prize_form.text_field :name %>
</div>
<% end %>
<% end %>
<% end %>
Which generates:
<input id="campaign_prizes_attributes_0_name" name="campaign[prizes_attributes][0][name]" type="text">
<input id="campaign_prizes_attributes_1_name" name="campaign[prizes_attributes][1][name]" type="text">
<input id="campaign_prizes_attributes_2_name" name="campaign[prizes_attributes][2][name]" type="text">
<input id="campaign_prizes_attributes_3_name" name="campaign[prizes_attributes][3][name]" type="text">
<input id="campaign_prizes_attributes_4_name" name="campaign[prizes_attributes][4][name]" type="text">
In my controller I have this
class CampaignsController < ApplicationController
respond_to :html, :js
def index
#campaigns = Campaign.all
end
def new
#campaign = Campaign.new
#campaign.prizes.build
end
def create
#campaign = Campaign.new(campaign_params)
#campaign.prizes.build
end
def campaign_params
params.require(:campaign).permit(:name, :date_start, :date_end, :status, :rules, prizes_attributes: [name: []])
end
end
No matter what I do, I always get this error:
Unpermitted parameters: name
I need to make each campaign have a varying ammount of prizes, but I'm not able to make this work. What am I doing wrong?
Thanks.
The campaign_params method should be:
def campaign_params
params.require(:campaign).permit(:name,
:date_start,
:date_end,
:status,
:rules,
prizes_attributes: [ :name ])
end
How permit nested attributes
Your campaign_params are wrong
prizes_attributes: [name: []]
must be
prizes_attributes: [:name]

Virtus: Replace accepts_nested_attributes (one-to-many) with a form object

Since more than a month I try to get behind the secrets of form objects in Rails 4.
Using virtus, I am already able to build very simple forms. However, I fail to develop a form object that replaces accepts_nested_attributes_for (in the model) and fields_for (in the form view).
In this question I explain a small phonebook-example: the form provides the possibility to enter a person's name and 3 phone numbers at once (find the whole code here).
Now I try to do the same with a form object. I get as far as this:
# forms/person_form_new.rb
class PersonFormNew
class PhoneFormNew
include Virtus
include ActiveModel::Model
attr_reader :phone
attribute :phone_number, String
end
include Virtus
include ActiveModel::Model
attr_reader :person
attribute :person_name, String
attribute :phone, PhoneFormNew
def persisted?
false
end
def save
if valid?
persist
true
else
false
end
end
private
def persist
#person = Person.create(name: person_name)
#person.phones.build(:phone)
end
end
# views/people/new.html.erb
<h1>New Person</h1>
<%= form_for #person_form, url: people_path do |f| %>
<p>
<%= f.label :person_name %> </ br>
<%= f.text_field :person_name %>
</p>
<p>
<%= f.fields_for :phone do |f_pho| %>
<%= f_pho.label :phone_number %> </ br>
<%= f_pho.text_field :phone_number %>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
This gives me the error
undefined method `stringify_keys' for :phone:Symbol
line: #person.phones.build(:phone)
I fear however, this is not the only error.
Can you point me the way to realize a one-to-many assignment with a form object (preferable using Virtus)?
One solution is to create the associated object in a separate function on the form model. I was succussful by doing the following:
def persist!
#user.save!
#account.save!
create_admin_membership
end
def create_admin_membership
#membership = Membership.create! do |membership|
membership.user = #user
membership.account = #account
membership.admin = true
end
end
You can find an extended explanation here: http://w3facility.org/question/how-to-create-another-object-when-creating-a-devise-user-from-their-registration-form-in-rails/

Multi-model nested form, can't add users to current account

I've searched everywhere for a solution but haven't come up with any.
The part that works: My app allows customers to create an account using a nested form. The data collected creates records in four models - accounts, users, accounts_users (because a user can be associated with many accounts), and profile (to store the user's fname, lname, phone, etc).
That part that doesn't work: Once logged in, I want the users to be able to add more users to their account using the form below. I don't receive any errors upon submit but I am brought back to the same form with no additional records created. Any help would be awesome!
Here is the nested form...
<%= form_for #user, :validate => true do |f| %>
<fieldset>
<%= f.fields_for :profile do |p| %>
<div class="field">
<%= p.label :first_name %>
<%= p.text_field :first_name %>
</div>
<div class="field">
<%= p.label :last_name %>
<%= p.text_field :last_name %>
</div>
<div class="field">
<%= p.label :phone %>
<%= p.text_field :phone %>
</div>
<% end %>
<div class="field">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="actions">
<%= f.submit 'Create New User', :class => "btn btn-large btn-success" %>
<%= cancel %>
</div>
</fieldset>
The ApplicationController scopes everything to the current_account like so:
def current_account
#current_account ||= Account.find_by_subdomain(request.subdomain) if request.subdomain
end
The UsersController
def new
#user = User.new
#user.build_profile()
#current_account.accounts_users.build() #Edit2: This line was removed
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
def create
#user = User.new(params[:user])
#user.accounts_users.build(:account_id => current_account.id) #Edit2: This line was added
if #user.save
# Send Email and show 'success' message
flash[:success] = 'An email has been sent to the user'
else
# Render form again
render 'new'
end
end
Models look like this:
class Account < ActiveRecord::Base
attr_accessible :name, :subdomain, :users_attributes
has_many :accounts_users
has_many :users, :through => :accounts_users
accepts_nested_attributes_for :users
end
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation, :profile_attributes
has_many :accounts_users
has_many :accounts, :through => :accounts_users
has_one :profile
accepts_nested_attributes_for :profile
end
class AccountsUser < ActiveRecord::Base
belongs_to :account
belongs_to :user
end
class Profile < ActiveRecord::Base
belongs_to :user
attr_accessible :first_name, :last_name, :phone
end
Edit2: It turns out that I had required a password + password_comfirmation validation in the User model which prevented me from adding another user without these fields. I commented out these validations plus removed the line: current_account.accounts_users.build() in the 'new' action and added the line: #user.accounts_users.build(:account_id => current_account.id) in the 'create' action.
"I want the users to be able to add more users to their account using the form below." I assume you mean profiles (since your nested form is on profiles)?
If that's the case, I think your UsersController's create action isn't associating the profiles with users by using new.
Try this...
def new
#user = User.build
#profile = #user.profiles.build #build adds the profile to user's associated collection of profiles, but new doesn't
...
end
def create
#user = User.build(params[:user])
if #user.save
....
end
end
If you want the user to be associated with account, then you need to put the new and create actions in the AccountsController and do something similar to nest association of the users and profiles records.
Btw, the reason that it went back to new is because you render new at the end of the create, in case that's also part of the question. Hope that helps!

Resources