Non-CRUD Controller Actions - ruby-on-rails

This might seem like a n00b question, but I am trying to break some of my bad practice that I may have adopted using MVC, so I hope you can help me out
So, imagine I want to do something like "Upload CSV And Parse It", it doesn't seem obvious to me to fit it into the CRUD pattern... I am not interacting with the DB, so i don't need add or update or delete, but I still want to be able to use the action in a meaningful way from different views. Thus, it is "ok" to just an action called "UploadCSV" and have it be accessible via a URL such as "/data/uploadcsv"
Your thoughts are much appreciated!
Tom

It sounds like you are talking about RESTful ideas (having actions called index, create, new, edit, update, destroy, show).
In MVC you can call an action largely whatever you want (so yes, you can call it uploadcsv if you want). If you want it fit RESTful principles you might want to think about what the action is doing (for example is a data upload essentially a create or an update function) and name it using one of the RESTful action names.

I believe I have the same point of view as you.
In my projects I try to be as restful as possible whenever I can. However as you said sometimes a special case just does not 'fit'
After all it is also a question of 'feeling'
If you provide a csv import function, I see it as perfectly correct to not create a full REST implementation for CSV.
Let's imagine in your application you have clients. And you wnat to give the option for clients to import data using csv. You can add a route for this action using:
map.resources :clients, :member => { :uploadcsv => :get }
The route is properly declared, Your 'clients' resource is completely restful and you have an additional action properly declared to manage data importation.
The only warning I have is: don't use a route like this one "/data/uploadcsv". From my point of view It lacks clarity. I like to be able to understand what my application is going to do just be looking at the url. And '/data' is too vague for me :)

The persistence of the resource is not crucial here. I suppose that what you are doing here is this - creating some kind of resource (although not persistent) out of the csv provided. The thing here is to think about what this csv file represents. What's inside? Is it something that will become a collection of resources in your system, or is it a representation of only one object in your system? If you think about it it has to be something concrete. Can you be more specific about your problem domain?

Related

Search action in ReSTful rails model

So, I'm quite new to Rails and still working my way through the principles. I suppose like most people, I have started creating that basic CRUD. Okay. Done.
Now I want a new action: search. As it turns out, it is not one of the 7 rest sacred (!) actions (if got it right). While I know I could implement new custom actions and resource it and everything, I read in a few places to try my best to stick to the standard ones as long as possible. Okay. What would be the correct way?
Again a few sources like this guy suggest thinking of my scenarios in therms of nouns, case in which seems I'd need a "search" controller...? It just doesn't convince me that I'd have to create a whole class whereas I'd normally do def search just to keep it ResTful.
What did I get wrong? What would be the common solution here?
thanks.
REST is a concept, not a religion :-). But the core verbs are GET/POST/PUT/DELETE which map to their associated HTTP verbs. What's in the URL is typically a reflection of this, and (this is more the Rails philosophy) following convention can make everything much easier. The URLs you get with generic rails (e.g. scaffold) are not particularly ideal in several ways, but they work, and you can change them.
So, yeah, for search (assuming it's starting simple, e.g. finding records in a single model, say Product) then you could do a GET with a query string like this
def search
#results = Product.where("name ILIKE ?", params[:query])
...
end
Which would result in a URL like /product/search?query="foo" -- nothin' wrong with that.
It depends if your search is against ONE resource or many resources. For example if you have a ProductsController and you want to implement a search feature only for your products, you could create a collection action called "search" (the url would be /products/search)
If your search is for many resources, I'd create a SearchesController with a singleton resource :search in my routes file.
Then again, when you implement search functionality in your application, don't put all the logic in your controller but create models classes to handle your search. You can even create an abstract class to map to your search form and thus avoid using '*_tag' fields to create your search form.
See : https://github.com/slainer68/basic_active_model
If you want to adhere to REST (which is a guideline really, it has pros and cons), then the slideshare you link to is recommending the right way to do things.
So, for example if you have a comments_controller, and you want to be able to search comments, you could create a comments_search_controller. The search form would be at comments_search_controller#new, which would POST to comments_search_controller#create.
Yes, you are creating another class doing it this way, but that's not much different than creating another action in the comments_controller, and it does keep things consistent and separated. You wouldn't need a new CommentSearch model or anything, just that controller, which asks your Comment model for the relevant search results.

Basic Rails 3 Routing Question

I am trying to make some clean URLs in a Rails3 application I am working on... but I am having a hard time understanding how to (or if I even should) customise my routes to make this work.
Here is the example:
I have a list of Stores. Each store is in a category (health, sports etc). Each store has a location.
I have 2 ways I'd like to present the data. One display is a list of all the stores in a directory type structure, the other is on a map.
Ideally I'd like my URLs to work something like this:
/stores/health/map (or /stores/map/health) to show just the health stores on a map (where essentially the map parameter is effecting which view is displayed, but still using the Index controller... which using a collection in my route doesn't seem to suit)
The other URL I'd like is /stores/sports/ to show just the sports stores in a directory view (the default) for example...
I am not entirely clear how I can manipulate the routes to handle this...
Here is my current Route which isn't really doing it for me:
resources :stores do
collection do
get 'map'
end
end
On top of that, I'd like to be able to add filters without using ?query=params... so:
/stores/sports/hockey , would essentially filter out only hockey stores...
I have no issues doing this with ?query, it's just putting my params into a nicer URL that I'm trying to achieve.
The documentation does not seem to outline what I am trying to do, so Im assuming what Im trying to do is wrong.
Is this breaking REST? Am I looking at it all backwards?
Thanks for your help, JD
You might be overthinking this. :-)
If you want to route HTTP Get of 'stores/health/map' to the StoresController with an action name of, say, health_map, what you need to do is:
get 'stores/health/map' => 'stores#health_map'
Anything that is a clean URL and doesn't modify data and uses HTTP GET is RESTful. (And that is coming from a co-author of a book on REST). It is when you wish to modify data that you need to be more careful on how you use methods.
To do filtering, try something like:
get '/stores/sports/:filter' => 'stores#sports'
The value of the filter will come into your method as params[:filter]

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 :)

URL rewriting in ROR

I am Amit. i am new to Rails. Please forgive me if ask any stupid
questions.
I have gone through this article. I am also suffering with the same
problem.
my website URL like this: locahost:3000/users/edit/30
I don't want to show the controller:users and action: edit.
I want to Re-Write (rewrite) the URL or i want to maintain the URL as
http://127.0.0.0:3000/30/ only.(30 is a user id)
I am not interested to show the controller(user) and action (edit)
I total intention is to Hiding (HIDING) and rewriting (REWRING) the URL
and mainly i want to hide the URL Extensions with controller and actions
mainly..
It's an odd requirement to want to use a route this way as it will make it difficult for you to expand this scheme to support other actions in your application. One of the advantages of Rails conventions done as they are is that you usually don't have to worry about (often trivial) application details or have to have strong opinions about them.
But it you really, really want to, you can add this to your config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.connect "/:id", :controller => "users", :action => "edit"
end
Remember that this limited scheme will mean you can only route to the edit action. Not terribly useful I would suggest.
The rails routes.rb file is your answer:
Lots of information here on how to do exactly what you need:
http://guides.rails.info/routing.html#customizing-resourceful-routes
For most application I have been using the standard:
/script.cgi?arg1=foo&arg2=bar
Now obviously /user/edit/eric looks much better for a URL that involves
editing the "Eric" user. But how does this work out when you have
multiple items. For example if I want to delete a group of users. I
might have something like this in the old way:
/user/delete.cgi?id=3&id=5&id=7
or for a more readable format we could have:
/user/delete.cgi?username=eric&username=john&username=paul
This of course assumes that usernames are unique. :) So it seems that I
would want something like:
/user/delete/eric/john/paul
This makes it obvious what is happening from the url. But how do I
generate this? And if a action receives this request how does this get
parsed so I have something like:
#delete_usernames ===>>> [ 'eric', 'john', 'paul' ]
This way I can then load up those users and delete them. I know that
ActionController provides some methods for dealing with URL rewriting
but I am unsure how to best use them. Any help or pointers are greatly
appreciated.

Why do I need to work harder to make my Rails application fit into a RESTful architecture?

I started a Rails project recently and decided to use RESTful controllers. I created controllers for my key entities (such as Country) and added index, new, edit, create, show, update and delete. I added my map.resources :country to my routes file and life was good.
After development progressed a little, I started to encounter problems. I sometimes needed extra actions in my controller. First there was the search action that returned the options for my fancy autocompleting search box. Then came the need to display the countries in two different ways in different places in the application (the data displayed was different too, so it wasn't just two views) - I added the index_full action. Then I wanted to show a country by name in the URL, not by id so I added the show_by_name action.
What do you do when you need actions beyond the standard index, new, edit, create, show, update, delete in a RESTful controller in Rails? Do I need to add (and maintain) manual routes in the routes.rb file (which is a pain), do they go in a different controller, do I become unRESTful or am I missing something fundamental?
I guess I am asking, do I need to work harder and add actions into my routes.rb file for the privilege of being RESTful? If I wasn't using map.resources to add the REST goodies, the standard :controller/:action, :controller/:action/:id routes would handle pretty much everything automatically.
I would treat search as a special case of index. Both actions return a collection of resources. The request parameters should specify things like page, limit, sort order, and search query.
For example:
/resources/index # normal index
/resources/index?query=foo # search for 'foo'
And in resources_controller:
before_filter :do_some_preprocessing_on_parameters
def index
#resources = Resource.find_by_param(#preprocessed_params)
end
As for index_full and search_by_name, you might look at splitting your current controller into two. There's a smell about what you've described.
Having said that, you're absolutely right that there's no point in forcing your app to user restful routes when it doesn't deliver anything over /:controller/:action/:id. To make the decision, look how frequently you're using the restful resource route helpers in forms and links. If you're not using them, I wouldn't bother with it.
If I go beyond the standard CRUD actions with my models, I normally just add the methods as required. Searching is something I add to many controllers, but not every one, so I add it and maintain the routes normally:
map.resources :events, :collection => { :search => :get }
Moving these actions to an entirely separate controller might keep some of your controllers RESTful, but I find that keeping them in context is far more useful.
REST does not specify that you can't have additional views. No real world application is going to be able use only the supplied actions; this is why you can add your own actions.
REST is about being able to make stateless calls to the server. Your search action is stateless each time as the data so far is supplied back, correct? Your alternate display action is also stateless, just a different view.
As to if they should be manual routes or a new controller, that depends on how distinct the activity is. Your alternate view, if it provides a full set of CRUD (create, read, update, delete) operations would do well to be in a new controller. If you only have an alternate view to the data, I would just add an alternate view action.
In other words, it doesn't sound like your application is failing to be RESTful, it is more an issue of realizing that the automatically generated feature set is a starting point, not a conclusion.
In my opinion they may have gone a bit off the rails here. What happened to DRY?
I'm just getting back into Rails not having done much development with it since beta and I'm still waiting for the light-bulb to come on here. I'm still giving it a chance but if it hasn't happened for me by the end of my current project I'll probably just drop-back to the old standard routes and define the methods as I actually need them for the next one.
I won't go on to explain more about REST since I think that has been answered in this question, however I will talk a little bit about the default route.
My main problem with the default route is that if you have multiple sites using the same Rails app it can look horrible.
For example there may be controllers that you don't want people to be able to see on one app:
http://example1.somesite.com/example_2/foo/bar/1
compare this to
/:controller/:action/:id
This would go to the controller example_2/foo, action bar and id 1
I consider this to be the main flaw of Rails' default route and this is something that RESTful routes (with subdomain extensions) or only named routes (map.connect 'foo' ... ) can fix.
To remain RESTful in your design, you need to rethink what you call a resource.
In your example a show action for a search controller, (search resource) is the direction to remain restful.
In mine, I have a dashboard controller (show) and controllers for single fields of in-place ecditors (show and update)

Resources