How are these to rails functions connected? - ruby-on-rails

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/

Related

How work render action in controller?

I have method in controller like this:
def create
#player = Player.new(player_params)
if #player.save
flash[:success] = "Player created"
redirect_to player_path(#player)
else
render 'new'
end
end
And I have also test:
it "add players without nickname" do
visit new_player_path
click_button "Add player"
current_path should eq new_player_path
end
But after call render method my current path is:
http://localhost:3000/players
not
http://localhost:3000/players/new
But the layout is from players/new. Why?
And what should be my test? In test I just want to check if user don't type in nickname in nickname filed application return to create user page (players/new path).
Actually when there is any errors then it moves from new to create action, so the path becomes http://localhost:3000/players, and the render just renders the new action's template here. Now the main question is why so?
As in the new action you have submitted the form with some data to the create action. Now if suppose it sends you back to new action then it would have to redirect you back to that page, so what will redirect do? It will make rails to loose all the form data which you have submitted and the data would never persist. So instead what is done is you remain on the create action which has the url: http://localhost:3000/players and it renders the new action which means the new action's template (or you can say form). It doesn't redirects it just renders it in the same one.
Now the question is how does the data persist? The data persists with the object. This line #player = Player.new(player_params) creates a new object of the Player class with the attributes you have passed in the form. Now as you remain in the same action and the object is persisted so the data on the form is also persisted.
If you want to test it then in your code replace this:
render 'new'
with:
redirect_to new_player_path
and you will notice the data would never persist unless you pass that explicitly.
Hope this helps.

Can I override just part of an ActiveAdmin controller action using `super` + custom redirect?

I've registered a Widget in ActiveAdmin and want to change the redirect that takes place after creating a new one. So that I can accomplish various things with Javascript, I've created a custom form for creating/editing them such that in /admin/widget.rb I have this:
form do |f|
render "create_or_edit_widget"
end
I want to modify the basic Admin::WidgetsController#create action to change where the user is redirected after successfully creating one. I can fill out the rest of the custom action to complete this, except I don't know how to handle a case where the .save fails and the user is redirected back to the form with the formtastic inline error messages. I know how I could do this if I wanted the normal Rails form behavior of creating a list of error messages but not enough about Formtastic to copy its behavior. So far I have this:
controller do
def create
#widget = Widget.new(params[:widget])
if #widget.save
redirect_to admin_widgets_path, notice: "Successfully created Widget."
else
redirect_to :back
end
end
end
I was wondering if I can somehow user super and then only change the redirect path after successful creation instead of having to write out the entire action. If that's not possible, can anyone tell me where in the ActiveAdmin GitHub I'd be able to find the standard #create action so I can copy it out and change the one part?
Yes, you can do that. Here is a working code from my application using super and just changing the redirection
def create
super do |format|
redirect_to admin_submission_discussion_path(id: resource.discussion.slug, submission_id: resource.discussion.client_application.slug) and return if resource.valid?
end
end

When is the object creation invoked in Ruby on rails?

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.

How to send form errors back to where they came from?

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?

Give a detailed illustration of how the MVC works in Rails, and how the controller communicates with forms etc?

Perhaps this can even become a Community Wiki, but I would love a detailed description of how the controller works - or rather, how I can get it to do what I want it to do.
I understand the general structure of MVC and how the model stores the db structure, and the controller interacts with the db and passes info to the view.
However, I am puzzled (on a fundamental level) about how to accomplish simple tasks using my controller. I know that if I want to create a new record for a model/object, I just do object = Object.new(:name => "Object Name") in the Rails console.
But how on earth would I do that in the CRUD elements of the controller and why?
Please use a simple example - e.g. showing a user the balance of their bank account (I know there are many complexities surrounding this, but ignore them for the sake of this explanation). What would the model look like (just include: Name, Address, Transaction Type (Deposits/Withdrawals), Balance).
What would a view look like? What would the controller look like? Any choices you make (like using a form) please explain them. Why would you use a form, as opposed to a drop down menu and (in layman terms) how does the form or drop down menu interact with the controller? How do I get the info captured there to the db and why am I doing it that way?
I know this sounds like a lot to ask, but I have done RailsTutorial.org, watched many Railscasts, read the Rails guides, and read many other tutorials and still have some basic gaps in my understanding of the way Rails works and why.
Thanks in advance.
I don't know how much more help I can be, but I understand your pain having just come to rails myself. The article recommended by ghoppe, "Skinny Controller, Fat Model" explains the function of Ms Vs & Cs nicely. Seeing as that does not fully answer your question I will try to explain the mechanics of each structure.
Model
class Account < ActiveRecord::Base
belongs_to :user
validates_presence_of :address
def name # Account does not have a name field, but User does so I will make a name method for Account and feed it name of the user it belongs to.
user = self.user # Account gets the user method with the <belongs_to :user> association
# note: Rails expects Accounts to have a user_id field so it can perform the "magic" to associate Accounts with Users
if user.name
return user.name
else
return nil
end
end
end
The model describes your object. Like an object in any OOP language you want to put all of your object logic here. This includes the rails helpers for association(has_one, belongs_to, ...) and validation, as well as any other method or library you want the object to be able use throughout your Models Views and Controllers.
Controller
class AccountsController < ApplicationController
before_filter :name, :only => :edit, :destroy # #account.name will be executed before the edit or destroy method(action) can be invoked on #account. If the user who has the account has a name the action will execute.
def index # This is a RESTful action and is mapped by Rails by default to an HTTP GET request. Rails expects an index.html.erb or index.haml.erb or index.something in the Accounts view to map this action to.
#accounts = Account.all # #accounts is an instance variable and will be accessible in the view this action is mapped to.
end
def show
#account = Account.find(params[:id]) # params[:id] is passed to the controller from the view. The params hash is the primary tool form moving data from a form or URL into a controller. Anytime you click on the link_to the show or edit action of an object Rails will put that objects id in the params hash and call the appropriate action in that objects controller. If you click the show link on an account it will call this action. Now the instance variable in the view show.html.erb will hold a single account instead of an array
end
def new
#account = Account.new # This initializes a new account with all the fields set to blank unless you specified a default in your migration. This account has not been save to the db yet. It is ready for a user to fill in.
respond_to do |format| # Rails can automatically respond differently to different client request. If a client i.e browser wants HTML rails responds with HTML. If a client e.g. an API want XML Rails responds with XML.
format.html # new.html.erb #
format.xml { render :xml => #account }
end
end
def edit
#account = Account.find(params[:id]) # Same as show, but mapped to a different view
end
def create # Finally we have a POST. All the prior actions were GETs, but now we are saving some data to the db.
#account = Account.new(params[:account]) # The :account key is special. It is a hash of hashes. It is populated by the form fields in new.html.erb. To access a specific field such as address we say <params[:account][:address]> and whatever the user entered in the address field in the View is at out fingers in the Controller.
respond_to do |format|
if #account.save # If the validations pass and the account gets saved redirect to the show page of the new record, otherwise refresh/render the new page (hopefully showing what error caused the record to fail to save).
format.html { redirect_to(#account, :notice => 'Account was successfully created.') }
format.xml { render :xml => #account, :status => :created, :location => #account }
else
format.html { render :action => "new" }
format.xml { render :xml => #account.errors, :status => :unprocessable_entity }
end
end
end
def update # This is another of the seven RESTful Rails actions and results in a PUT request because you are updating an existing record
#account = Account.find(params[:id])
respond_to do |format|
if #account.update_attributes(params[:account])
format.js # Rails can also respond with JavaScript. Look up UJS. Rails 3 has made large improvements here.
format.html { redirect_to(#account, :notice => 'Account was successfully updated.') }
format.xml { head :ok }
else
format.js
format.html { render :action => "edit" }
format.xml { render :xml => #account.errors, :status => :unprocessable_entity }
end
end
end
def destroy # This results in a DELETE
#account = Account.find(params[:id])
#account.destroy # destroy is a more thourough delete and will check the options of this records associations and destroy the associated objects as well if they are dependant on this object. The option <:dependant => :destroy> is not set for this object's only association: User. The user this account belongs to will therefore survive the destruction of this account.
respond_to do |format|
format.html { redirect_to(accounts_url) }
format.xml { head :ok }
end
end
end
View
Hopefully you can draw your own logic from here. The view is designed to render information passed as instance vars from a controller to a client: browser, api, smart phone. As well as to pass information from a client to the controller via the params hash. No complicated logic should get performed in a view even though a view with erb has the capability to execute any ruby code.
If an example view would also be helpful I am happy to oblige.
The best description of what the controller is:
http://edgeguides.rubyonrails.org/action_controller_overview.html
http://edgeguides.rubyonrails.org/routing.html
The controller doesn't communicate with the Database. The controller talks to the model, which then communicate with the database.
When I was starting I found very useful to use scaffolding and just looking at what was created.
Do this:
rails generate scaffold Post name:string title:string content:text
Examine all files under the app/ folder. Examine the file config/routes
Then comment here your specific questions.
At first, I thought this question was far too broad, along the lines of "how do I program?" But after reading your comments, I see what you're getting at. You don't quite grasp how MVC works in Rails and are wondering where your code goes.
What you should strive for is a Skinny Controller and a Fat Model. Keep logic out of views. So in your example, you calculate the account balance in the Model, and pass that information along (using the controller) to the view.
For a concise explanation for beginners with sample code, I recommend this article over here.

Resources