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.
Related
I am following this tutorial: Youtube rails tutorial
He is creating a basic website where people can add books, review them, rate them, delete them and so on. In the main controller script, he defines a function "new" to add a new book like so:
def new
#book = Book.new
end
def create
#book = Book.new(book_params)
if #book.save
redirect_to root_path
else
render 'new'
end
end
def book_params
params.require(:book).permit(:title, :description, :author)
end
He also uses a creat function for that. He has a "new" view named new.html.erb that actually shows the form where users can add a new book and this is related to the "new" function shown in the controller. The new.html.erb file only has this:
<h1>new book</h1>
<%= render "form" %>
I get most of it, what I don't get is, since the "new" function doesn't do anything and the "create" function is the one doing all the work for the "new" function, how are they related? I am asking this because the "new" function does not "call" the create function at all but the work the "create" function does is shown in the webpage. Are they connected through "#book"?
new action renders new.html.erb, which contains a form to create a new book. When a user clicks submit on that form, that new book is created with the create action.
I will recommend going to more tutorials and reading the guides to get a full understanding on how Rails works.
In Rails the new action displays the form for creating a resource.
create responds to when the user posts a form.
class BooksController < ApplicationController
# GET /books/new
def new
#book = Book.new
end
# POST /books
def create
#book = Book.new(book_params)
# ...
end
end
I am asking this because the "new" function does not "call" the create
function at all but the work the "create" function does is shown in
the webpage. Are they connected through "#book"?
You're fundamentally confused about how web applications and MVC work. Web applications respond to requests coming from a user. The server sends a a response and thats it. The program ends. The server does not sit around waiting for the user to click like a desktop program.*
They are not "connected" at all. new and create respond to a different kinds of requests at different paths. The are never both invoked in same request.
The new action shows and initialize the form. What you complete on it before you click on create button will send to the create action of the BooksController. This action will create a new Book object with the params, and save in DB.
This is MVC pattern. You should read about it: https://www.sitepoint.com/the-basics-of-mvc-in-rails-skinny-everything/
I'm trying to build a profile page that displays posts sent only to the requested user, and allows the visitor to write a post of their own. Because this simplified example should have two distinct controllers: users and posts, I made partials for each post action to render within the user's show action.
Directory structure for my views directory looks like this:
- posts
- _index.html.erb
- _new.html.erb
- users
- show.html.erb
... (etc.)
Section that displays these partials within the user's show.html.erb:
<section>
<h3>Posts:</h3>
<%= render '/posts/new', :post => Post.new %>
<%= render '/posts/index', :posts => Post.where(target_id: params[:id]) %>
</section>
I eventually found out that you could pass variables into the partial in this render line, and though this works, it's very messy and probably doesn't follow the best practices.
Ideally, I'd want these partials to be connected with the posts controller so I can write more complex database queries in a place that isn't the view:
class PostsController < ApplicationController
def new
#post = Post.new
end
def index
#posts = Post.where(target_id: params[:id])
end
def create
#post = Post.new(post_params)
#post.user_id = current_user.id
#post.target_id = params[:post][:target_id]
if #post.save
redirect_to :back, notice: 'You published a post!'
else
render new
end
end
private
def post_params
params.require(:post).permit(:body)
end
end
Currently, I haven't found a way of doing this. I know this is a newb question, but thanks for any help in advance.
You are attempting to treat your controllers like models: doing the post work in post controller and the user work in user controller. But controllers are task-oriented, not model-oriented.
Since you want posts info in your user form, it's typical to gather it in the user controller. E.g.
class UsersController < ApplicationController
def show
...
#posts = Post.where(user_id: user.id)
end
end
That #posts instance variable is visible in the show template and any partials it calls. But many coders prefer to send it explicitly through render arguments, as more functional:
<%= render '/posts/post_list', posts: #posts %>
For one thing it's easier to refactor when you can see at a glance all of the partial's dependencies.
I agree somewhat with #Mori's advice. As he said, you are trying to put too much logic into the controller. I think this was a result of you trying to get it out of the view, which is the right idea, but you want business logic to be in the model.
Also, those index and new actions for PostsController are never going to be called. When you are calling the render posts/new for example, that is rendering the view, not the controller action. So, those controller actions have no reason to exist.
I would implement the fix in perhaps a different way than Mori described. It's a recommended practice to try and pass as few instance variables from the controller to the view as possible (see 3rd bullet in the linked section).
Since it's really the show action of the UsersController we are talking about here, I as someone trying to understand your code would assume the instance variable you are passing to the show view is something like #user.
You may want to use an includes method when instantiating the #user object. The includes statement will allow you to load the additional models you will need to instantiate using the minimum number of queries possible (preventing an N+1 query situation). You probably don't want to load every single one if there are thousands of matching posts, so I put an arbitrary limit of 10 on that.
UsersController
def show
#user = User.find(params[:id]).includes(:received_posts).limit(10)
end
#....
View
<section>
<h3>Posts:</h3>
<% unless #user.id == current_user.id %>
<%= render 'posts/form', post: Post.new(user_id: #user.id) %>
<% end %>
<%= render #user.received_posts %>
</section>
Putting the partial for a new post instead as a view called posts/form will allow you to reuse that form if you want to render an edit action (form_for knows which action to use on submit by calling the passed model's persisted? method).
Note that this code assumes the User model has the second relationship with posts set up to be called received_posts, but you can change it to whatever reflects the reality. By passing the received_posts collection to the render method, Rails is smart enough to know that if you want to render a collection of Post models to look for a posts/_post partial and render one for each Post. It's a little cleaner looking IMO. Just make sure to move your posts/show code into that. posts/show implies this is its own action and not something used as a partial for something else.
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.
I have a page which shows a book and it's many reviews. On this page there is also a form to write a new review and post it to the create action of the reviews controller. When you post the form, the id of the corresponding book gets sent also so that the relationship can be established correctly.
At the reviews controller, we attempt to save the review. Here is what my controller looks like:
def create
#review = current_user.reviews.build(params[:review])
#book = Book.find_by_ean params[:book]
#review.book = #book
if #review.save
redirect_to book_path(#book)
else
# In here I want to go back to book_path(#book), sending #review with it so that I can have access to #review.errors
end
end
Of course, when the review fails to save (review content is mandatory for example) I would like to go back to the book show page and display the form, along with errors to the user. Now as far as I can work out, there are 2 possibilities here:
render "books/show", :review => #review --- This does send back the review with accompanying errors (I think, not 100% on this) but the URL stays as "/reviews" which causes a ton of it's own problems. For example partials which I keep in the "/books" directory can't be found.
redirect_to book_path(#book) --- This does get me back to the right URL but it doesn't send the #review with it so I can't show the error messages.
What's the best way to solve this problem?
I usually solve this by sending the psot data to a member action in the original controller (in this case books) instead of a nested controller. For example:
# routes.rb
resources :books do
member do
post 'create_review'
end
end
And then in your view
# books/show.html.erb
<%= form_for #new_review, :url => create_review_book_path(#book) do |f| %>
...
<% end %>
And finally in your books controller
# books_controller.rb
def create_review
#book = Book.find(params[:id])
#new_review = #book.reviews.build(params[:review])
if #new_review.save
#new_review = Review.new
end
render :action => :show
end
What happens is that the form has been manually directed to post it's data to our new member route in the books controller. If the Review is successfully saved, then we assign a new review object to the variable in preparation for the next review. If it is not succesfull, then the #new_review variable will contain the errors which can be accessed in the form.
Well, you can't really pass an object through the get params (with a redirect) unless you serialize it and you don't want that.
I see two solutions for this problem.
1) Save the review in the books controller
2) Make the form submit via ajax and update the form in the review controller using "render :update"
I don't know which one is the best, this depents on the project specs like can you use ajax?
How can I make a sticky form in rails?
Thanks
Rails scaffolds do this automatically, right? Your form behavior shouldn't be departing much from theirs.
When you do <% form_for #user %>, all of the user's attributes are automatically filled in to that form. When your user fails to validate and does not save, the form is displayed, and #user still has all of the attributes that the user originally submitted; therefore, the form fields fill themselves out as intended.
If upon submission it does not pass validation you want to send the user back to the same action without resetting it. To achieve this you need the following code in your controller:
render :action => 'new'
or
render :action => 'edit'
These 2 would typically be in the create and update method respectively.