I'm new to RoR and I am having some trouble understanding some of the code.
I tried looking it up but the results haven't helped me.
Here is the code located in the user controller. (If you need any other code, comment it and I'll update
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params) #I didn't see any parameters in the constructor
if #user.save #Checks if #user was saved?
session[:user_id] = #user.id #Creates a session? What's :user_id and #user_id?
redirect_to'/' #Redirects to http://localhost:8000/
else
redirect_to '/signup' #If all fails go back to signup page
end
end
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password)
end
end
This is part of a programming course which failed to explain this to me properly. I'm generally aware that this is for a signup form, but I am having trouble comprehending the create and user_params function processes.
When I'm asking for help I am asking you to lead me through the process of what is happening.
I also need specific help with
params.require(:user).permit(:first_name, :last_name, :email, :password)
#user = User.new(user_params) #I didn't see any parameters in the constructor
user_params is the name of a method. In ruby, you can call a method without writing () after the method name. If you look down at the bottom of the code you posted, you can see the method definition:
private
def user_params
params.require(:user).permit(:first_name, :last_name, :email, :password)
end
end
That method returns something, and that return value is used in the constructor. You can see what the return value is by adding the following to your code:
def create
#user = User.new(user_params)
puts '******'
p user_params
puts '******'
...
...
end
Then look in your server window for the output. You'll see something like:
******
{“first_name"=>”Joe”, “last_name”=>”Smith”, “email”=>”joe_smith#yahoo.com”}
*******
params.require has to do with security. The subject is called strong parameters, which you can read about here:
https://www.sitepoint.com/rails-4-quick-look-strong-parameters/
if #user.save #Checks if #user was saved?
Yes:
By default, save always run validations. If any of them fail the
action is cancelled and save returns false.
session[:user_id] = #user.id #Creates a session? What's :user_id and #user_id?
A session is used to make variables persist from one request to another. A session is like a Hash, and :user_id is just a random key that you are creating in the Hash. You can name the key anything you want, but it should describe the data that you are saving.
#user.id is the value you are saving in the session Hash. The id comes from the user you created here:
#user = User.new(user_params)
I'm generally aware that this is for a signup form, but I am having
trouble comprehending the create and user_params function processes.
First, you use a GET request to display the form for creating a new user--you do that by entering localhost:3000/users/new in your browser. That will display the form. The <form> tag has an action attribute, which specifies the url where the form will send a request accompanied by the data.
If you use your browser's developer tools, you can click on something like Page Source to see the raw html of the form, which will look something like this:
<form class="new_user" id="new_user" action="/users" accept-charset="UTF-8" method="post">
...
...
A POST request that is sent to the url /users is routed to the create action in the UsersController. That's because when you declare a route like:
resources :users
or
resources :photos
Rails uses the chart below to route urls to actions (in this case the urls are routed to actions in the PhotosController):
Note that the url /photos is routed to both the index and the create action. Rails checks whether the request is a GET request or a POST request to determine which action to execute.
For additional information, check out the Rails Guide on routing.
There are two thins going on here. The controller is probably mapped to /users/ path. The controller will direct all POST to create. Similarly, it will direct all GET to index. Which you don't have.
user_params is a function that was created probably as part of your scaffolding. Like rails generate ... In older versions of Rails, it wasn't like this. This allows you to say for the user scope, first_name, last_name, etc are allowed to be submitted via POST. Why is this done? Mostly security. It allows you to whitelisted parameters so that for example user.admin cannot be updated. You can read more about it here.
In your web-app you may want to create and update users' information.
For example, in both your views new.html.erb ( which create new user) and edit.html.erb (which update existing user's information), you will probably render a form to let users type their information (with bootstrap).
<div class='row'>
<div class='col-xs-12'>
<%= form_for(#user, :html => {class: "form-horizontal", role:"form"}) do |f| %>
<div class="form-group">
<div class="control-label col-sm-2">
<%= f.label :first_name,"FName:" %>
</div>
<div class="col-sm-8">
<%= f.text_field :last_name, class: "form-control", placeholder: "Enter username", autofocus: true %>
</div>
</div>
<div class="form-group">
<div class="control-label col-sm-2">
<%= f.label :last_name,"LName:" %>
</div>
<div class="col-sm-8">
<%= f.text_field :last_name, class: "form-control", placeholder: "Enter username" %>
</div>
</div>
<br>
<div class="form-group">
<div class="control-label col-sm-2">
<%= f.label :email, "Email:" %>
</div>
<div class="col-sm-8">
<%= f.email_field :email, class: "form-control", placeholder: "Enter your email" %>
</div>
</div>
<br>
<div class="form-group">
<div class="control-label col-sm-2">
<%= f.label :password, "Password:" %>
</div>
<div class="col-sm-8">
<%= f.password_field :password, class: "form-control", placeholder: "Enter your password" %>
</div>
</div>
<br>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<%= f.submit(#user.new_record? ? "Sign up now!" : "Update your account", class:'btn btn-primary btn-lg') %>
</div>
</div>
<% end %>
<div class="col-xs-6 col-xs-offset-3">
[ <%= link_to 'Cancel request and return to home', root_path %> ]
</div>
</div>
Back to your question:
By doing "params.require(:user).permit(:first_name, :last_name, :email, :password)" will allow the user controller to modify you first_name, last_name, email and password parameter with security.
Related
I have a form in my rails app that accepts nested attributes. However, what I want to do is for rails to reject the creation of the nested model if a checkbox (outside the model itself) is checked.
Any idea on how to pass an attribute to the :reject_if option of the accepts_nested_attributes_for in the model from the controller?
Thank you very much in advance.
EDIT:
My controller looks like this:
def new
#course = Course.new
#course.course_template = CourseTemplate.new
end
def create
#course = Course.new(course_params)
#course.user = current_user
if #course.save
flash[:success] = t(".new_course_created_succefully")
redirect_to courses_path
else
render 'new'
end
end
And the form:
<%= form_for #course do |f| %>
<%= render 'shared/error_messages', error_model: #course %>
<div class="form-group has-feedback mb">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group has-feedback mb">
<div class="checkbox c-checkbox needsclick">
<label class="needsclick">
<%= check_box_tag "template", "1", false, {class: "needsclick"} %>
<span class="fa fa-check"></span>Is Template?
</label>
</div>
</div>
<%= f.fields_for :course_template do |ff| %>
<div class="form-group has-feedback mb">
<%= ff.label :name %>
<%= ff.text_field :name %>
</div>
<% end %>
<% end %>
send that checkbox as a parameter from the form and put the build operation inside an if statement. No need to bother with the reject_if
You need to handle your create and build operations separately. so instead of passing your model all attributes, youll pass the model the model attributes, and the association, the nested attributes
# controller
course = Course.new(course_params.reject{|attrib| attrib == :course_template_attributes})
unless params[:skip_create]
course.course_templates.build(course_params[:course_template_attributes]
end
...
what you need to do is conditionally create the course_templates, so you can just pass Course.new all your course_params because that creates both the course and the templates, which needs to be done separately.
Note I'm shorthanding with that reject statement up there. you can either manually add in the various params or better yet create another method with strong params and whitelist only the model attributes (not including the course_template_attributes)
additionally. the params[:skip_create] is whatever the parameter is for that checkbox that decides whether or not you want to create the templates
I'm creating a wedding rsvp application and have ran into a problem. I have an RSVP Model with some fields then in my form I have added an extra text field (text_field_tag) that does not belong to the RSVP Model, however I want to validate it in my rsvp.rb model to make it required.
The extra field is a "code" field (as seen below) that I will be providing in the wedding invitations.
I also want to "compare" the entered "code" in my controller rsvp_controller.rb to the valid static code upon rsvp creation.
new.html.erb
<%= form_for(#rsvp, :html => { class: 'form-horizontal', role: 'form' }) do |r| %>
<div class="form-group">
<div class="control-label pull-left">
<%= r.label :party, 'Name' %> <span class="required">*</span>
</div>
<div class="control-label">
<%= r.text_field :party, class: 'form-control', placeholder: 'Individual or family name', autofocus: true %>
</div>
</div>
...
<div class="form-group">
<div class="control-label pull-left">
<label for="rsvp_code">Enter code found in invitation</label> <span class="required">*</span>
</div>
<div class="control-label">
<%= text_field_tag 'rsvp_code', nil, class: 'form-control' %>
</div>
</div>
...
<% end %>
rsvp_controller.rb
def create
#rsvp = Rsvp.new(rsvp_params)
#compare the values of the text field to invitation code
#if values match then proceed
#else send error message
if #rsvp.save
flash[:success] = 'Thank you'
redirect_to root_path
else
render 'new'
end
end
rsvp.rb
class Rsvp < ActiveRecord::Base
validates text_field_tag presence: true #or something like this
end
The others answers here will work. But they are very messy. Validations belong on the model.
class Rsvp < ActiveRecord::Base
attr_accessor :rsvp_code
validates :rsvp_code, presence: true
end
You will also need to change your form from: <%= text_field_tag 'rsvp_code', nil, class: 'form-control' %> to <%= f.text_field :rsvp_code, class: 'form-control' %>
Read more on attr_accessor
Since the value is not stored in the model, it doesn't make sense to validate it in there. Add this logic in your controller instead. And depending on the way you store your "static code" your controller logic shoud look like this :
def create
if params["rsvp_code"] == "YOUR_CODE"
Rsvp.new(rsvp_params)
...
else
flash["error"] = 'Your invitation code does\'t match'
redirect_to rsvp_new_path
end
end
I am building my first rails application using the Devise gem for authentication.
The login works great but the new registration (sign up) page delivers an error on the PAGE and will not let me register.
See image
Is this an issue with strong parameters or my application controller. Your help would be greatly appreciated.
This is probably because you have validations enabled in your User model where you specified the email and password can't be blank.
So, you have to provide email and password of a user when trying to save the info.
Update
If that's not the case, then it could be related to strong parameters. See this thread.
In your application_controller.rb file, add the following:
before_filter :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :password_confirmation) }
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:email, :password, :password_confirmation) }
end
Configure your other actions like above too.
More update
The main issue was in the sign up form as it was not using the form builder properly and was not posting the params. I modified the app/views/devise/registrations/new.html.erb file to this:
<h2>Be cool and sign up if you dont have an account:</h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="col-md-4">
<h2>Signup</h2>
<form class="form-signup">
<div class="form-group">
<label for="email">Email</label>
<%= f.text_field :email, class: "form-control", placeholder: "First Name", tabindex: "2" %>
</div>
<div class="form-group">
<label for="password">Password</label>
<%= f.password_field :password, class: "form-control", tabindex: "2", placeholder: "Password", required: true %>
</div>
<div class="form-group">
<label for="password_confirmation">Password</label>
<%= f.password_field :password_confirmation, class: "form-control", tabindex: "2", placeholder: "Password Confirmation", required: true %>
</div>
<div><%= f.submit "Sign up", class:"btn btn-primary"%></div>
</form>
<%= render "devise/shared/links" %>
</div>
</div>
</div>
<% end %>
</div>
making sure the params are posting to the controller properly, and everything is working as expected now.
With what I can see, and what you explained:
you filled in the email and password field
it went through your controller and then to the model
there is validation in your model that is complaining of the absence of email and password.
I am assuming that you are using the sign-up page generated by Devise (this is already set up to get a new user form, bound with a new instance of the User class).
From the above three points (and the assumption), it stands to reason that both the email and password that you filled in are getting lost somewhere along the way.
My guess will be that this is happening somewhere in your controller. According to your routes, that will be the registrations controller (the create action).
Do you have a custom registrations#create controller? if yes => I think your params are not being allowed into building the new User which the save action is being called on.
If that is the case, you will need something like:
def new_user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
and then building and saving the new user as:
#user = User.new(new_user_params)
#user.save
Hope this helps to throw some light into it...
I am building a Rails 4 app which interacts heavily with a SOAP XML webservice. I am successfully making requests to the webservice with Savon using models inside the app.
But now I want to simply pass variables (and one hardcoded value) from the result of one request to pre-fill a form on another page before the user clicks submit and makes another request. I am doing this through an edit link at the moment. When I click on this it automatically calls the webservice (displaying an error message) - I don't want to call it at all at this stage.
I'm guessing that I can solve this somehow by changing the model and/or controller but I'm really stuck right now.
My view page which displays results from first request. (I need to send all the data in the edit link, which is very long):
<% if #search %>
<dl id ="reservation_info">
<h3><%= #search.restaurant_name %></h3>
<h4><%= #search.restaurant_phone_number %></h4>
<!-- HIDDEN -->
<% #search.restaurant_id %>
<dd>Booking ref: <%= #search.confirmation_number %></dd>
<!-- FOR CANCELLATION: -->
<% #search.reservation_id %>
<% #search.allowed_to_cancel_online %>
<dt><strong>Time / Date</strong></dt>
<dd><%= #search.dining_date_and_time %></dd>
<dt><strong>Guests</strong></dt>
<dd><%= #search.size %></dd>
<%= link_to 'Edit', guests_reservations_path(:partner_code => "DEV-DAN-BETH:73411"), restaurant_location_id: #search.location_id, session_id: #search.session_id, dining_date_and_time: #search.dining_date_and_time, first_name: #search.first_name, last_name: #search.last_name, email: #search.email, :method => :post %>
<dt><strong>Area</strong></dt>
<dd><%= #search.location_name %></dd>
<!-- HIDDEN -->
<% #search.location_id %>
<dt><strong>Your details:</strong></dt>
<dd><%= #search.first_name %></dd>
<dd><%= #search.last_name %></dd>
<dd><%= #search.session_id %></dd>
<dd><%= #search.email %></dd>
</dl>
<% end %>
Form which needs to be pre-filled / pre-populated:
<%= form_tag(reservations_path, method: :post) %>
<!-- ALWAYS HIDDEN FIELDS -->
<div class="field">
<%= text_field_tag :partner_code %>
</div>
<div class="field">
<%= text_field_tag :restaurant_location_id %>
</div>
<div class="field">
<%= text_field_tag :session_id %>
</div>
<!-- ACTION FIELDS -->
<div class="field">
<%= label_tag :dining_date_and_time %><br>
<%= text_field_tag :dining_date_and_time %>
</div>
<div class="field">
<%= label_tag :size, 'Guests' %><br>
<%= text_field_tag :size %>
</div>
<!-- HIDDEN PERSONAL DETAILS -->
<div class="field">
<%= label_tag :first_name %><br>
<%= text_field_tag :first_name %>
</div>
<div class="field">
<%= label_tag :last_name %><br>
<%= text_field_tag :last_name %>
</div>
<div class="field">
<%= label_tag :email %><br>
<%= text_field_tag :email %>
</div>
<div class="actions">
<%= submit_tag 'Continue', name: nil %>
</div>
1st request controller (not the best practice I know, but this is the only way it works, already tried to refactor it).:
class SearchesController < ApplicationController
def index
if params[:confirmation_number] && params[:email]
end
end
def show
#search = Search.new params[:confirmation_number], params[:email]
end
private
def search_params
params.require(:search).permit(:confirmation_number, :email)
end
end
Controller for second request (Guest is empty as not sure what to do there):
class ReservationsController < ApplicationController
def index
if params[:partner_code] && params[:restaurant_location_id] && params[:session_id] && params[:dining_date_and_time] && params[:size] && params[:first_name] && params[:last_name] && params[:email]
end
end
def show
#reservation = Reservation.new params[:partner_code], params[:restaurant_location_id], params[:session_id], params[:dining_date_and_time], params[:size], params[:first_name], params[:last_name], params[:email]
end
def guest
end
private
def reservation_params
params.require(:reservation).permit(:partner_code, :restaurant_location_id, :session_id, :dining_date_and_time, :size, :first_name, :last_name, :email, :confirmation_number)
end
end
2nd request model (the one which is calling automatically) Also, N.B Active Record inheritance is redundant here as values are not being saved to DB when webservice is called / form posted:
class Reservation < ActiveRecord::Base
attr_accessor :confirmation_number, :reservation_id, :dining_date_and_time, :size, :session_id, :first_name, :last_name, :confirmation_number, :allowed_to_cancel_online, :restaurant_phone_number, :restaurant_id, :restaurant_name, :location_id, :location_name, :email
def client
Savon.client(wsdl: "http://wsdl-example-uri", follow_redirects: :follow_redirects)
end
def initialize(partner_code, restaurant_location_id, session_id, dining_date_and_time, size, first_name, last_name, email)
message = {'PartnerCode' => partner_code, 'RestaurantLocationId' => restaurant_location_id, 'SessionId' => session_id, 'DiningDateAndTime' => dining_date_and_time, 'Size' => size}
message.merge!('Booker' => {'UserWithoutALogin' => {'FirstName' => first_name, 'LastName' => last_name, 'EMail' => email}})
response = client.call(:book_reservation, message: message)
if response.success?
data = response.to_array(:book_reservation_response).first
if data
#confirmation_number = data[:confirmation_number]
#reservation_id = data[:reservation_id]
end
else
errors.add "Failed to make SOAP request"
end
end
end
Set up boundries!
When dealing with API's and web services - it can be tempting to do it on the model layer - you add a little fetch_from_facebook method and start whittling out attributes_from_facebook methods and so forth. But this is a huge anti-pattern as it creates strong couplings between your application and the external service.
You definitely NEVER want to call an external service in the initialize method of your model as it will make testing extremely difficult. And you should ALMOST NEVER override the initialize method of an ActiveRecord model. If you do your models should at least have the expected interface:
def initialize(hash = {}, &block)
end
Don't be that guy - be nice to your fellow devs and respect the principle of least surprise.
Instead use service objects (plain old ruby objects) to handle fetching data from remote services.
Also if you are not saving to the database don't use ActiveRecord for you models! Instead just use a plain ruby class and include ActiveModel::Model.
What about forms?
Rails has many built in form helpers which make it easy to bind form inputs to a model instance:
<%= form_form(#reservation) do |f| %>
<div class="field">
<%= f.label_tag :dining_date_and_time %><br>
<%= f.text_field_tag :dining_date_and_time %>
</div>
<% end %>
See RailsGuides: Form Helpers. I would consider taking a break and do a basic tutorial of setting up a CRUD app before you continue on with you project. Dealing with external data sources is tricky and you need a solid foundation to start on.
Edit
This is an example of how you would use services to deal with interacting with an external API:
class PushReservationService
# Use dependency injection so we can mock the client in tests
def initialize(client = nil)
#client = client || Savon.client(
wsdl: "http://wsdl-example-uri",
follow_redirects: :follow_redirects
)
end
# return [reservation]
def call(reservation, **kwargs, &block)
# set default options
message = kwargs.merge(
'PartnerCode' => reservation.partner_code,
'RestaurantLocationId' => reservation.restaurant_location_id,
'SessionId' => reservation.session_id,
'DiningDateAndTime' => reservation.dining_date_and_time,
'Size' => reservation.size
)
response = #client.call(:book_reservation, message: message)
if response.success?
data = response.to_array(:book_reservation_response).first
if data
reservation.assign_attributes(
confirmation_number: data[:confirmation_number]
reservation_id: data[:reservation_id]
)
yield reservation, data if block_given?
reservation.save
return true
end
end
false
end
end
Calling them from your controller is pretty easy:
def create
#reservation = Reservation.new(reservation_params)
if #reservation.save
service = PushReservationService.new
if service.call(#reservation)
redirect_to #reservation, notice: 'Confirmed!'
else
# #todo strategy for dealing with failed confirmation.
flash[:alert] = 'Your reservation was created but we could not confirm your reservation.'
redirect_to edit_reservation_path(#reservation)
end
else
# render form again so that user can correct basic validation errors like a wrong email
render :new
end
end
This is basically how I would do based on my experience of dealing with SOAP based payment systems.
Within your boundaries everything is a run of the mill Rails app. Only your services have to deal with the huge PITA that is SOAP and the eccentricities of whatever API you are dealing with.
I currently have a working rails contact form (taken from another SO answer), available on /contact, that uses a controller, model and mailer (Mandrill). It responds to new and create routes in the controller, and still uses activemodel features. The form does not use ajax.
I now need to try to make my contact form work on the home page, as opposed to the /contact page via a modal pop-up.
I have read a number of SO queries, about modals, but all seem to be connected with getting a modal to do something specific - and not so much on creating a modal form to mimic a normal form.
To start I added a working modal, to the homepage.
When I then try to add the form into the homepage model, I run into method errors as the form is part of the home_controller. After copying my new and create controller actions into my home controller, I realized, that the form is just hidden, and is still being run when the page loads (by the index action).
Adding #message = Message.new into the index action does not seem to help - and wouldn't I want this on the modal load ?
Here are the main moving parts of my working form from the contact page, and the working modal box from the homepage - how can I merge the two and retain the functionality I have ?
here is the contact controller:
#/app/controllers/contact_controller.rb
class ContactController < ApplicationController
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
NotificationsMailer.new_message(#message).deliver
redirect_to(root_path, :notice => "Message was successfully sent.")
else
flash.now.alert = "Please fill all fields."
render :new
end
end
end
the message model
#app/models/message.rb
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :subject, :body
validates :name, :email, :subject, :body, :presence => true
validates :email, :format => { :with => %r{.+#.+\..+} }, :allow_blank => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
And the view:
<%= form_for #message, :url => contact_path do |form| %>
<fieldset class="fields">
<div class="field">
<%= form.label :name %>
<%= form.text_field :name %>
</div>
<div class="field">
<%= form.label :email %>
<%= form.text_field :email %>
</div>
<div class="field">
<%= form.label :subject %>
<%= form.text_field :subject %>
</div>
<div class="field">
<%= form.label :body %>
<%= form.text_area :body %>
</div>
</fieldset>
<fieldset class="actions">
<%= form.submit "Send" %>
</fieldset>
<% end %>
Finally within my home page I have this for the modal (not containing the form)
<div class="modal fade" id="modal_contact_form">
<div class="modal-header">
<a class="close" data-dismiss="modal">×</a>
<h3>Contact Form</h3>
</div>
<div class="modal-body">
<p>Test Content for Modal</p>
</div>
<div class="modal-footer">
</div>
</div>
Which is called by this <li>contact</li>
My attempts have been many and varied to get this working up until this point - the main issue I need to get my head around (I think) is that the form is loaded by the index action on the home_controller - Ideally I guess I still want to use the logic in my contact_controller ?
Any advice and suggestions appreciated - at this point I am wondering if there is an easy way just to move a form into a modal or not!
Cheers,
Mizpah
note: In response to queries I have about ajax, I have only avoided ajax due to perceived difficulty and lack of progress with trying it on rails 3.2 - I would be delighted to use it if it would actually be easier to turn this into an ajaxified form ?
It seems that I was not far away after all!
After a pointer from #rubyonrails channel, the answer was:
1) To copy the form as is, into the modal. (note that different styling is probably needed - but this answer gives the functionality needed).
2) To add #message = Message.new into the index action on my home_controller.rb
I had tried something very similar to this, but must of introduced another error, and thus bypassed the correct solution.