rails, general understanding of URIs and controllers - ruby-on-rails

i have a Message model and controller. and in it, i have the standard functions such as index, show etc.
when i go to "localhost:3000/messages", my index.html.erb gets displayed, which in turn renders my partial _messages.html.erb.
and in there, i have an anchor href
Received Messages
however, i encounter 2 issues.
received_messages isn't being appending the URL, which i thought it would normally do unless i added a / in the beginning. how do i make it append?
and also.. if i manually type in "localhost:3000/messages/received_messages", i keep getting redirected to the messages#show function. why is that in general?
i have a received_messages.html.erb and even in my routes, i have
match '/received_messages', to: 'messages#received_messages'
which i have the function #received_messages in my controller as well.
why does it keep getting redirected to messages#show
thanks a lot = )

You asked:
if i manually type in "localhost:3000/messages/received_messages", i
keep getting redirected to the messages#show function. why is that in
general?
In general, you have the idea of "messages"... and to
view the collection of messages, you'll go to "/messages"
To view a specific message (with id 56) , you'll go to "/messages/56"
To edit, "/messages/56/edit"
To create a new one: "/messages/new"
So, when you go to /messages/received_messages, the system, is attempting to show the message with id "received_messages"
If you want an action on the Collection of messages, you can setup your routes like:
resources :messages do
collection do
get :received
end
end
This will match up "/messages/received", and run the MessagesController#received action, and render the app/views/messages/received.html.erb file.

Related

Rails 4 - First argument in form cannot contain nil or be empty

My problem is very similar to Rails edit.html.erb ArgumentError
After reading through that page (and about 7 others with the same problem) multiple times and spending several hours trying to fix this, I haven't been able to. The first paragraph worth of error messages on that linked page is identical to the one I'm receiving every time I refresh a user's page.
I'm an absolute novice who's following along with Michael Hartl's Ruby on Rails tutorial and am trying to make one small change. Instead of rendering the form for new microposts on the home page, I'm trying to do so on the profile page of users who are logged in. After logging into user #1 and viewing that user's profile page at [URL]/users/1, I receive the aforementioned error message. When viewing that user's profile page while logged out, I don't receive any error messages, and the micropost form is not displayed, as intended. I apologize for asking such a stupid question when I'm sure the answer is obvious to everyone who reads this, except for me. But what am I doing wrong?
Error message details:
ArgumentError in UsersController#show
First argument in form cannot contain nil or be empty
app/views/shared/_micropost_form.html.erb:1:in _app_views_shared__micropost_form_html_erb'
app/views/users/show.html.erb:9:in _app_views_users_show_html_erb'
Parameters:
{"id"=>"1"}
config/routes.rb:
resources :microposts, only: [:create, :destroy]
views/shared/_micropost_form.html.erb: http://imgur.com/a/84nPO (Picture #1)
One of many iterations of views/users/show.html.erb that I've tried to use. I've tried so many different things that I believe it doesn't matter which attempt I screenshot; the error message is always the same: (Picture #2 in the previous album; I can only paste two links in this question.)
"show" action under controllers/users_controller.rb, if it's relevant: (Picture #3 in the previous album; I can only paste two links in this question.)
You're getting that error because #micropost is nil in your partial as it's not actually defined anywhere.
By convention, a local variable with the same name of the partial is created for each item in the partial collection. So, in this case the variable name locally scoped to your collection partial is micropost:
in _micropost.html.erb
<%= form_for micropost do |F| %>
Note that your partial will actually need to be named _micropost.html.erb
If you look at the action that originally had your form, you'll see somewhere in that controller that a variable is being set to feed your form with the needed instance variable. If you want to render the form on a different view, you need to make sure that the action that is now responsible for it has it.. (and you can take it out of the original one if you haven't already.

Rails routing issue, can't figure out the link path

Let me fair from the outset, and tell you that I've 'solved' the problem I'm describing. But a solution that you don't understand is not really a solution, now is it?
I have a resource, Newsbites. I have an index page for Newsbites. All my CRUD actions work fine.
I created a separate index (frontindex.html.erb) that acts as the front page of my website to show the newest Newsbites. The formatting is different from my normal index so readers get a larger photo, more of the text of the article(more ads too:).
In my routing table, I have the following statements:
resources :newsbites
get 'newsbites/frontindex'
root 'newsbites#frontindex'
Rake routes show the following:
newsbites_frontindex GET /newsbites/frontindex(.:format) newsbites#frontindex
If I load my website from the root (localhost:3000), it works great. There is a separate menu page that is rendered at the top, and it loads fine. I can click on all links, except the 'Home' link, and they work fine.
The 'Home' link is:
<%= link_to 'Home', newsbites_frontindex_path %>
When I click on the linked, I get the following error:
Couldn't find Newsbite with 'id'=frontindex
The error points to the 'show' action of my Newbites controller. Here are the frontindex and show def from the controller. They appear exactly as I'm posting them:
def frontindex
#newsbites = Newsbite.all
end
def show
#newsbite = Newsbite.find(params[:id])
end
I don't get it. Why is the show action being called by newbites_frontindex_path when there is both a def and views that match? Now, I can get around this by simply pointing home to root_path. But that doesn't help me understand. What if this was not the root of the site?
Any help would be greatly appreciated.
Actually I'm very surprised your code worked at all. A route must define two things
Some sort of regex against which the URL of the user is matched (newsbites/frontindex is different than newsbites/backindex)
What do you want to do for a given URL ? You want to point to a controller action
Rails won't usually "guess" what that action is. Or maybe, he was still able to "guess" that you wanted to use the newsbites controller, but it didn't guess the action right this time :(.
You should declare the root like this, which is what you did
root 'controller#action'
For the rest, there are two ways you can declare it. I prefer the second one
resources :newsbites
get 'newsbites/frontindex', to: 'newsbites#frontindex'
resources :newsbites do
# stuff added here will have the context of the `newsbites` controller already
get 'frontindex', on: :collection # the name of the action is inferred to be `frontindex`
end
The on: :collection, means that 'frontindex' is an action that concerns ALL the newsbites, so the URL generated will be newsbites/frontindex.
On the other hand get 'custom_action', on: :member, means that the custom_action targets a specific item, the URL generated would look like newsbites/:id/custom_action
EDIT : Rails also generate path_helpers based on the route declaration
get 'test', to: 'newsbites#frontindex'
get 'test/something', to: 'newsbites#frontindex'
resources :newsbites do
get 'frontindex', on: :collection
get 'custom_action', on: :member
Will generate path helpers
test_path
test_something_path
# CRUD helpers : new/edit/show/delete, etc. helpers
frontindex_newsbites_path
custom_actions_newsbite_path(ID) # without s !
You can always override this by adding an as: option
get 'custom_action', on: :member, as: 'something_cool'
# => something_cool_newsbites_path
Rails routes thinks that frontindex is an id. That's what the error message says. So it goes to GET newsbite/:id which maps to show.
You need to find a way let Rails routes know that frontindex is not an id.
On a side note: The order in which you define routes matters. The first one matched will be used. If you have GET newsbite/:id and GET newsbite/frontindex then the one that appears first will be matched. In your case this is the first one.
Maybe try to change the order.

Rails: Routes, Controllers, Views, Oh My(exclamation)

I'm failing to understand the correlation between routes, controllers, and views (and how they connect to each other).
So, I've got my controller with the index,show,new,create,destroy methods. And the corresponding
GET /entries(.:format) entries#index
POST /entries(.:format) entries#create
GET /entries/new(.:format) entries#new
GET /entries/:id/edit(.:format) entries#edit
GET /entries/:id(.:format) entries#show
PUT /entries/:id(.:format) entries#update
DELETE /entries/:id(.:format) entries#destroy
How come if I add a new method vote_up or vote_down, for example, and the views with matching action names, it doesn't work.
1) What is the proper way to add new actions and connect them to views?
2) Bonus What is the proper way to make these methods ajax-compatible (render a partial with ajax)? What happens if the user doesn't have js enabled?
I may expand/evolve this question based on the answers I get.
I'm tired of googling things like custom action route rails and the like to hodge-podge my apps. It's draining and poor form and I'm finally getting to the level to comprehend the lingo--I've been self taught 100%...so please try to be understanding if you can for a young padawan.
Here's how to think of it, from the beginning:
1) The one and only thing your app does is respond to HTTP REQUESTS.
The most typical kinds of requests are:
GET - the user types something into the URL bar of their browser and hits enter.
POST - the user submits a form.
There are also other kinds of HTTP requests, most importantly PUT, PATCH and DELETE. Rails follows the REST pattern, which means it assigns specific meanings to these HTTP verbs.
2) When any request comes into your app, it has to be routed to a Controller Action.
Your routes.rb file is a set of instructions for the Rails Router (ActionDispatch) that tells the router where to send requests. The "standard" rails resource is given as a shortcut, like this:
resources :things
This means the following:
GET /things => things#index
GET /things/:id => things#show
GET /things/new => things#new
GET /things/edit/:id => things#edit
POST /things => things#create
PUT /things/:id => things#update
DELETE /things/:id => things#destroy
These are considered the standard RESTful actions - nothing else is set by your resources :things declaration. So, if you want the controller to perform other non-standard actions, you have to add them manually.
If you want to perform an action on a specific record, the best way is to use:
resources :things do
member do
get 'vote_up'
end
end
This tells the router that if someone makes a GET request to /things/123/vote_up that it should trigger the ThingsController vote_up action.
All of this is spelled out in great detail in the Rails Guide, you should read the whole thing.
3) Your controller's job is to send a response to the request.
Normally this means something like loading a record from the database and rendering the view for that record.
Each controller action ends by sending the response back to the incoming request. This response can be either a render call - which means send back some data in some format - or a redirect call - which basically makes a new request for you and therefore you get the response of that other request.
In Rails a redirect is effectively sending the request to a different controller action.
A Render call sends data as a response to the request.
When you call render :new, this is a shortcut to render :template => :new, which loads the app/views/things/new.html.erb (or whatever) template, sends it the data from the controller (normally your instance variables) and evaluates this using the template language (erb, haml, etc.) This results in a big string of HTML, which the controller then delivers to the browser.
Want to see what this for yourself? Try ending a controller with render :text => 'Hello World', or even:
render :inline => '<!DOCTYPE html><head><title>Inline Wow!</title></head><body>Mind blown.</body></html>'
See what happens.
When responding (rendering) you can send "normal" HTML templates, with a whole page worth of information in it (head, body, etc.), or a partial that is used by Ajax. You can also send raw data such as JSON or XML. It's all actually just text, and depending on the content of that text (and the HTTP headers that come with it) the browser, script, or client application handles it accordingly.
Again, see the Rails Guide.
4) When the request is made by a browser you probably want to send back HTML. If the request is made by Ajax you probably want to send back JSON.
In the case of a custom action like vote_up you might not want to show a template at all, but just redirect. So, you might have something like this:
ThingsController < ApplicationController
def vote_up
#thing = Thing.find(params[:id])
#thing.vote_up
redirect_to #thing
end
end
Now, one of the benefits of the router is it will give you URL helpers. If you've created the route and action as shown before, on your "show thing" page you could have a URL like this:
link_to 'Vote up this thing!', vote_up_thing_path(#thing)
That would create a link to things/123/vote_up, and if someone clicked on it it would run the code in the vote_up action on the ThingsController, and then redirect back to the show thing view.
5) Your templates send messages to the controllers using links and forms. Links make GET requests, forms make POST requests.
If you want to start having AJAX requests, that's fine. In that case, you just need to make the request in Javascript, and handle the response. So, for instance, you could put something like this in your template:
= link_to 'Vote up this thing', vote_up_thing_path(#thing), :id => 'vote-up-button'
Then in Javascript (with jQuery) you could have a function like this:
$(function(){
$('a#vote-up-button').click( function(event){
event.preventDefault();
$.ajax({
url: this.attr('href'),
type: 'GET',
success: function(){...},
error: function(){...}
});
});
});
In this case the jQuery Ajax method is just making a get request, and then running a callback function based on the response it got.
6) The structure of your controller/routes does not affect what kind of requests you can make, only what action will respond to what HTTP method on what URL.
What you do INSIDE your controller action determines whether you are ready to respond to javascript or html requests etc.
While rails is certainly able to handle multiple request formats in a single controller action, using the respond_to block, as a matter of practicality I find things work much more smoothly when you choose to have routes only respond to one format or another.
IE: I would make your normal page load requests (index, show, new, edit) just HTML requests, and then I would make any additional AJAX actions you want to add be Javascript only -- ie. they respond with JSON instead of HTML. You don't have to do this, of course, but your life will be easier if you do.
I hope this gives you a clearer sense of what is happening in your app. Welcome to Rails, you're joining a great community!
Welcome to the ROR world. :)
Routes, controllers, and views work together to translate a HTTP request into a response of some kind (be it HTML, JSON, or XML). Each attacks a different piece of the problem.
Starting from the end, views are the templates in the rails world and they typically are ERB. ERB is just one templating system, others can be used as well, like haml. Their job is to take some data given to them by the controller and produce formatted output, again typically HTML, JSON, or XML.
But how do you know which view to render for a particular request? How do you get data into your view so that it can do all the fancy dynamic stuff you need? This is where controllers come in. Controllers are ruby classes with the job of examining the parsed HTTP request and any related parameters, fetching data from the database (or wherever), and then passing that data to a view. A controller will typically have several different methods, each corresponding to a different task (e.g. create, show, update, etc).
Lastly, Routes are a DSL for parsing HTTP requests and dispatching a parsed HTTP request to a particular controller method. They are the glue that Rails uses to route URLs to methods, hence the name, routes.
Regarding your specific questions:
1) To create a new action, you have to add a route, a controller method, and a view. So for instance if you wanted to get a count of your entries, you could add a route like:
get '/entries/count' => "entries#count"
This tells ROR to call the count method in the entries controller when that URL is received. Your controller method in this case would be something simple like:
def count
#cnt = Entries.count
end
Then last, you'd make a view in app/views/entries/count.html.erb that had something like:
<p>Count: <%= #cnt %></p>
2) Ajax compatible code really just a way of asking "what if the requested format for this request is JSON?". For this, you'll want to use respond_to method. In a way, respond_to is a formal way of specifying a different view to handle the formatting of the request. To continue with the example above, you could do:
def count
#cnt = Entries.count
respond_to do |fmt|
fmt.html # This just renders the default template
fmt.json { render json: { count: #cnt } }
end
end
N.b. - All code above freehanded. :)
If you have the RESTful controller:
resources :entries do
collection do
get :vote_down
get :vote_up
end
end
this will generate 2 additional routes:
GET /entries/:id/vote_up(.:format) entries#vote_up
GET /entries/:id/vote_down(.:format) entries#vote_down
You can easily change HTTP method to GET, POST, PUT and DELETE.
Also, check "Adding More RESTful Actions" in the routes documentation.

how to hit a put route

I have an additional method in one of my otherwise restfull controllers called 'importdata'. As I'm actually changing the data (importing csv in the database), I understood that it should be a put route instead of get.
Initially I had
resource data_set do
put 'importdata', on: :method
end
what I also tried is:
put 'data_sets/:id/importdata', "data_sets#importdata'
rake routes shows the route I want in both cases.
What I did when I had the method on (1st example) route in the controller was
redirect_to import_data_sets_path id: dataset.id
And with the second example:
redirect_to controller: "data_sets", action: "importdata", id: dataset.id
The message I get in both cases is:
No route matches [GET] "/data_sets/28/importdata"
Which is correct, because it's a put route. The only way I get this to work is to change the put for a get:
get 'data_sets/:id/importdata', "data_sets#importdata'
How can I get that to work on a put route? Should it be a put route in the first place?
Thanks for your time.
Simply put you can't 'upgrade' a HTTP request issued by an user. redirects only work over GET. If the user is changing something do it through a form and make sure it's a PUT request as you're modifying an existing resource.
If the PUT is conditional there's several options, either figure out how to solve this in the UI, use an HTTP client to issue the PUT(which doesn't make sense for an local call) or extract the editing of the resource in some other kind of class and use it in the controller.
However, even if the edit is optional it makes more sense to let the user fire a PUT in the first place.
Hope that helps.

How to deal with routes that don't respond to GET in Rails?

I have a singleton route that looks like this:
resource :avatar, :only => [:edit, :update]
When a user edits the avatar, they go to /avatar/edit to view the form, when they save they are sent with a put request to /avatar and then redirected back to /avatar/edit. What should happen when we get a GET request to /avatar?
At the moment the route is not recognized and a generic 404 is returned. But this happens way more often than it should, so I feel we should do something about it. I'm tempted to add a:
get "/avatar" => redirection("/avatar/edit")
Any reason not to do that? any dangers in doing it?
I'm not sure I have all the reasons why this error happen, but I know some:
There's an error so instead of redirecting, the user is just shown the edit form again in the put route, but then, instead of re-submitting, the user re-uses the url (pressing enter in the url field, copying and pasting, bookmarking, etc).
The user at that point decides to change locale (happens often because the error message is the first time the person actually needs to understand the web site, so they decide to switch language to one that they understand) which of course generates a get in the same URL.
I now having a plain redirection wouldn't be the best in the second case, because the error is lost, but at least it's better than a 404.
Part of the confusion with this route is that it is plural and yet refers to a singleton. Any chance that you could use just "avatar"? If I went to a route "/avatars" I would expect a list of avatars. If it is singular, then I would expect "/avatar" to view the avatar. I also think this might be useful in a route like "/avatar.jpg" that would respond with the avatar image. However, if your specs define no use for a show and always to edit try:
get "/avatars" => "avatars#edit"
or the singular
get "/avatar" => "avatars#edit"

Resources