Rails routing: how to mix "GET" and "PUT" - ruby-on-rails

Not sure how to frame this question (I'm still wrapping my head around Rails).
Let's try this:
Say I wanted to implement the user side of Ryan Bates' excellent railscast on nested models. (He shows how to implement a survey where you can add and remove questions and answers dynamically). I want the user's side of this: to be able to answer questions and, not in the tutorial, be able to add comments.
It seems to me that you have to implement a view that shows the questions and answers, allow selection of the answers, and the input of comments. So there would need to be a way to show the information, but also update the model on input, right?
I know I'm not explaining this very well. I hope you understand what I'm getting at.
Is it just a question of setting up the right routes? Or is there some controller mojo that needs to happen?

The typical way to do this in Rails uses "resourceful" routing, which more or less naturally maps the standard CRUD actions to methods in your controller, using the appropriate HTTP verbs.
In the routes file (config/routes.rb), you set up the desired resources and actions. For example:
map.resources :questions, :has_many => :answers
Would set up a routing scheme for a question with multiple answers, mapping to the actions according to Rails' conventions:
index: GET /questions/1/answers # list of answers for question id=1
show: GET /questions/1/answers/2 # display answer 2
new: GET /questions/1/answers/new # render form for new answer for question id=1
create: POST /questions/1/answers # create a new answer for question id=1
edit: GET /questions/1/answers/2/edit # render form for answer for question id=1
update: PUT /questions/1/answers/2 # update answer 2
destroy: DELETE /questions/1/answers/2 # delete answer 2
In the controller you create methods mapping to these standard actions. You can also create your own methods and actions for things that don't fall into the CRUD paradigm (like a search for an AJAXified autocomplete field, for example)
Hope that answers some of your question.

You need a "question" resource, "answer" resource and "comment" resource. You also need to implement:
POST for "answer (which is "create" method in controller) to answer the question
POST for "comment" (which is "create" method in controller) to create comments
PUT for the "question" (which is "update" in controller) to "pick" answers, which is effectively changing the state of the "question" resource

In ASP.NET MVC there are two controller methods with the same name but different parameter signatures. One method is decorated with an attribute that tells it to service GETs, the other is decorated with an attribute that tells it to service POSTs. The GET method displays the view, the POST method updates the model.
I assume that it works in a similar fashion in Rails.

Related

Rails 4 new form defaults from params

I am using form_for in the _form.html.erb view in order to create my form for both the edit and new actions, as per a standard scaffold.
I have a model Owner which has_many pets.
I would like to put an html link on my views/owners/show.html.erb to create a new pet for said owner. This link will point to the new action of pets_controller.rb which when accessed will render the view in pets/new.html.erb
What I want to happen is for the the owner_id to be passed with the link in the url to the new action of pets_controller.rb and then be used as the default for a collection_select in pets/new.html.erb
So I have a link to create a new pet but because that link was on a specific owner page, I want the form to create a new pet to have that owner already set, so the user does not have to select from the list.
This has to be done without changing the behaviour of the edit action/view in pets.
I know that I can pass GET arguments then access them in the controller via params, then create variables in the action which are passed to the view. I can then manually check for a default and set it in the view. I do not need assistance in coding if this is the only solution.
Is there is a better way to do this? A format with which I can pass the params such that the view will just pick them up? Without manually editing my controllers and views?
While my personal inclination would be to do as you said and pass a parameter in the link helper and then access the params array in the pets view, you may find that this is the perfect opportunity to explore Nested Resources. Essentially, you could declare owners/:owner_id/pets/:pet_id route with:
resources :owners do
resources :pets
end
You could then link to this route, and reference :owner_id without having to append the query string to the URI (making somewhat cleaner for reuse).
This is likely more work for you, but also potentially more extensible (and certainly more inline with the Rails way of doing things).
REVISION
Added the following regarding link helpers to the comments, but wanted to reflect it in the answer as well.
To show a pet should be:
<%= link_to owner_pet_path( owner_variable, pet_variable) %>
To view pets' index index should be:
<%= link_to owner_pet_path( owner_variable ) %>
The answer given to this question is fantastic.
As #ConnorCMcKee suggests it would be wise to consider nesting your routes. However, if you are a beginner as myself I found that it helped my learning to simply nest my second controller into the first (i.e. nest PetsController into OwnersController) as a first step. Then afterwards I would continue with the routes.
The method would be something like:
1./ In owners/index.html.erb:
Links to PetsController index action
The key to make this work is to send the :owner_id in your link parameters. Then that Pets index action will have access to that :owner_id and know which :owner_id called it.
2./ In PetsController you would then be able to find that Owner using that id, like so:
params[:owner_id]
Then your actions can start to take advantage of knowing what Owner called them. Remember though that all your redirects inside your PetsController need to preserve params[:owner_id]. That is because once you are inside that nested structure you have to maintain it and stay inside it and always know which :owner_id you are working with.

very beginner RoR routing (new dynamic URL)

I have worked through the "getting started" Rails tutorial and am trying to understand routing better so that I can expand the sample project. (I've read this routing guide but didn't fully understand it - I think what I'm trying to do is simpler than what's covered there.)
Taking the sample blog project as a base, I am trying to add the ability to view all entries by a given author. I've modified the original project so that "author" is a field in the database and is accessible in the index (along with "title" and "text") (I can call #book.author), and I've figured out a (probably very hacky and incorrect) way of listing all authors and subjects on a separate page, but I have been unable to set up a way of filtering the articles by author.
In the same way that the "show" link in the sample project passes the article id to the controller and gives a view of the article with that id, I want to be able to have a similar link for each author which would pass the contents of the :author field to the controller to get all articles that share that author.
(I went down a really tangled rabbit hole manually setting up a separate controller for "authors" and trying to create a gets "authors/:author" line in the routes.rb file. Creating the controller and a view where I could list the authors themselves worked, but any attempt to set up a route with :author (or :id) as a parameter (so that each author would have a page listing his articles) didn't work - not sure why given that it looked, to me, the same as what was shown in the routing guide (above). But I'm pretty sure this is not the way to do this, so I'm starting over and trying to do it right.)
(I hope that this question isn't too general. I'm assuming that the answer here is very simple - I just haven't been able to figure it out from the obvious documentation or tutorials I've seen.)
It's usually a simple nested resources...
resources :authors do
resources :articles
end
This gives you a route authors/:author_id/articles (named as author_articles_path(#author))
In your articles index controller you would do..
def index
#author = Author.find(param[:author_id])
#articles = #author.articles
end
hope this helps.

RESTful Quiz Representation

I'm building a quiz. A user can pick a subject and answer 5 questions. After each question they view the answer. I'm trying to stick to a strict RESTful representation of this workflow but cant really settle on a url scheme. For example:
User Joe picks the subject sport and is ready to see the first question. The url is
user/joe/subject/sport/question/1
When he submits his answer 'B' ( its a multiple choice quiz) , Joe is creating a new answer, we POST to
user/joe/subject/sport/question/1/answer/B
before viewing the correct answer at
user/joe/subject/sport/answer/1
we then view the next question at
user/joe/subject/sport/question/2
This is all obviously too complicated. How would you approach this problem in a RESTful manner?
Start with this presentation. It's is a great resource for RESTful API design. With that in mind, here are some starting suggestions:
RESTful URLs have an implicit hierarchy. Take the user information out of the URL. It belongs in the HTTP headers.
/subject/sport/question/1
/subject/sport/question/1/answer/B
/subject/sport/answer/1
/subject/sport/question/2
I don't see any useful information added by the subject part. The subject is identified by (in your example) sport.
/sport/question/1
/sport/question/1/answer/B
/sport/answer/1
/sport/question/2
Categories should be plural.
/sports/questions/1
/sports/questions/1/answers/B
/sports/answers/1
/sports/questions/2
When you POST to answer a question, you're not POSTing to add a new answer resource (that is, defining a new possible answer). Aren't you are POSTing to an existing resource?
You haven't mentioned anything about HATEOAS. If you're really going to implement REST, you should be doing things like providing "next" links in the hypermedia.
For me the basic idea in a REST service is the "resource". First you need to identify your resources.
So there is a user and she starts a new quiz.
POST /user/joe/quiz.
It returns: Location: /user/joe/quiz/1
Then the user selects a sports question, so you update your quiz to include a random (server selected) question.
POST /user/joe/quiz/1 -> Subject:sport
It returns: Location: /user/joe/quiz/1/question/1
The user answers:
PUT /user/joe/quiz/1/question/1 -> Answer B
Now rinse and repeat.
The resources we've got:
Users
Quiz for a user
Questions in a Quiz (The question is updated with an answer)
I would remove /user/joe from the routes entirely. You can get the current_user using Devise, Authlogic, or some other authentication framework.
Otherwise this looks okay to me, as it's only two nests which is readable enough. So you'd have:
GET subjects/sports/questions/1
POST subjects/sports/questions/1 # pass along params with {:answer => 'B'}
GET subjects/sports/answers/1

How to stick with REST?

I'm basically putting together an app that does a super simple question => answer test in an attempt to learn more about rails.
ie:
Question: "What's your dog's name?"
Answer: "Doggington"
I have a Question model with two simple attributes:
question:string
correct_answer:string
My struggle here is how do i apply REST principals to this -- specifically, when i am checking a user's input(answer) to see if he got the question right or not.
I'm not sure if i should do something like modify the "show" method (or any other action) to accept values for answer posted to it... and it SEEMS like I should create a new method in my questions_controller called "verify_answer" or something a long those lines.
This breaks REST.
What do you think?
thanks!
AnswersController#create should accept the answers. Whether or not this controller actually has a related Answer model is irrelevant. One action should never perform two actions. For instance, if your QuestionsController#show both displays the question, and accepts a :put or :post with the answer to the question then you are breaking basic rails design principals.
Note that your routes file might very well look like this:
resources :questions do
resource :answer
end
Which will expose the /questions/8/answer route that you can :post to, which will go to AnswersController#create.
Off the top of my head I'm forgetting the exact name of the helper url method you can use to generate the url. Something like question_answer_path(#my_question).
This routing file is for rails3, which I assume is what you're using since there's no reason to use anything else if you're starting a new app in my opinion :p
If you do have an Answer model (maybe you want to store users' answers and look at them later or aggregate them and come up with statistics and such) then you should change the router to use resources :answer instead of the singular version.
For more information about routing and some RESTful tips you should visit the Ruby on Rails guide for routing found here: http://guides.rubyonrails.org/routing.html
I'm on an editing spree! There are times when you might need to add an additional method to your Questions controller that isn't strictly REST. This isn't necessarily considered bad practice but just make sure you look at your decisions and find out if you aren't actually just hiding the existence of another resource. I don't consider this to be one of those times as explained above :)

In Rails, I'm trying to pass an attribute to a Model's "new" method, then to the "create method". What's the most efficient way to do this?

In my Rails application I have a Forum with many Topics. From the Forum's, "show" page I have a list of Topics with a link to create a new Topic. My goal is to make sure that when creating the new Topic I have the ID of the Forum when I get to the Topic's "create" method. Right now I'm thinking I'd have to create a hidden field on the Topic's "new" page and set the hidden field to the Forum's ID. I was wondering if there is a better, cleaner way.
Thank you for looking!
Nested resources are the way to go for this, so you nest the Topic resource within the Forum resource. Take a look at these tutorials:
Nested Resources Rails Guide
Nested Resources Railscast

Resources