Rails 4.2 routing: deep and dynamic defaults for params? - ruby-on-rails

I'm looking to reuse controllers while maintaining pretty links for different resources.
Let's say there are articles, users and comments in the system.
Comments controller index action takes a hash parameter named filter_params and does the filtering. So if params[:filter_params][:user_id] has value 1, it will display all comments by user with ID 1. Similarly with articles and article_id.
What I'm looking for is to have the following routes work:
/users/1/comments, to comments#index, params { filter_params: { user_id: 1 } }
/articles/1/comments, to comments#index, params { filter_params: { article_id: 1 } }
/articles/1/comments?filter_params[user_id]=1, to comments#index, params { filter_params: { article_id: 1, user_id: 1 } }
My initial idea was to use defaults option for routes and construct the default filter_params there. But that seems to accept only a static hash instead of a proc where I could access the request. And on top of that, it would not work with 3rd example, since defaults can't get overwritten and subsequently ?filter_params[user_id]=1 would get ignored.
So is there a way to make it work with only the router? Or should I give up and create a before_filter in CommentsController that stuffs params[:user_id] and params[:article_id] into params[:filter_params]?

Related

Rails 4 route constraints with query string or numbers only

I'm trying to implement these two routes with constraints:
get "products/:id", to: "products#show", constraints: { id: /\d/ }
get "products/:name", to: "products#search", constraints: { name: /[a-zA-Z]/ }
The first route should trigger with an URL like localhost:3000/products/3 and the last one should trigger with localhost:3000/products/?name=juice.
Doesn't work, I googled a lot about this problem but I seem to find solutions for the second or third version of Ruby on Rails, and most of them are deprecated now.
Any ideas? Thanks.
You can use the routes as-is if you like the look of the resulting URLs. No need for a query string.
In your controller, you'd have something along these lines:
def show
#product = Product.find(params[:id])
# do stuff
end
def search
# I'm assuming your Product model has a `name` attribute
#product = Product.find_by(name: params[:name])
# do stuff
end
You probably want some error checking on find_by returning nil because :name did not match anything, but that's basically it.
Your routes.rb would have your original route definition:
get "products/:id", to: "products#show", constraints: { id: /[[:digit]]/ }
get "products/:name", to: "products#search", constraints: { name: /[[:alpha:]]/ }
Your application should respond to both:
localhost:3000/products/3
localhost:3000/products/juice
First you have to know what the query string and url.
localhost:3000/products/3 this is url and the three is symbol :id when use question mark localhost:3000/products/3?name=juice, name=juice is query string.
So get "products/:name", to: "products#search", constraints: { name: /[a-zA-Z]/ } you should replace below
get "products/:id?name=:name", to: "products#search", constraints: { id: /\d/,
name: /[a-zA-Z]/ }
example: localhost:3000/products/3?name=xxxxx
I have a feeling the regex matching might be a little different in routes - I think it anchors to the start and end of the parameter every time.
With that in mind, your regexes are matching a single character each. Try id: /\d+/ and name: /.*\D.*/ instead.
It might be easier and more Railsy just to specify a different route:
get "products/:id", to: "products#show"
get "products/search/:name", to: "products#search"
Or, just have your products#index action handle params[:q] as the search string, that's quite common too. Then your URL would be www.example.com/products?q=juice.

Rails query by arbitrary column

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.

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

Mapping through a hash of key/values

I have a form being submitted that is saving multiple records, and the parameters look something like this:
{
"utf8"=>"✓",
"_method"=>"put",
"products"=> {
"321" => {
"sale_price"=>"10"
},
"104" => {
"sale_price"=>"10"
}
}
}
Then in my controller, I have this:
#updated_products = Product.update(params[:products].keys, params[:products].values)
This expects the keys (321, 104) to be IDs.
However, I'm using the to_param in my model to change my urls from IDs to another column value.
Is there a way to take the params[:products].keys and swap them for the appropriate IDs so I can use IDs in the .update() statement.
I can use Product.find_by_column_name(321).id to get the id although I don't know how to do this. Still new to rails.
Any help would be appreciated. Thanks.
Looking at the source code here #update iterates through each key and runs update_attributes so it goes through all the validations. You can change your method to
#updated_products = params[:products].inject([]) do |array, (column_id, attributes)|
product = Product.find_by_column_id column_id
if product.update_attributes(attributes)
array << product
else
array
end
end
This may seem a little complex but it is equal to this one below which is easier to understand and code read
#updated_products = []
params[:products].each do |column_id, attributes|
product = Product.find_by_column_id column_id
if product.update_attributes(attributes)
#updated_products << product
end
end

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