Rails form doesn't create single params hash with input value - ruby-on-rails

I am having a little trouble with a form in Rails. (I'm new to Rails)
What I get after submitting the form is this:
--- !ruby/hash:ActionController::Parameters
utf8: "✓"
name: Jim
email: Jim#jim.com
subject: hello
message: goodbye
controller: contacts
action: create
It should be like this:
contact:
name: Jim
email: Jim#jim.com
subject: hello
message: goodbye
I have no idea what I'm doing wrong here. Here is the form (minus all the bootstrap divs and spans):
views/contacts/new.html.erb
<%= form_for(#contact, url: contact_path) do |f| %>
<%= f.text_field :name, name: "name", value: nil, class: 'form-control', placeholder: 'Enter full name' %>
<%= f.email_field :email, name: "email", class: 'form-control', placeholder: 'Enter email address' %>
<%= f.text_field :subject, name: "subject", class: 'form-control',
placeholder: 'Enter subject' %>
<%= f.text_area :message, name:"message", class: 'form-control', rows: 6, placeholder: 'Enter your message for us here.' %>
<%= f.submit :submit, class: 'btn btn-default pull-right' %>
<% end %>
config/routes.rb
get 'contact' => 'contacts#new'
post 'contact' => 'contacts#create'
controllers/contacts_controller.rb
class ContactsController < ApplicationController
def new
#contact = Contact.new
end
def create
#contact = Contact.new(params[:contact]) #<-- always fails because no :contact
if #contact.valid?
if #contact.send_mail
# todo
else
# todo
end
else
flash.now[:error] = params.inspect
render 'new'
end
end
end
models/contact.rb
class Contact
include ActiveAttr::Model
attribute :name
attribute :email
attribute :subject
attribute :message
validates_presence_of :name
validates_format_of :email, with: /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i
validates_presence_of :subject
validates_presence_of :message
def send_mail
ContactMailer.form_message(self).deliver_now
end
end
I have tried using form_for(:contact), routing with resources, changing the model to use the mail_form gem, but still no luck. Of course I could just get all values by doing params[:name] etc. but it bugs me that isn't creating a single hash with all form input values. Does anyone know why this is happening? Thanks in advance.

Remove the name: '...' option, because the helpers already set the name and it's not the one you are setting. In your case, rails will expect that the fields are named like contact[name] and contact[email] instead of the ones you are setting.

Related

How to pass array of of hashes from form to controller in Rails?

I have services fields that can be duplicated multiple times(using JS), and they need to be saved in PostgreSQL database.
Database schema:
create_table "working_acts", force: :cascade do |t|
t.text "services", array: true
end
Controller:
def new; end
def create
#working_act = WorkingAct.new(working_act_params)
if #working_act.save
render json: { data: 'Working act created!', status: 201 }
else
render json: { error: #working_act.errors.full_messages, status: 422 }
end
end
def working_act_params
params.require(:working_act).permit(services: [])
end
My spec factories which has the correct data for services:
services: [{title: 'Create design', describe: 'Create design for Article page'},
{title: 'Update back-end', describe: 'Update back-end for Article page'}]
My form which can be duplicated several times using JavaScript:
.row id="01"
.form-group
= f.label :title, class: 'control-label'
= f.input :title, class: 'form-control'
.form-group
= f.label :description, class: 'control-label'
= f.input :description, class: 'form-control js-select-ua'
.form-group
= f.label :action, class: 'control-label'
.form-group.form-actions
= button_tag 'Add new', type: 'button', class: 'btn js-duplicate-act'
But I got an error Unpermitted parameter: :services and I cannot pass more than one set of parameters, saves only the last set of hashes.
What I'm doing wrong ?
The name attribute of the inputs gets turned into the keys of the formdata pairs sent to your server.
To pass arrays use empty brackets:
irb(main):001:0> Rack::Utils.parse_nested_query("foo[]=1&foo[]=2&foo[]=3")
=> {"foo"=>["1", "2", "3"]}
Hashes are created by using brackets as well:
irb(main):002:0> Rack::Utils.parse_nested_query("foo[a]=1&foo[b]=2&foo[c]=3")
=> {"foo"=>{"a"=>"1", "b"=>"2", "c"=>"3"}}
For an array of hashes combine the two:
irb(main):005:0> str = "services[][title]=foo&services[][description]=foo&services[
][title]=bar&services[][description]=bar"
irb(main):006:0> Rack::Utils.parse_nested_query(str)
=> {"services"=>[{"title"=>"foo", "description"=>"foo"}, {"title"=>"bar", "description"=>"bar"}]}
You don't really need to do this manually though. fields_for can be used to generate scoped inputs. But you really want to ditch the idea of using an array column and create an actual table and an association that you can iterate through:
class WorkingAct
has_many :services
accepts_nested_attributes_for :services
end
class Service
belongs_to :working_act
end
form_with(model: #working_act) do |form|
form.fields_for(:services) do |f|
.row
.form-group
= f.label :title, class: 'control-label'
= f.input :title, class: 'form-control'
.form-group
= f.label :description, class: 'control-label'
= f.input :description, class: 'form-control js-select-ua'
.form-group
= f.label :action, class: 'control-label'
.form-group.form-actions
= button_tag 'Add new', type: 'button', class: 'btn js-duplicate-act'
def working_act_params
params.require(:working_act)
.permit(
services_attributes: [:title, :description]
)
end
See:
https://api.rubyonrails.org/v6.1.3.1/classes/ActiveRecord/NestedAttributes/ClassMethods.html
https://api.rubyonrails.org/v6.1.3.1/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for

uninitialized constant on Rails 4

I'm trying to build my first contact form, that would send someone's name, email and message to my email address to me. I've seen quite a lot of tutorials and answered questions on stackoverflow, but it didn't help me to fix the problem I'm having. I know it has something to do with the routing but I can't figure what and why.
I'm getting the following error:
Routing Error
uninitialized constant ContactController
Here are my files :
routes.rb
match '/send_mail', to: 'contact#send_mail', via: 'post'
match '/mail', to: 'contact#contact', via: 'get'
controllers/contacts_controller.rb
def send_mail
name = params[:name]
email = params[:email]
body = params[:comments]
ContactMailer.contact_email(name, email, body).deliver
redirect_to contact_path, notice: 'Message sent'
end
mailers/contact_mailer.rb
class ContactMailer < ActionMailer::Base
default to: 'mymail#gmail.com'
def contact_email(name, email, body)
#name = name
#email = email
#body = body`enter code here`
mail(from: email, subject: 'Contact Request')
end
end
views/contact.html.twig
<div class="container-content">
<div class="container">
<%= form_tag(send_mail_path) do %>
<div class="form-group">
<%= label_tag 'name', 'Name' %>
<%= text_field_tag 'name', nil, class: 'form-control', placeholder: 'John Doe' %>
</div>
<div class="form-group">
<%= label_tag 'email', 'Email' %>
<%= email_field_tag 'email', nil, class: 'form-control', placeholder: 'johndoe#domain.com' %>
</div>
<div class="form-group">
<%= label_tag 'comments', 'Comments' %>
<%= text_area_tag 'comments', nil, class: 'form-control', rows: 4, placeholder: 'How can I help you?' %>
</div>
<%= submit_tag nil, class: 'btn btn-default btn-about' %>
<% end %>
</div>
</div>
views/contact_email.html.erb
<!DOCTYPE html>
<html>
<head>
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Mail received from <%= "#{ #name } (#{ #email }):" %></p>
<p><%= #body %></p>
</body>
</html>
config/initializers.rb
module Contact
class Application < Rails::Application
config.action_mailer.delivery_method = :sendmail
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = true
config.active_record.raise_in_transactional_callbacks = true
end
end
initializers/stmp_config.rb
config.action_mailer.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "gmail.com",
:user_name => "mymail#gmail.com",
:password => "password",
:authentication => :login,
:enable_starttls_auto => true
}
As I'm newbie in the rails world, I'm not sure at all of what I'm doing, but I've seen a lot of tutorials where people had a code similar to mine, but no one seems to have this problem
Where did i go wrong ?
Thank you in advance
You may want to change your routes.rb as follows (contacts instead of contact):
match '/send_mail', to: 'contacts#send_mail', via: 'post'
match '/mail', to: 'contacts#contact', via: 'get'
Thats one way to create a contact form - however its not really a Rails app. Its just an app that happens to be using Rails.
This is how you can use the Rails conventions to make this more robust and less messy.
Model
Lets generate a model and migration:
$ rails g model enquiry name:string email:string body:text
Then we run the migration:
$ rake db:migrate
So why create a model if we are just sending an email? Because email fails. And you don't want to lose important enquiries from potential customers.
We also want our model to validate that the user provides the required fields:
# app/models/enquiry.rb
model Enquiry < ActiveRecord::Base
validates_presence_of :name, :email, :body
validates_format_of :email, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
end
Controller
Lets create a RESTful route:
# config/routes.rb
# ...
resources :enquiries, only: [:new, :create]
Note that we are calling it enquiries. Instead of using an procedure oriented send_contact route we have the resource enquiries which gives us conventions for how to show, create and update it.
You can see what routes are generated with $ rake routes.
See Resource Routing: the Rails Default.
Lets create a controller for our new route:
# app/controllers/enquiries_controller.rb
# note controllers use the plural form
class EnquiriesController < ApplicationController
# GET '/enquiries/new'
def new
#enquiry = Enquiry.new
end
# POST '/enquiries'
def create
#enquiry = Enquiry.new(enquiry_params)
if #enquiry.save
ContactMailer.contact_email(name, email, body).deliver
redirect_to root_path, success: 'Thanks for getting in touch, we will look into it ASAP.'
else
render :new
end
end
private
def enquiry_params
params.require(:enquiry).permit(:name, :email, :body)
end
end
View
We also need a form:
# app/views/enquiries/new.html.erb
<%= form_for(#enquiry) do |f| %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="form-group">
<%= f.label :email %>
<%= f.email_field :name %>
</div>
<div class="form-group">
<%= f.label :body %>
<%= f.text_area :body %>
</div>
<%= f.submit %>
<% end %>
By using a form builder and a model our form will automatically "play back" the values if the user submits an invalid form.
Your problem in 'ContactController' - it's must be in plural: 'ContactsController'!

Multiple require & permit strong parameters rails 4

In the below case, i am trying to use strong parameters. I want to require email_address, password and permit remember_me fields.
But using like below it only allows the LAST line in the method Ex:- In below case it only take params.permit(:remember_me)
private
def registration_params
params.require(:email_address)
params.require(:password)
params.permit(:remember_me)
end
Another Ex:- In this below case, if i rearrange it like below it will take only params.require(:email_address) where am i going wrong ?
def registration_params
params.require(:password)
params.permit(:remember_me)
params.require(:email_address)
end
UPDATE
Params hash be like
{
"utf8" => "✓",
"email_address" => "test1#gmail.com",
"password" => "password123",
"remember_me" => "true",
"commit" => "Log in",
"controller" => "registration",
"action" => "sign_in"
}
Ok found the answer through a friend ...one way to do this is
params.require(:email_address)
params.require(:password)
params.permit(
:email_address,
:password,
:remember_me
)
Works good.
Stong parameters are to prevent mass-assignment to Active Record models. Your parameters should be set up in a model backed form. Example from the Michael Hartl Tutorial:
REGISTRATION FORM
<%= form_for(#user) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation %>
<%= f.submit "Create my account", class: "btn btn-primary" %>
<% end %>
This will create a parameter that looks like:
PARAMS
{
"utf8" => "✓",
"user" => { email: "test1#gmail.com", name:"Test Name", password: "password", password_confirmation: "password" },
"remember_me" => "true",
"commit" => "Log in",
"controller" => "registration",
"action" => "sign_in"
}
Then in your registration controller you can use strong parameters like:
STRONG PARAMETERS
params.require(:user).permit(:name, :email, :password, :password_confirmation)
It looks like in your case, you are handling a log in, in which case, you only need to use regular parameters to capture the login information.
SESSION CREATION
def sign_in
email = params[:email]
password = params[:password]
if User.authenticate!(email, password)
# do something
else
# do something different
end
end
Edit:
Here is the Rails way for you to handle logins and, I believe, cases where you need to 'require' multiple parameters and provide errors back to the user.
Unlike using strong params, this approach provides feedback to the user (using validation errors) when parameters are missing or blank. This is more user-friendly than throwing an exception.
Create an ActiveModel (not ActiveRecord) form backing object. This form backing object is where you specify which fields are required and when a call to valid? is performed, these fields will be validated.
With this, you will get nice user-friendly errors if:
email is missing
password is missing
email and password do not match
models/session.rb
class Session
include ActiveModel::Model
attr_accessor :password, :email, :remember_me
validates_presence_of :password, :email # these fields are required!
def authenticate
return false unless valid? # this checks that required params
# are present and adds errors to the
# errors object if not
if User.authenticate(:password, :email) # validate credentials
true
else
errors.add(:email, "and password didn't match") # wrong credentials. add error!
false
end
end
end
Create the controller. Here is what your controller would look like for logging in a user:
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
# GET /login
def new
#session = Session.new
end
# POST /login
def create
#session = Session.new(login_params)
if #session.authenticate
# do whatever you need to do to log the user in
# set remember_me cookie, etc.
redirect_to '/success', notice: 'You are logged in'
else
render :new # shows the form again, filled-in and with errors
end
end
private
def login_params
params.require(:session).permit(:email, :password, :remember_me)
end
end
Set up the view
app/views/sessions/new.html.erb
<% if #session.errors.any? %>
<ul>
<% #session.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<%= form_for #session, :url => login_path do |f| %>
<div>
<%= f.label :email, 'Email:' %>
</div>
<div>
<%= f.text_field :email %>
</div>
<div>
<%= f.label :password, 'Password:' %>
</div>
<div>
<%= f.password_field :password %>
</div>
<div>
<%= f.label :remember_me, 'Remember Me?' %>
<%= f.check_box :remember_me %>
</div>
<div>
<%= f.submit %>
</div>
<% end %>
Lastly, make sure the routes are configured
config/routes.rb
get 'login' => 'sessions#new'
post 'login' => 'sessions#create'
2020 solution:
def registration_params
params.require([:email_address, :password]) #require all of these
params.permit(:email_address, :password, :remember_me) #return hash
end

Action Mailer Preview: Email 'new' not found in MessageMailerPreview

I have three mailers, all of which work. But for one (the contact form) the mailer preview doesn't work and I don't understand why.
The error message:
undefined method 'email'
def new_message(message)
#message = message
mail from: message.email, subject: "Message from #{message.name}"
end
In app/mailers/message_mailer.rb I have:
class MessageMailer < ApplicationMailer
default to: "<info#myemail.com>",
return_path: "<info#myemail.com>"
def new_message(message)
#message = message
mail from: message.email, subject: "Message from #{message.name}"
end
end
Part of app/views/messages/new.html.erb:
<%= form_for :message, url: contact_path do |f| %>
<%= f.label :name %>
<%= f.text_field :name, placeholder: 'Name', class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, placeholder: 'Email', class: 'form-control' %>
<%= f.label :content %>
<%= f.text_area :content, placeholder: 'Your message…', class: 'form-control' %>
<%= f.submit "Send", class: "btn btn-primary" %>
<% end %>
test/mailers/previews/test_mailer_preview.rb:
class MessageMailerPreview < ActionMailer::Preview
message = [name: "My Name", email: "noreply#example.com", content: "This is my message"]
MessageMailer.new_message(message)
end
end
The contact form is properly working on the development server, and the other two mailers (password reset and authentication) also work and for these last two mailers the previews also work properly. These two mailers also have model.email in the mailer, and there it does work. I don't understand why the preview for the contact form mailer produces the error message.
I turns out that if I replaced
message = [name: 'My Name', email: 'noreply#example.com', content: 'This is my message']
with
message = Message.first
message.email = "example#example.com"
or even just
message = Message.first
the preview does work. Don't understand why since I would expect the original formulation also to work.
Your mailer class isn't inherited from ActionMailer, which is fine if you have an ApplicationMailer class doing that already, can you post the code for it please? Otherwise try this.
class MessageMailer < ActionMailer::Base

Rails 4: Password Validation Error: "{:password=>["can't be blank"]}" even though form is filled out

I'm relatively new to Rails (using Rails 4), and am having a problem with validation for my user model. Even when the form is fully filled in with both the passwords, when I submit the code two errors print out:
{:password=>["can't be blank"], :password_confirmation=>["doesn't match Password"]}
I would like the user to be saved into the database, but these validation errors are preventing that from happening. What I would like to know is what I need to change in order to get rid of these errors.
I am printing out the params object and it looks like this (the authenticity token is omitted here):
params: {"utf8"=>"✓","authenticity_token"=>"[omitted]",
"user"=>{"username"=>"testuser1", "password"=>"test",
"password_confirmation"=>"test", "email_attributes"=>{"email"=>"d#d.com"},
"first_name"=>"test", "last_name"=>"user", "gender"=>"male", "city"=>"la",
"state"=>"ca", "country"=>"usa", "dob"=>"1980-11-20"},
"commit"=>"Create Account", "action"=>"create", "controller"=>"users"}
So it appears that the password and password_confirmation attributes are getting passed correctly. I am wondering if this may have to do with the virtual attribute password I have defined in the user model, but if that is the case I am still not quite sure how to solve this problem. Any help would be greatly appreciated. Let me know if I need to elaborate further.
Here is relevant code for reference:
Controller:
class UsersController < ApplicationController
def new
#user = User.new
#user.build_email
end
def create
if #user = User.create(user_params)
logger.debug "#{#user.errors.messages}"
logger.debug "params: #{params}"
redirect_to :action => "new"
else
logger.debug "#{#user.errors.messages}"
logger.flush
redirect_to :action => "new"
end
end
private
def user_params
params.require(:user).permit(:username, :password, :password_confirmation, :first_name, :last_name, :gender, :dob, :city, :state, :country, :admin_level, email_attributes: [:email])
end
end
Model:
class User < ActiveRecord::Base
has_one :email
validates_presence_of :username, :email, :password
validates_confirmation_of :password, :on => :create
accepts_nested_attributes_for :email
def password_valid?(candidatePass)
candidatePassAndSalt = "#{candidatePass}#{self.salt}"
candidatePasswordDigest = Digest::SHA1.hexdigest(candidatePassAndSalt)
if (candidatePasswordDigest == self.password_digest)
return true
else
return false
end
end
def password
end
def password=(text)
self.salt = Random.new.rand
passAndSalt = "#{text}#{self.salt}"
self.password_digest = Digest::SHA1.hexdigest(passAndSalt)
end
end
View:
<%= form_for #user, url: {action: "create"}, html: {class: "user-creation-form"} do |f| %>
<%= f.text_field :username %>username<br/>
<%= f.password_field :password %>pw<br/>
<%= f.password_field :password_confirmation %>pwcopy<br/>
<%= f.fields_for :email do |email_form| %>
<%= email_form.text_field :email %>email<br />
<% end %>
<%= f.text_field :first_name %>first<br/>
<%= f.text_field :last_name %>last<br/>
<%= f.radio_button :gender, "male" %>
<%= f.label :gender_male, "M" %>
<%= f.radio_button :gender, "female" %>
<%= f.label :gender_female, "F" %><br />
<%= f.text_field :city %>city<br/>
<%= f.text_field :state %>state<br/>
<%= f.text_field :country %>country<br/>
<%= f.date_field :dob %>dob<br/>
<%= f.submit "Create Account" %><br/>
<% end %>
The issue is your empty getter:
def password
end
It always return nil.
2 small additions to the previous answer, which should resolve your issue by the way.
1) If you're using Rails >3 (I assume you are by looking at your user_params method in the controller) you don't have to specify all those password fields and validations.
ActiveRecord automatically includes this ActiveModel method :
has_secure_password
More details at : http://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password
2) If the uncrypted password/password_confirmation are shown in your log files your app is insecure. Add this to your config/application.rb :
config.filter_parameters = [:password, :password_confirmation]
This should not be needed if you are using has_secure_password in your User model.

Resources