I am in chapter 8 of the book, where we are trying to implement the signup functionality for the sample app with actions "new" and "create". Here is my questions about these 2 methods/actions,
The "new" action/method is defined as below in the User controller
class UsersController < ApplicationController
.
.
.
def new
#user = User.new
#title = "Sign up"
end
end
here the #user is defined so that its is accessible in the form of the html page for signup. As soon as the user hits signup button the "create" action/method of the user controller gets called, the code for the create action/method is below,
class UsersController < ApplicationController
.
.
.
def create
#user = User.new(params[:user])
if #user.save
# Handle a successful save.
else
#title = "Sign up"
render 'new'
end
end
end
Here is my question,
why are we calling "User.new" twice once inside the "new'method/action and inside "create" method/action" ?
Thanks for the reply,
what if I implement the create method like the one below, I have removed the second call to new, Is this wrong. if so what is wrong ?
def create
if #user.save
# Handle a successful save.
else
#title = "Sign up"
render 'new'
end
end"
Thanks
If you are using the form_for implementation on the erb view.
This uses the #user object to associate the fields with the objects attributes.
This fields will be passed as key value pairs.
<%= form_for #user do |u| %>
<%= f.text_field :name %><br />
<%= f.text_field :age %><br />
<%= f.submit %>
<% end %>
For this you create a blank user object in the new method.
In the create method you create the object with the params submitted.
This helps you to create a User object directly from the parameters, and validate and save the object.
#user = User.new(params[:user])
After the submission of the form, the request params are passed to with the key as user object attributes.
The first time User.new is called, you are creating a model in memory that is used to generate the fields to populate the new user view. That html page then gets returned to the client, and the server forgets all about it. When the client fills out the form and commits it back to the server the create method gets called on the controller. The first thing the controller does is make a new User model, and populate it with the parameters. Until then, nothing has been persisted which is why the new method gets called twice
The first new in the new action is needed to get an empty object, which later is used in the user form in the view, so that Rails form helpers can determine the form object and have something to get the information Rails needs to automatically set all the default values of the form (like the default url to your UserController). With this information the form and page are rendewred and Rails forgets about it. (If the model has default values for some attributes, those will be set too and would appear in the form)
Now you have the form in your browser, fill in the values and submit it. This submit is handled by the create action and here the second new creates an object and fills it with the values submitted from your form and now available in the params hash. This object will have values and the #user.save call will save them to the database if they pass validation (result true). If there are errors, like missing data in mandatory fields, the save will fail and the form from the 'new' view will be rendered again. This time with the data in the object that was created, so all valid data will be filled in the input fields.
Related
I'm trying to implement a form with the following behavior:
1) Input some semantic data about a user (i.e. username).
2) Do a User.find_by(:username = username).
3) If such a user exists, direct to show page for that user.
4) If such a user does not exist, create a new user with the provided username, then redirect to the show page for that user.
This should be simple but I cannot figure out how to format the form_for helper and my show and create actions in my user_controller to implement this behavior.
I currently have:
form_with :url => 'users/:id', :method => :get do
...
end
because I'm ultimately trying to invoke the "show" method of the controller. However, my form does not take in a user's id as a parameter, and when the user does not yet exist there is no :id parameter to access at the time of the form's submission.
How can I set up my form to redirect to show in each case, while still adhering to the logic explained above?
You can do something like this in your User's Controller create action
def create
usr_name = params[:username]
#user = User.where(username: usr_name).first_or_initialize
if #user.persisted?
redirect_to user_path(#user) # or whatever your user show path is
elsif #user.save
redirect_to user_path(#user)
else
render :new
end
end
You would just need to make sure that you are validating the uniqueness of usernames.
Also, first_or_initialize(and its counterpart first_or_create) can take in a block. So, you can assign other attributes to the new User like this...
User.where(username: usr_name).first_or_initialize do |usr|
usr.some_attribute = some_value
end
you can use find_or_initialize_by(unique_key) in your create method.unique_key can be any key which you are using to identify your user such as email,phone etc.
I have a small problem:
I am trying to send a variable from controller 1 to controller 2 and then send it back to controller 1. Here is the logic:
In order to start making a new model, the user has to sign in.
Guest users must also have access to the path of the new model.
If not logged in, guest users have to be redirected to sign in.
Once logged in, users have to go back to the path they previously tried to access.
I decided to pass the type of the model to the log in screen and then pass it back to the new model action.
The variable type usually comes from the route, so upon redirecting to the login screen, I just pass it over.
businesses_controller.rb:
before_filter :require_login
def require_login
unless current_user
redirect_to signin_path( :type => params[:type])
end
end
When I get redirected, the variable gets passed into my url - so far, so good:
/signin?type=ccompany
The problem comes when I try to grab the variable from the URL and use its value to redirect the successfully logged in user back to where he tried to go to:
sessions_controller.rb:
class SessionsController < ApplicationController
before_filter :initialize_type , :only => [:new , :create]
def new
end
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
sign_in user
if defined?(#type)
redirect_to send("new_#{#type}_path")
else
redirect_back_or user
end
else
flash.now[:error] = t('sign.invalid')
render 'new'
end
end
def destroy
sign_out
redirect_to root_url
end
private
def initialize_type
#type = params[:type]
end
end
When I put the #type variable into my view, its value renders without problems. But when I try to log in, this is the error I get:
undefined method `new__path' for #<SessionsController:0x007f76189d42f8>
I also tried
redirect_to :controller => "businesses" , :action => "new" , :type => params[:type]
But the type symbol passes with no key. I also tried putting a variable there, still doesn't work.
So far, it has only worked when I just write down a string that matches the path, but that is not what I need. I need to pass the value of the params[:type] hash. I've put the value of the variable in the view and it's all good, but when I try to pass it into a function, its value somehow 'disappears'.
How can I make this work?
Problem is:
if defined?(:type)
it always returns true, as a symbol is always defined. What you want is:
if defined?(#type)
however this might add 'unintentional feature' to your code, as nil is well defined in ruby. You should do:
if #type
NOTE:
I need to pass the value of the :type symbol. - symbol has no value. Variables do have values, symbol is a symbol and it is its own value.
After understanding the question:
The above still holds.
You cannot pass instance variable from one action to another. For each request rails instantiate new controller instance and all instance variables are lost. There are number of ways to pass it though.
Create hidden field in your form to hold the value. You need to keep in mind that its value can be overwritten by any internet user with a minimal knowledge of how internet works.
In your new action simple write the value into a session and read it in you create action. Since session is either stored server side or stored in encrypted cookie, there is very small chance anyone can temper with it.
Ok, I found a workaround.It's a bit clumsy and may cause security issues, but it works. I forced the variable to pass as a session parameter.
I put the received variable as a hidden field in the new session form:
<%= form_for(:session , url: sessions_path ) do |f| %>
<%= f.label :email , t('session.email') %>
<%= f.text_field :email %>
<%= f.label :password , t('session.password') %>
<%= f.password_field :password%>
<%= f.hidden_field :val , value: #type %>
##type is set in the before filter as a variable passed from another controller
Then, upon submit, I just catched the parameter and put it into its place:
if user && user.authenticate(params[:session][:password])
sign_in user
if defined?(#type)
params.require(:session).permit(:val)
#type = params[:session][:val]
redirect_to send("new_#{#type}_path")
else
redirect_back_or user
end
else
flash.now[:error] = t('sign.invalid')
render 'new'
end
end
Now the function works properly. Unfortunately, I have to use the view as a "conduit" that passes a variable. I would be really happy if somebody writes a helper method for this or a better workaround.
Cheers.
Beginner with some dev experience here.
I have an app with multiple models and I have managed to work everything out but i am stuck here.
I have a model, called CartEntries
class CartEntry < ActiveRecord::Base
acts_as_paranoid
belongs_to :cart
belongs_to :sign
With a create method in the Cart Entry controller
def create
#entry = #cart.entries.create(entry_params)
if #entry.save
flash[:notice] = translate 'flash.notice'
else
flash[:error] = translate 'flash.error'
end
support_ajax_flashes!
respond_to do |format|
format.html # renders view
format.json { render json: #entry }
end
end
And a Model Sign with static signs inputed in the database and no create method.
class Sign < ActiveRecord::Base
has_many :cart_entries
accepts_nested_attributes_for :cart_entries
And from a Sign's view I initialize a new instance of a CartEntry and succsessfuly create a new Cart Entry after clicking the link, generating a notification.
<% #entry = CartEntry.new(sign: #sign)%>
<%= link_to t('.add_to_cart'), user_cart_entries_path(:entry => #entry.attributes),method: :post, remote: true, "data-type" => :json%>
The Cart Entry has another field called Count with a default value of 1. Im looking for a way for the user to input this number in a text field and when creating the Cart Entry , pass the Count the user inputed instead of the default 1.
What ever I try passes the default value.
While
<% #entry = CartEntry.new(sign: #sign, count: 5)%>
Does the trick properly, and passes 5 as the value , but I want the user to input this number since its clearly a variable.
While I understand that
<% #entry = CartEntry.new(sign: #sign)%>
Initializes the entry object on page load and that i must move it, I'm asking you kind people, where?
UPDATE
Entry Params:
private
def entry_params
params.require(:entry).permit(:sign_id, :count)
end
Answering in reverse order from your question:
Initializing the new CartEntry object should probably be in the new action of your controller. Rails controllers often have both new and create, new being tied to rendering the form to receive input and create being the action tied to the 'Submit' button. Your new action is often just something like:
def new
#entry = CartEntry.new(sign: #sign)
end
Your view to prompt the user for data should be named new.html.erb and have the form in it.
For getting the data from the form to your create method, you are half way there I think. If you moved the example you gave:
<% #entry = CartEntry.new(sign: #sign, count: 5)%>
to the create action in the controller, it would be
#entry = CartEntry.new(sign: #sign, count: params[:count])
#entry.save
Remember 'params' is just a hash that contains the form input data.
Hope that helps!
class UserSessionsController < ApplicationController
def new
#user_session = UserSession.new
end
def create
#user_session = UserSession.new(params[:user_session])
if #user_session.save
flash[:notice] = "Successfully logged in."
redirect_to root_path
else
render :action => 'new'
end
end
Am new to RoR, So long i have been working on tradition c/c++ so i have some basic doubts about object creation and stuff,
In UserSessionsController there is two methods namely "new" and "create". In the "new" method an object for UserSession is created without any parameters and in "create" method again object is created with some parameter.
Initially i thought that the "new" method is redundant and removed it. But i recieved the following error
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
The code works fine if i include the "new" method. I couldn't see this method being called anywhere in the entire code. Am referring to following sample project
railscasts/160-authlogic
Kindly let me know how this object creation is happening.
Thanks.
new and create are part of CRUD.
new action is used to render the new view for the UserSessionsController. In new action you simply create an instance of UserSession model class with #user_session = UserSession.new. After this, new.html.***(* is template handler like erb, haml, etc) is rendered where you will enter details of UserSession object that you would like to be created. Upon submission of this form create action would be invoked.
In create action you collect the parameters passed from the new view with #user_session = UserSession.new(params[:user_session]) and when you say #user_session.save it actually creates a record in database table user_sessions
UPDATE
The new action is invoked when you click on the Login Link. Why is it invoked? Because you have defined the login_path in routes.rb
Since you are a beginner I would highly recommend you to:
Read the Getting Started with Rails which will help you to understand the fundamentals of a Rails Application development.
Then, I also recommend you to complete Learn Rails by Example By
Michael Hartl.
And finally, watch the Ruby on Rails Railscasts By Ryan Bates.
Although, you can search on Google and you will find many great resources for the Rails beginners but the above 3 are THE de facto ones.
The 'new' action is generally used in combination with a user interface that will accept input from the user such as a form. It is not strictly necessary that the new action create a new UserSession object, but it is necessary if you want to use a "form_for" helper.
<% form_for #user_session do |f| %>
As you can see, if #article is not defined, this form will raise an error. The benefits of using form_for are that rails will automatically generated the correct params for you when you submit the form and send the form-data to the create action. For example:
<% form_for #user_session do |f| %>
<%= f.label :user_session %><br />
<%= f.text_field :user_session %>
<p><%= f.submit "Submit" %></p>
<% end %>
This form will create a param user_session[:user_session] when you submit the form. Now, when you call:
#user_session=UserSession.new(params[:user_session])
the #user_session object will have its user_session attribute automatically set to the value passed in by the form. This might seem trivial when there is only one attribute, but in a form with many attributes the ability to instantiated a new object and set all the attributes in one line is nice.
This functionality can be recreated by hand but the form_for helper does all the work for you.
In UserSessionsController the new and create methods refer to controller actions that correspond to particular RESTful HTTP requests (routes). In this case, a GET /user_sessions/new HTTP request would invoke UserSessionsController#new and a POST /user_sessions HTTP request would invoke UserSessionsController#create.
The new action renders the form (found at views/user_sessions/new.html.erb) for creating a new user session. That view expects you to provide a user session object as #user_session, which is accomplished in the controller's new action by the #user_session = UserSession.new statement. Without that line, the view is trying to render the form with a nil object reference, resulting in your error.
The create action handles the form submission that comes from new. It expects to see a hash of properties that are appropriate for a UserSession. UserSession.new is called with that hash of properties, creating a new UserSession populated with data from the submitted form. Calling save on the UserSession instance runs validations, which can potentially fail. You can see that if the save succeeds, the controller will redirect the user to the root URL with a "Success!" flash message. If it fails, it sends the user back to the form to fix their mistakes.
I am trying to figure out the best way to do the following (there are a few ways I can think of, but I want to know what the best way to handle it is):
A user is putting together a shipment, and then clicks the "Send" link, which sends him to the /shipments/:id/confirm page. The confirm action checks to see if the user has a completed ShippingAddress; if not, it sends him to the ShippingAddress#new. (If he does, it render the confirm page.
I want the user to be able to complete the ShippingAddress#new page, submit it, and then be redirect back to the /shipments/:id/confirm. How can I do that? How can I pass the :id to the ShippingAddress#new page without doing something like redirect_to new_shipping_address_path(shipment_id: #shipment.id) in the Shipment#confirm action? Or is that the best way to do that?
class ShipmentsController < ApplicationController
def confirm
#shipment = Shipment.where(id: params[:id]).first
unless current_user.has_a_shipping_address?
# Trying to avoid having a query string, but right now would do the below:
# in reality, there's a bit more logic in my controller, handling the cases
# where i should redirect to the CardProfiles instead, or where I don't pass the
# shipment_id, and instead use the default shipment.
redirect_to new_shipping_address_path(shipment_id: #shipment.id)
end
end
end
class ShippingAddressesController < ApplicationController
def new
#shipment = Shipment.where(id: params[:shipment_id]).first
end
def create
#shipment = Shipment.where(id: params[:shipment_id]).first
redirect_to confirm_shipment_path(#shipment)
end
end
[In reality, there is also a CardProfiles#new page that needs to be filled out after the shipping address is].
Try calling render instead of redirect_to, and set the id into an instance variable. Adjust the view logic to pull that instance variable if it exists.
#shipment_id = #shipment.id
render new_shipping_address_path
In the view
<%= form_for #shipment_address do |f| %>
<% if #shipment_id %>
<%= hidden_field_tag :shipment_id, #shipment_id %>
<% end %>
I don't know your view logic entirely, but giving an example.