Ruby on Rails: Basic parameterized queries and URL formation - sql

I'm trying to learn how to query a rails database and return the results as JSON. In my example, I want to query the data using the parameters, city and state.
So far, in my controller, I have gotten the following action to work.
def state
#bathrooms = Bathroom.where("state = ?" ,params[:state])
respond_to do |format|
format.json { render :json => #bathrooms }
format.js { render :nothing => true }
end
end
This is also my routing entry.
match '/bathrooms/state/:state',
:controller => "bathrooms",
:action => "state"
I can call this resource with the following URL:
http://localhost:3000/bathrooms/state/CA.json
That's all good but I don't know how to query by more than one parameter. Adding and AND clause in the controller seems to be easy enough.
BUT....I don't know how to
a.) Correctly write the routing entry?
b.) What would the URL look like if I tested it in a browser?
I've tried to understand rake routes but I must be missing something.
Could someone provide a basic example for what the action should look like? What the routing entry should look like and what does the URL to access the resource look like?
Again, if written in SQL, this is what I would like to be returned.
SELECT * from bathrooms WHERE city='Chicago' AND state = 'IL'
Any help appreciated.

You don't have to pass everything by the route - the URL also support GET parameters - those are the parameters you usually see after the question mark in the URL. You can add those GET parameters without changing your routes: http://localhost:3000/bathrooms/state/IL.json?city=Chicago. Then your can access the city parameter via params[:city]. but in your case, I think it will be better to use http://localhost:3000/bathrooms/index.json?state=IL&city=Chicago. You'll also need to change your routing to
match '/bathrooms/index',
:controller=>:bathrooms,
:action=>:index
and put the code in the index method of BathroomsController. You access the parameters the same - but the concept is different - you don't enter a state and look for bathrooms by city, you just look for bathrooms by state and city.
Anyways, you don't want to write the URL by hand - you want to a Rails helper or an HTML form generate it:
link_to "bathroom in Chicago, IL",:controller=>:bathrooms,:action=>:index,:state=>'IL',:city=>'Chicago'
If you want to use a form(to let the users choose their own state and city), you need to set it's method to GET:
form_tag {:controller=>:bathrooms,:action=>:index},:method=>:get do
and put state and city as fields.
It's also worth noting that while you can use SQL's AND to perform a search by multiple fields, you can also chain where methods: Bathroom.where(:state=>params[:state]).where(:city=>params[:city]).

You can put any arbitrary parameters in your querystring.
For example:
http://localhost:3000/bathrooms/state/CA.json?city=Chicago
your query looks like this:
#bathrooms = Bathroom.where("state = ? and city= ?" ,params[:state], params[:city])

Related

Scope/Filter collection from route

I've got a standard resourceful route setup for 'Invoice' however I'm looking to add in the ability to filter the invoice records based on their state.
/invoices - shows all invoices
/invoices/unpaid - shows all unpaid invoices
/invoices/paid - shows all paid invoices.
/invoices/3 - shows invoice #3
I've gotten this working no problem with an explicitly defined match route.
match "/invoices/pending" => "invoices#index", :state => 'pending'
However, with a growing number of possible states this means modifying the routes regularly, and also means I'm repeating myself quite a lot.
My next attempt was to make this route a little more dynamic with named params in the match route.
match "/invoices/:state" => "invoices#index"
However this then negates the /invoices/id route and trying to look for /invoices/3 finds no records as it's searching based on the state parameter.
Can anyone help with defining a filter route such as this that'll work dynamically?
Add regular expression as constraint in the routes:
match "/invoices/:id" => "invoices#show", :id => /\d+/
match "/invoices/:state" => "invoices#index"
It should select the only-number id-s for show, and the rest for the index.
Try to use routing constraints . Something like
match "/invoices/:id" => "invoices#show", constraint: { id: /\d+/ }
match "/invoices/:state" => "invoices#index", constraint: { state: /\w+/ }
I decided to take a slightly different route (excuse the pun) with this, by adding a constraint that rather then using REGEX used a method to look at possible states direct on the state machine.
# State based routes, which match the state machine dynamicly.
match "/invoices/:state" => "invoices#index", constraints: lambda { |r|
# Get an array if potential state names.
states = Invoice.state_machine.states.map &:name
# See if the request state name matches or not.
states.include?(r.params[:state].parameterize.underscore.to_sym)
}
# Resource routes go here.
This basically means that if I now go looking for /invoices/paid or /invoices/unpaid I get my expected index action, with the state variable set as expecetd.
However, doing something like /invoices/pay_soon which is not a valid event returns a 404.
Pretty happy with that solution, but thanks to the other suggestions which got me on to the track of using a constraint.

How to check rails route presence in a view

I want something like this:
if has_route?(:controller => 'posts', :action => 'index')
...
How I can do this in a view?
---- SOLUTION ----
def has_route? options
Rails.application.routes.routes.map{|route| route.defaults}.include?(options)
end
I know this is sort of the inverse of what you want, but you can do something like this:
Rails.application.routes.recognize_path('/posts')
Which will return a hash like this:
{:controller=>"posts", :action=>"index"}
But i don't know of an API for giving it the hash and returning the path.
You could look through the ActionDispatch::Routing::RouteSet API.
Also, note that Rails.application.routes.routes returns an Array of Journey::Routes objects, so you could map this array for the controller/action pairing you want. But it seems like this API should already exist. If not, perhaps it should. :)
You could try something like respond_to?("[name of rails routing method]")
Usually the routes test in this way
it "routes to #index" do
get("/projects").should route_to("projects#index")
end
The scaffold for resource will generate the spec file for testing routes, for example you can look here

How to test a search controller action which has filters in Rails with RSpec?

We have a bar with filters in almost all of our table-based views in a rails app and we need to test the controller action.
An example of the code:
def index
#users = User.
with_status(params[:status]).
with_role(params[:role_id]).
search(params[:q])
end
The above methods are ActiveRecord scopes which are setup to be bypassed if the passed value if blank.
What I need to do now is spec it sanely and test all the esge cases:
no params passed
only role, only status, only search
role + status, role + search, ... (pairs of 2)
role + status + search
The basic spec example I have written is as follows:
context "when filtering by status" do
before do
1.times { Factory(:user, :status => "one") }
3.times { Factory(:user, :status => "other") }
end
it "returns only users with the provided :status" do
get :index, :status => "one"
assigns(:users).size.should == 1
assigns(:users)[0].status.should == "one"
end
end
I want to write a matrix that will mix and match the role, status and search params and generate the appropriate spec examples.
Is the Array#permutation the solution or is there a better way to do it?
I would test the scopes in the model, so make sure that they can handle the blank value correctly, and also handle the set value correctly.
Then inside the controller, I would test the expectation that the chain is called (use stub_chain). The fact that the chain will return the correct result is handled by the fact that each scope individually has the correct behaviour (you tested that), and the combined behaviour is ensured by rails/activerecord. You should test the passed parameters are handled correctly.
The code you built to test the matrix is very impressive. But for me I try to make sure that my tests are readable, I consider them a kind of documentation of what a piece code is expected to do. To me your code is not comprehensible at first sight.
Hope this helps.

What is the proper RESTful way to "like" something in Rails 3?

Let's say I have a Rails 3 app that displays videos. The user can "Like" or "Dislike" the videos. Also, they can like/dislike other things like games. I need some help in the overall design and how to handle the RESTful routes.
Currently, I have a Like Class that uses polymorphic design so that objects are "likeable" (likeable_id, likeable_type)
I want to do this via AJAX (jQuery 1.5). So I was thinking something like:
javascript
// these are toggle buttons
$("likeVideo").click( function() {
$.ajax({
url: "/likes/video/" + video_id,
method: "POST",
....
});
} );
$("likeGame").click( function() {
$.ajax({
url: "/likes/game/" + game_id,
method: "POST",
....
});
} );
rails controller
Class Likes < ApplicationController
def video
# so that if you liked it before, you now DON'T LIKE it so change to -1
# or if you DIDN'T like it before, you now LIKE IT so change to 1
# do a "find_or_create_by..." and return JSON
# the JSON returned will notify JS if you now like or dislike so that the
# button can be changed to match
end
def game
# same logic as above
end
end
Routes
match "/likes/video/:id" => "likes#video", :as => :likes_video
match "/likes/game/:id" => "likes#game", :as => :likes_game
Does this logic seem correct? I am doing a POST via AJAX. Technically, shouldn't I be doing a PUT? Or am I being too picky over that?
Also, my controller uses non-standard verbs. Like video and game. Should I worry about that? Sometimes I get confused on how to match up the "correct" verbs.
An alternative would be to post to something like /likes/:id with a data structure that contains the type (game or video). Then I could wrap that in one verb in the controller...maybe even Update (PUT).
Any suggestions would be appreciated.
Rest architectural style does not specify which "verb" you should be using for what. It simply says that one can use HTTP if they want to for connectors.
What you are looking for is HTTP specifications for method definitions. In particular POST is intended for:
- Annotation of existing resources;
- Posting a message to a bulletin board, newsgroup, mailing list,
or similar group of articles;
- Providing a block of data, such as the result of submitting a
form, to a data-handling process;
- Extending a database through an append operation.
while PUT:
requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server.
Which category your functionality falls into is up to you - as long as you are consistent with yourself about it.

How do I perform a search in Rails3?

I'm aware that I can search queries by calling where() on a model as follows:
Post.where(:title => 'My First Post')
However, what if I don't know if the user wants to filter out the search parameters?
For example, I have a search form that has an optional title field. If the title is filled, the form should search for a title. If it is not, however, the form should just return all fields.
I tried doing something along the lines of
search = Post.all
if params[:title].present?
search.where(:title => params[:title])
end
However, Rails immidiately returns the result of the search when I call Post.all and I cannot further add conditions/
How do I do this?
Thanks!
.all is a 'finisher' method for arel and causes the query to actually be called (same goes for .each and .first). So, if you want to be able to keep building up the scope conditionally, .all should be the last thing called, if you want to call it at all.
Given that there really isn't much in the way of relations to chain here, it would seem that a simple one-liner with a ternary operator might do:
#posts = params[:title].present? ? Post.where(:title => params[:title]) : Post.all
... because when there is no :where clause scope, you couldn't later chain it to :all, anyway.