How is params[:id] available to the create action in Rails? - ruby-on-rails

I'm learning Rails and am practicing by writing the classic blog application.
def update
#article = Article.find(params[:id])
#article.update(article_params)
flash.notice = "Article '#{#article.title}' updated!"
redirect_to article_path(#article)
end
How is params[:id] available to the update method? My form_for just passes in the #article object from:
def edit
#article = Article.find(params[:id])
end
From looking at http://guides.rubyonrails.org/form_helpers.html#binding-a-form-to-an-object, the url that the form will POST to is /articles/create. It doesn't seem to have a query string, so id doesn't seem to be sent via GET. And from what I understand, params[:articles] is all that is being passed in via POST. So how is params[:id] available to the update action?

update is a PUT, and the url constructed is /articles/1
create is a POST, and the url constructed is /articles
This is what really is in the doc.
So now you can see where the id param comes from in update, and yes there is no id for create :)

It's go to update through rails router. Read this. If you look into config/routes.rb of your application you will find something like this:
resources :articles
and if you run rake routes task inside you application it'll return something like:
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
so it's how RESTful resource are maps HTTP verbs. So when you create article there is no :id because obviously you will get it from DB auto increment counter. And when you update it, you already have id and route your request to /articles/:id with HTTP PUT request. And :id there is query param that will be available in controller action.

#article = Article.find(params[:id])
is returning instance of article include the ID of the article and all the fields that are in the database.
then (i guess you have form) in parsing the instance and show all the fields in the model.
then when you press submit it pass all changes in hash into update method(PUT method in browser).
hope in answered your question.

Related

Ruby - redirect_to redirects to the correct page, but isn't changing the URL

I am teaching myself Ruby and Rails... and until today, have been feeling pretty good about it. :)
However, today I ran into an issue I can't seem to crack. I am practicing by creating a blog. I have the routes and views set up - and the code will write to the database - but the redirect_to doesn't seem to be working properly.
If the user navigates to: .../article/new, they can enter a new article. Upon successful submission, the app should redirect to the URL ../article/id, display the article on show.html.erb and flash a message that the article saved successfully.
For some reason, I cannot get the URL redirect portion to work. The article saves, the show.html.erb displays with the proper message... but the url stays ../article/new. If the redirect doesn't work, the rest of the application won't work because other actions require different URLs (../article/id/edit, for example).
I have been using <%= #article.inspect %> on the show page and it looks like everything has saved correctly (which I suspected because it calls the correct view).
Any thoughts would be very, very welcome. I'm really excited to be learning Ruby and Rails and look forward to getting through this.
articles_controller.rb:
class ArticlesController < ApplicationController
def new
#article = Article.new
end
def create
#article = Article.new(article_params)
if #article.save
flash[:notice] = "Article was successfully created"
redirect_to article_path(#article)
else
render 'new'
end
end
def show
#article = Article.find(params[:id])
end
private
def article_params
params.require(:article).permit(:title, :description)
end
end
rake routes:
Prefix Verb URI Pattern Controller#Action
root GET / pages#home
about GET /about(.:format) pages#about
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
Have you tried changing article_path to article_url? This might trigger the behavior you are looking for.
After digging around, a friendly user at the cloud9 forums was able to help me figure it out. I was running the app in a c-9 window... running it in an actual browser window fixed the problem.
Here is a link to the conversation where it was figured out over at the cloud9 forum:
https://community.c9.io/t/ruby-rails-redirect-to-not-working-properly/3644
And thanks again to Oxyrus for helping me figure it out.

what is the difference between redirect_to #article and article_path

I am following this rails guides. And I have this following code
class ArticlesController < ApplicationController
def new
end
def show
#article = Article.find(params[:id])
end
def create
#article = Article.new(article_params)
#article.save
redirect_to #article
end
private
def article_params
params.require(:article).permit(:title, :text)
end
end
In the above code, in the create action, I have modified #article to article_path. Which I believe is the same as per the routes.
Prefix Verb URI Pattern Controller#Action
welcome_index GET /welcome/index(.:format) welcome#index
root GET / welcome#index
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
# the above route
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
So as per the routes, I mentioned the article_path. But when I did that, it redirects me to /articles instead of /articles/:id
can anyone explain me what is happening.
This seems pretty obvious to me.
When you redirect to #article you are redirecting to THAT article defined by that instance stored in the variable. This surely means /articles/:id, where :id is the id of THAT article stored in the variable.
But when you redirect to articles_path you're not going to a particular article, but to the URL of all articles, i.e., /articles. If you redirect to article_path (now singular) without telling which article do you want, you are going to be redirected the same, to a point where you may find all articles, i.e., /articles
It is just a question of thinking about the semantics of the REST calls.
Hope it helps!
article_path will need to be called with a param of either article.id or #article (an object that responds to to_param)
For a correct redirect you have to use redirect_to article_path(#article). This way Rails knows to what article it should redirect to.

How is this sent to the Show action

I am new to ruby on rails and i want to understand how this piece of code redirect to the show action thanks :
def create
  #article = Article.new(params[:article])
  #article.save
  redirect_to #article
end
If you define your routes using the Rails convention of defining RESTful routes i.e. resources :articles in your routes.rb file, then redirect_to #article will take you to the show page of this particular #article instance. Rails is doing the underlying magic here.
When you write resources :articles in your routes.rb file, Rails is generating these routes for you automatically:
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
So you have this particular route which is mapped to articles controller's show action:
article GET /articles/:id(.:format) articles#show
This route is matched when you do: redirect_to #article and that is why it's taking you to the show page of this #article.
To know more about how RESTful routes works, see this Rails tutorial
Basically you have the following route:
Prefix Verb URI Pattern Controller#Action
article GET /articles/:id(.:format) articles#show
In order to redirect to the show action of a specific article, you need to end up with a string of the form '/articles/:id', and there are several levels of syntastic sugar to accomplish this:
redirect_to #article
redirect_to article_path(#article)
redirect_to article_path(#article.id)
You could also explicitly specify the path, or even the full URL:
redirect_to "/articles/#{#article.id}"
redirect_to "http://myapp.com/articles/#{#article.id}"
although I wouldn't recommend that.
See the API docs for more ways of using redirect_to
I am new to ruby on rails
To understand how it works, you need to understand the nature of Ruby, rather than Rails.
OOP
Because Rails is built on top of Ruby, it's an exponent of the object orientated programming pattern - the idea that every element of your application should be geared towards the creation & manipulation of objects:
Although the answer to your question lies in the Rails application of this methodology, its understanding begins in how Ruby gets it to work.
In order to have a truly object orientated experience, Ruby builds classes which are available in memory to manipulate & utilize. These classes are invoked, and have a series of methods which you can use to interact with them.
In the sense of Rails (I'm not super experienced with Ruby), these methods come in two forms:
Class methods (def self.x)
Instance methods (def x)
These work to give you the ability to call these methods to manipulate your object as you see fit. For example, if you were creating a game with aliens, you could have #alien.is_visible?
This is the underline structure which Ruby gives us.
Rails
Because Rails is built on top of Ruby (Ruby being the language, Rails being the framework), your answer lies in how Rails manipulates the objects you create.
To understand it properly, you need to know that Rails is an MVC (Model View Controller) framework:
As you can see, Rails builds your objects in your Models. This is why you can call #article = Article.find x.
Thus, when you ask how redirect_to #article works, you need to remember that Rails builds its objects in a certain way (IE with a series of base methods - inherited from ActiveRecord::Base).
These methods give each model object a certain structure, which Rails can use within its helpers. This is the case with redirect_to:
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
Rails expects its objects to conform to a certain structure, backed up by CRUD / RESTful routes:
Thus, if you have #article (which is to conform with Rails' model structure, and you have the routes set up as below):
#config/routes.rb
resources :articles
Rails will be able to take your instance of the Article object, and use it to populate the redirect_to route.
--
This is how Rails populates many of its helper methods, including form_for and render. It's intelligent guessing, based on the conventions which govern the framework.
It's why Rails programmers are often eager to point out how "Railsy" their code is.

how does redirect_to method work in ruby

Here is my update function, the moment something is updated it perfectly goes to http://localhost:3000/articles/2
def update
#article = Article.find(params[:id])
if #article.update(article_params)
redirect_to #article
else
render 'edit'
end
end
How does the redirect_to #article work and show the artiles/2/ page?
Below is my routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
new_article GET /articles/new(.:format) articles#new
edit_article GET /articles/:id/edit(.:format) articles#edit
article GET /articles/:id(.:format) articles#show
PATCH /articles/:id(.:format) articles#update
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
welcome_index GET /welcome/index(.:format) welcome#index
root GET / welcome#index
The magic method is polymorphic_url.
When you call link_to, redirect_to etc. and you don't pass a string or a hash of routing options Rails will end up calling this method.
Active Record (and Active Model compliant objects) have a model_name class method that returns an ActiveMethod::Name object which has a bunch of methods for obtaining the 'correct' name for a model class for various uses.
One of those (singular_route_key) says what name is used for the model in routes. This also takes care of handling things like namespacing if you're in an engine.
In the simplest case, this boils down to
#article.class.model_name.singular_route_key #=> 'article'
Rails then generates the method name from that by joining this with any prefix options and the correct suffix ('url', 'path', etc.), and ends up with a method name like article_url. It then calls that method (generated for you based on your routes file), passing the article, which returns the url for this article.
redirect_to method checks the class of instance provided and gives you the /articles based on the model class, and path provided in routes, and then it check if the object has id or not. If the object persist in database then it checkes for to_params method on that object and add to /articles/{result of to_params}. And if the object does not persist in database then it gives you articles/new.
See the line with the name article -
article GET /articles/:id(.:format) articles#show
So when you give something like redirect_to #article, it checks for the id within the current view to get the id and displays that corresponding view of record.
Scenario:
1) You are in article index with many records being shown.
2) You clicked the second record whose link is defined with redirect_to #article in a controller.
3) The controller is smart enough to first route through to identify the route and then picks the correct path for it.
4) The View shows the view based on the route forwarded by the controller.
In your controller you have
#article = Article.find(params[:id])
redirect_to #article
So #article contains the array of ids of Article.And also you have /articles/:id(.:format) articles#show
So whenever a particular Article is updated it redeircts to the corresponding article based on the :id which #article contains.
In your case,you are updated an article with id = 2, so the route is /artcles/2

Editing a comment that belongs to a post

So I have created a blog that users can create posts and others can comment on that post.
I want users to be able to edit the comments.
What I did so far is the following:
My routes are like this:
resources :posts do
resources :comments
end
and when I rake route I get this:
new_post_comment GET /posts/:post_id/comments/new(.:format) comments#new
edit_post_comment GET /posts/:post_id/comments/:id/edit(.:format) comments#edit
post_comment GET /posts/:post_id/comments/:id(.:format) comments#show
on my view file I use this which is obviously wrong
<%= link_to 'Edit Comment', edit_post_comment_path(#comment) %>
and my controller is like that:
def edit
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
end
def update
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
if #comment.update(comments_params)
redirect_to post_path(#post)
else
render 'edit'
end
end
When I try to visit the page (of the post) I get this error:
No route matches {:action=>"edit", :controller=>"comments", :id=>nil, :post_id=>nil, :format=>nil} missing required keys: [:post_id, :id]
That's the first time I work on nested routes. I read some tutorials but had nothing really helped me to fix this. I am not sure how can I pass these keys...
(if you need any other info please let me know and I will provide them)
Any help is greatly appreciated!
You need to pass both the #post and #comment instance.
edit_post_comment_path(#post, #comment)
Generally speaking, you need to pass every instance matching the resource the route is part of. So, in a single route like
edit_post_path(#post)
you pass only the post. In a post/comment nested route you pass both. In a post/comment/whatever you will need to pass the three resources, etc.

Resources