Rails query by arbitrary column - ruby-on-rails

In my Rails API / Angular app, I want to be able to search Rails tables using field values. I currently have this code below working, which allows searching the users table by email id, and it returns the users record as JSON.
api/controllers/users_controller.rb
def query # by email
queried_user = User.where(email: params[:email]).first
if !queried_user.nil?
render json: queried_user, root: false
else
render json: {error: 'Does not exist'}, status: :not_found
end
end
config/routes.rb
get 'api/users/:id/query' => 'api/users#query'
Example url
http://0.0.0.0:8080/api/users/1/query?email=testuser1#example.com
Example returned JSON
{"id":14,"title":"Dr.","first_name":"John","last_name":"Smith","email":"testuser1#example.com","job_title":"Head Bioligist","organisation":"NIH","phone_office":null,"city":null,"country":null,"approved":true,"admin":false,"template":false}
This is all working fine at present, but there are two issues I cannot resolve.
I would like the url to not contain an :id I find when I leave the id out of the url, Rails treats the query parameter as the id. I can made it work by hard-coding a fake id, but it doesn't seem like the right answer to me.
I would like to pass an abitary param hash to the query method. It should map the columns based on the hash contents.
if params = {email: 'testuser1#example.com'} then it should work as now, but other desired options might be:
{job_title: 'Manager'}
{city: 'LA', last_name: 'Smith'}
I expect I will change this code, but don't know how to pass arbitrary elements to the where.
queried_user = User.where(email: params[:email])

The where method can accept a hash, therefore you can pass the param hash containing the condition for the query. Just note only equality and range conditions can be used when passing a hash to the where method. Just be sure that in terms of security of your application you are covered. example:
queried_user = User.where(params[:user])
To get rid of the :id in your routes file define a new route similar to this:
match 'api/users/query', to: 'users#query', as 'user_search'
and then use the 'user_search_path' for sending the search to the query action of the users controller.

Related

How can I selectively add query parameters in redirect_to?

In my application, the session hash can contain the keys sort and ratings (in addition to _csrf_token and session_id), depending on what action a user takes. That is, it can contain both of them, either one of them, or neither, depending on what a user does.
Now, I wish to call redirect_to in my application and, at the same time, restore any session information (sort or ratings) the user may have provided.
To do this, I want to insert whatever key-value session has currently got stored (out of sort and ratings) as query parameters in my call to redirect_to. So, the path might look something like /movies?sort=...&ratings=....
I don't know how to write the logic for this. How can I do this? And how do I go about selectively inserting query parameters while calling redirect_to? Is it even possible to do this?
Any help is greatly appreciated. Thank you in advance.
First just compose a hash containing the parameters you want - for example:
opts = session.slice(:sort, :ratings)
.merge(params.slice(:sort, :ratings))
.compact_blank
This example would contain the keys :sort, :ratings with the same keys from the parameters merged on top (taking priority).
You can then pass the hash to the desired path helper:
redirect_to foos_path(**opts)
You can either just pass a trailing hash option or use the params option to explitly set the query string:
irb(main):007:0> app.root_path(**{ sort: 'backwards' })
=> "/?sort=backwards"
irb(main):008:0> app.root_path(params: { ratings: 'XX' })
=> "/?ratings=XX"
irb(main):009:0> app.root_path(params: { })
=> "/"
An empty hash will be ignored.
If your calling redirect_to with a hash instead of a string you can add query string parameters with the params: key:
redirect_to { action: :foo, params: opts }
If you're working with an arbitrary given URL/path and want to manipulate the query string parameters you can use the URI module together with the utilities provided by Rack and ActiveSupport for converting query strings to hashes and vice versa:
uri = URI.parse('/foo?bar=1&baz=2&boo=3')
parsed_query = Rack::Utils.parse_nested_query(uri.query)
uri.query = parsed_query.except("baz").merge(x: 5).to_query
puts uri.to_s # => "/foo?bar=1&boo=3&x=5"

accessing multiple values from hash ruby on rails

This code snippet
room = Room.find(roomId)
returns a single column from room table, the returned value contains multiple attributes, like id, name, description, duration.
I want when coding
render json: room
to return only duration and name.
Do i have to write something like that
render json: room[:duration, :name]
query that will only give you the attributes that you want :
room = Room.select(:id, :duration, :name).find(room_id)
You can use the only option of as_json to include only certain attributes.
render json: room.as_json(:only => [:duration, :name])
You're slightly incorrect when you say Room.find(roomId) returns a "single column from the room table". It actually returns a single row.
Anyway, there are a few ways you can do this.
A good starting point is to use the .attributes method on a model to convert it to a Hash.
So for example say you have a user with a name, email, and id columns in the database. You can call user.attributes to get
{ "name" => "max", "email" => "max#max.com", "id" => 5 }.
you can select just the name and email with
user.select { |k,v| k.in?("name", "email") } and then call to_json on the result.
To avoid having to write custom to_json code for each controller response, you can overwrite .attributes in the model. Say my User table has a password column that I never want to expose in my API:
class User
def attributes
super.reject { |k,v| v.in?("password") }
end
end
Then, if you need to return a list of users as JSON:
render json: { users: #users.map(&:attributes) }.to_json, status: 200
The answer by Pavan will work as well, by the way.
Another approach is to use jbuilder and create json "views" for your records.

rails "where" statement: How do i ignore blank params

I am pretty new to Rails and I have a feeling I'm approaching this from the wrong angle but here it goes... I have a list page that displays vehicles and i am trying to add filter functionality where the user can filter the results by vehicle_size, manufacturer and/or payment_options.
Using three select form fields the user can set the values of :vehicle_size, :manufacturer and/or :payment_options parameters and submit these values to the controller where i'm using a
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, :vehicle_size => params[:vehicle_size] )
kind of query. this works fine for individual params (the above returns results for the correct vehicle size) but I want to be able to pass in all 3 params without getting no results if one of the parameters is left blank..
Is there a way of doing this without going through the process of writing if statements that define different where statements depending on what params are set? This could become very tedious if I add more filter options.. perhaps some sort of inline if has_key solution to the effect of:
#vehicles = Vehicle.order("vehicles.id ASC").where(:visible => true, if(params.has_key?(:vehicle_size):vehicle_size => params[:vehicle_size], end if(params.has_key?(:manufacturer):manufacturer => params[:manufacturer] end )
You can do:
#vehicles = Vehicle.order('vehicles.id ASC')
if params[:vehicle_size].present?
#vehicles = #vehicles.where(vehicle_size: params[:vehicle_size])
end
Or, you can create scope in your model:
scope :vehicle_size, ->(vehicle_size) { where(vehicle_size: vehicle_size) if vehicle_size.present? }
Or, according to this answer, you can create class method:
def self.vehicle_size(vehicle_size)
if vehicle_size.present?
where(vehicle_size: vehicle_size)
else
scoped # `all` if you use Rails 4
end
end
You call both scope and class method in your controller with, for example:
#vehicles = Vehicle.order('vehicles.id ASC').vehicle_size(params[:vehicle_size])
You can do same thing with remaining parameters respectively.
The has_scope gem applies scope methods to your search queries, and by default it ignores when parameters are empty, it might be worth checking

Get current action's path/url including query string? (Rails)

Simple question - how do I get the path or full URL of the current action INCLUDING the query string?
I wish to save it to the session variable like so:
def show
#thingy = Thingy.find(params[:id])
session[:some_var] = current_url
...
end
At the moment I'm doing the following, but it seems a bit heavy-handed (especially the specifying of query string params individually):
def show
#thingy = Thingy.find(params[:id])
session[:some_var] = thingy_path(#thingy, :q1 => params[:q1], :q2 => params[:q2])
...
end
request.url is probably what you are looking for.
access params variable,it will give you query as well as controller and action.
By using request object you can dig more deeper if you want.

Get two random elements from a RoR model

I'm trying to use RoR for something simple and I'm having some trouble picking up the basics. My closest background is ASP.NET MVC but I'm finding all of the RoR tutorials focus on what rails is really good at (scaffold stuff) but not how to make your own actions and get them to do stuff with parameters etc. (something trivial in ASP.NET MVC).
At the moment I am trying to get two random elements out of the model.
I think I'm dealing with an ActiveRecord collection of some sort?
I have read that there is a .rand method somewhere on collections/arrays, although other places suggest that rand is just a method for getting a random number up to a certain count. I can't even get the following code to work:
def index
#items = Array.new(Item[0], Item[0])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #domain }
end
end
Anything that can help with this, and ideally help with further patching from ASP.NET MVC to RoR would be really appreciated.
To retrieve two random items from an ActiveRecord model:
#things = Thing.all(:order => 'RANDOM()', :limit => 2)
If you want 2 random items from the database, then ask the database for 2 random items:
#items = Item.find(:all, :limit => 2, :order => "RANDOM()")
There's no point loading all of the Items from your system if you're only using 2, that's a waste.
If you do already have an array from somewhere else that you need to get random values from, then Rails adds a rand method to the Array class:
#items = [my_arr.rand, my_arr.rand]
I don't know what you were trying to do with Item[0] but that doesn't do anything meaningful in Rails.
What does your model look like? I'm not sure what you're trying to do with Item[0] there. For randomizing your array you could do something like this:
#items = ["item1", "item2", "item3"].sort_by {rand}
then you could just do #items[0] and #items[1] to get 2 items of the randomized array.
As for params, you can get any form variables or request params from the query string by using the params hash:
params[:user]
The symbol name is just the name of the form field or param in the query string.
Rails controllers usually contain one or more restful actions (index, show, new, create, delete, edit, update) if you've routed it as a resource, but you adding your own actions involves just adding a new method to your controller, routing that action in the routes.rb, and creating a view with with the name of that action.
More info on your model & what you are trying to accomplish would help, but if you are trying to pull a random record from a database like sqlite, you can do something like:
#item = Items.find(:first, :order => 'RANDOM()')
Where Items is your model class. The 'RANDOM()' is just a string handed to the database to tell it how to sort, so you'll have to adjust to match whatever database you're using.
With a Mysql Database use RAND() and not RANDOM()
#items = Item.find(:all, :limit => 2, :order => "RAND()")

Resources