I have a controller which has a lot of options being sent to it via a form and I'm wondering how best to separate them out as they are not all being used simultaneously. Ie sometimes no, tags, sometimes no price specified. For prices I have a default price set so I can work around with it always being there, but the tags either need to be there, or not. etc.
#locations = Location.find(params[:id])
#location = #locations.places.active.where("cache_price BETWEEN ? AND ?",price_low,price_high).tagged_with([params[:tags]).order(params[:sort]).paginate :page => params[:page]
I haven't seen any good examples of this, but I'm sure it must happen often... any suggestions? Also, even will_paginate which gets tacked on last should be optional as the results either go to a list or to a google map, and the map needs no pagination.
the first thing to do when refactoring a complex search action is to use an anonymous scope.
Ie :
fruits = Fruit.scoped
fruits = fruits.where(:colour => 'red') if options[:red_only]
fruits = fruits.where(:size => 'big') if options[:big_only]
fruits = fruits.limit(10) if options[:only_first]
...
If the action controller still remains too big, you may use a class to handle the search. Moreover, by using a class with Rails 3 and ActiveModel you'll also be able to use validations if you want...
Take a look at one of my plugins : http://github.com/novagile/basic_active_model that allows you to easily create classes that may be used in forms.
Also take a look at http://github.com/novagile/scoped-search another plugin more specialized in creating search objects by using the scopes of a model.
Related
I'm not sure if this is just a lacking of the Rails language, or if I am searching all the wrong things here on Stack Overflow, but I cannot find out how to add an attribute to each record in an array.
Here is an example of what I'm trying to do:
#news_stories.each do |individual_news_story|
#user_for_record = User.where(:id => individual_news_story[:user_id]).pluck('name', 'profile_image_url');
individual_news_story.attributes(:author_name) = #user_for_record[0][0]
individual_news_story.attributes(:author_avatar) = #user_for_record[0][1]
end
Any ideas?
If the NewsStory model (or whatever its name is) has a belongs_to relationship to User, then you don't have to do any of this. You can access the attributes of the associated User directly:
#news_stories.each do |news_story|
news_story.user.name # gives you the name of the associated user
news_story.user.profile_image_url # same for the avatar
end
To avoid an N+1 query, you can preload the associated user record for every news story at once by using includes in the NewsStory query:
NewsStory.includes(:user)... # rest of the query
If you do this, you won't need the #user_for_record query — Rails will do the heavy lifting for you, and you could even see a performance improvement, thanks to not issuing a separate pluck query for every single news story in the collection.
If you need to have those extra attributes there regardless:
You can select them as extra attributes in your NewsStory query:
NewsStory.
includes(:user).
joins(:user).
select([
NewsStory.arel_table[Arel.star],
User.arel_table[:name].as("author_name"),
User.arel_table[:profile_image_url].as("author_avatar"),
]).
where(...) # rest of the query
It looks like you're trying to cache the name and avatar of the user on the NewsStory model, in which case, what you want is this:
#news_stories.each do |individual_news_story|
user_for_record = User.find(individual_news_story.user_id)
individual_news_story.author_name = user_for_record.name
individual_news_story.author_avatar = user_for_record.profile_image_url
end
A couple of notes.
I've used find instead of where. find returns a single record identified by it's primary key (id); where returns an array of records. There are definitely more efficient ways to do this -- eager-loading, for one -- but since you're just starting out, I think it's more important to learn the basics before you dig into the advanced stuff to make things more performant.
I've gotten rid of the pluck call, because here again, you're just learning and pluck is a performance optimization useful when you're working with large amounts of data, and if that's what you're doing then activerecord has a batch api you should look into.
I've changed #user_for_record to user_for_record. The # denote instance variables in ruby. Instance variables are shared and accessible from any instance method in an instance of a class. In this case, all you need is a local variable.
I am developming a JSON API with Rails 4.2 which looks like this:
GET api/v1/car => app/controller/api/v1/car_controller#index
GET api/v1/car/:id/installment => app/controller/api/v1/carresources/installment_controller#index
Now i would like to extend these endpoint with filters.
api/v1/carresources/installment#index:
all installments for a specific car during a specific time period
all installments for a specific car by payment type (cash, mobile money, <- mobile money provider)
api/v1/car#index:
all cars by region
all cars by dealer
all cars by loan schema
The way i implemented is the following:
app/controller/api/v1/car_controller.rb
def index
res = []
if params.key?('start_date') and parms.key?('end_date')
res = Car.index_period(params['start_date'], params['stop_date']
elsif params.key?('loanstructure')
res = Car.index_loan_strucuture(params['loan_strucuture'])
....
end
end
Which is working, but not such a nice solution to put completely semantically different logic behind these if-"graves".
It would be possible to create a new endpoint for each filter, but i have the feeling that this bloats the routing and controller structure.
I would also like to avoid these if clauses, because these is also some authorization on the users role going on -which i skipped showing - which is also done with if clauses
Is there another clever way, or should i spent an extra route for each filter, where i then need to copy the whole authorization structure.
Many thanks in advance
Many thanks in advance
OP I would suggest breaking the filtering code little separately by handling the request.query.params in a filter class.
This filter class helps in de-coupling the code by
Allowing validation of the filters which are being passed so you can have a parseFilters function which does that.
You can use the hashtable which points to
{('start_date','end_date'): "index_period(params['start_date'], params['stop_date']", etc }
this will help you to apply the filters directly from the parsefilters.
This will ensure that your code is decoupled and also can be reused in other scenarios.
Lets say, for the sake of the question, that I have two user types: type1 & type2. I want Rails to use a controller/module depending on the type of user that is being displayed. For example:
If User(id: 1, type: 'type1') has type1 and User(id: 2, type: 'type2') has type2, going to:
/users/1
would select the Type1::UsersController. And going to:
/users/2
would select the Type2::UsersController.
This will allow me to use different controllers and views for each type.
Note: I don't want the type to be displayed in the URL, I want it to be dynamic.
As GoGoCarl says, this isn't really the Rails way to do things. That said, it's not that difficult to get it to work. You can do something like this in routes.rb:
get 'users/:id', to: 'type1/users#show', constraints: lambda { |request|
_id = request.fullpath.gsub('/users/','').to_i
# Note: there might be an easier way to get ID from the request object
User.find(_id)._type == 'type1'
}
get 'users/:id', to: 'type2/users#show', constraints: lambda { |request|
_id = request.fullpath.gsub('/users/','').to_i
User.find(_id)._type == 'type2'
}
I've renamed your type field to _type in my example (because Rails uses type for Single Table Inheritance). I've tested this and it works as desired.
This is possible, but you'd be doing a lot of (probably) unnecessary fighting against the Rails way. I would think you would want one controller as there's probably quite a bit of shared logic (such as saving, deleting, creation, etc).
To answer your question (because I hate when people leave recommendations instead of answers), then you'll need to create a Module that extends Routing, which will allow you to do custom matching. From there, you can do your checks and route appropriately. Here's an example.
That said, a better route to go (no pun intended) would be to have one controller which has a centralized method that can select views.
def find_view view_name
"#{view_name}#{#user.type}"
end
So, a call to render find_view('new') would attempt to render a view named "new-type1." You can put all your type1 user-specific logic in that view. Same for user type2.
Again, since I would think there would be much overlap in your user code, you may want to push this find_view method to a helper class so you can call it from your views, and do things like render specific partials instead based on the user type. That will allow for more code re-use, which is never a bad thing.
Once you get your head wrapped around having a single controller, there are a number of simple ways that you can push user-type-specific code to different avenues -- the views method explained above, you can push all your relevant code to separate helpers which are dynamically called based on the user type, and I'm sure there's more (probably better ones). But all those have one major thing in common -- you'll be fighting Rails a LOT less, and you will have less duplicate code, if you succumb to letting Rails have its way with one route, one controller.
Good luck, hope that helps.
There was a very similar question before but i still struggle.
Is it possible to build a query up in stages?
Let's say I have a search form with many text and select fields that may be chained with and/or or which could be blank.
So the sql statement should consist of several parts that are connected individually for each search.
I tried to create strings for every option and put them to a symbol? (i mean #options) and put that in the where clause (e.g. Product.where(#options) ). That works somehow but i have got troubles with this part: 'params[:query]' when it's in quotes. Either my sql statement says 'select products from products where (name like params[:query]') or if i try #{params[:query]} it says: select products from products (where 'name' like ''.)
So how can i chain different parts of a query?
I looking forward to your answers!
Never, ever, ever embed raw strings in your SQL. This is extremely bad form. You should always use the escaping mechanism provided by Rails or something equivalent to avoid ending up in serious trouble. Inserting content from params is very dangerous and should never be done as it only takes this to nuke your app: { :query => '\"-- DROP TABLE users;' }
Generally you use the helper methods provided by ActiveRecord to build up your query in stages:
scope = Product
if (params[:query].present?)
scope = scope.where([ 'name LIKE ?', "%#{params[:query]}%" ])
end
if (params[:example].present?)
scope = scope.where(:example => true)
end
#products = scope.all
You can build it up in stages like this, modifying the scope in-place each time, and then execute the final call to retrieve it. Generally that's when you use your paginator to split up the results.
It's okay to put pretty much anything in your options because it should be escaped by the time it hits the SQL phase, much as anything on the HTML side is escaped for you as well.
Don't confuse instance variables like #options with a symbol like :query. The two are very different things. Instance variables have the benefit of propagating to your view automatically, so they are often used extensively in controllers. Views should avoid modifying them whenever possible as a matter of style.
I'm working on implementing a search form in a ruby on rails application. The general idea is to use form_tag to submit the search fields (via params) to a search function in the model of the class I'm trying to search. The search function will then iterate through each of the params and execute a scoping function if the name of the function appears in params.
The issue is that when I call the search on a collection like so:
#calendar.reservations.search({:search_email => "test"})
I don't know how to refer to the collection of #calendar.reservations from within the search function.
Additionally I'm confused as to why #calendar.reservations.search(...) works, but Reservations.all.search gives me an error saying you can't call an instance method on an array.
I've got the details of the search method over here: https://gist.github.com/783964
Any help would be greatly appreciated!
I don't know how to refer to the
collection of #calendar.reservations
from within the search function.
If you use self (or Reservation, it's the same object) inside the classmethod, you will access the records with the current scope, so in your case you will see only the reservations of a particular calendar.
[edit] I looked at you search function, and I think what you want is:
def self.search(search_fields)
search_fields.inject(self) do |scope, (key, value)|
scope.send(key, value)
end
end
Additionally I'm confused as to why
#calendar.reservations.search(...)
works, but Reservations.all.search
gives me an error saying you can't
call an instance method on an array.
#calendar.reservations does not return a standard array but a (lazy) AssociationCollection, where you can still apply scopes (and classmethods as your filter). On the other hand Reservation.all returns a plain array, so you cannot execute search there (or any scope, for that matter).
You don't really need a search method at all, as far as I can tell.
Simply use where:
#calendar.reservations.where(:search_email => 'test')
I would strongly encourage you to look at the MetaSearch GEM by Ernie Miller. It handles the kind of thing you're working on very elegantly and is quite easy to implement. I suspect that your view code would almost accomplish what the GEM needs already, and this would take care of all your model searching needs very nicely.
Take a look and see if it will solve your problem. Good luck!
Reservation.all.search doesn't work because it returns all the results as an array, while Reservation.where(..) returns an ActiveRecord object (AREL). Reservation.all actually fetches the results instead of just building the query further, which methods like where, limit etc do.