How to get a string into a where clause in Ruby on Rails 3? - ruby-on-rails

I have this class method:
def self.default_column
"created_at"
end
How can I rewrite the following function, so that I can make use of my default_column method?
def next
User.where("created_at > ?", created_at).order('created_at ASC').first
end
I tried things like these...
def next
User.where("#{default_column} > ?", default_column).order('#{default_column} ASC').first
end
... but I must be awfully wrong here because it doesn't work at all.
Thanks for any help.

You can use:
def next
User.where("#{User.default_column} > ?", self.send(User.default_column)).order("#{User.default_column} ASC").first
end
Or even better
def next
klass = self.class # This is supposing you are inside User model
# Otherwise just use klass = User
klass.where("#{klass.default_column} > ?", self.send(klass.default_column))
.order(klass.arel_table[klass.default_column].asc)
end
Notice that if you handle the method in this way, you cannot chain it: like User.where(name: 'something').next
If you want to achieve this, you have to move next to be def self.next and in that case, you have to pass an instance of the user to it, like this:
def self.next(user)
klass = user.class
klass.where("#{klass.default_column} > ?", user.send(klass.default_column))
.order(klass.arel_table[klass.default_column].asc)
end
In this way you can write something like: User.where(name: 'test').next(#user). You can optionally chain .first to get directly the result, but in this way you will not be able to chain other things, like User.where(name: 'test').next(#user).where(email: 'my#mail.com')
Finally, if you want pure AREL (for portability)
def self.next(user)
klass = user.class
arel = klass.arel_table
column = klass.default_column # This helps cleaning up code
column_value = user.send(column)
klass.where(arel[column].gt(column_value))
.order(arel[column].asc)
end

def next
default_column = self.class.default_column
User
.where("#{default_column} > ?", send(default_column))
.order("#{default_column} ASC")
.first
end

Related

Chaining ActiveRecord_Relation in PORO

In a Rails 5.1 app, I have a query object (PORO) named CoolProducts.
class CoolProducts
def self.call(relation = Product.all)
...
# return an instance of Product::ActiveRecord_Relation
end
end
Now I need to limit the found Products based on the fact the name matches a string.
The following works
CoolProducts.call.where("name ILIKE ?", "%#{string}%")
However, I'd like to encapsulate the matching login within the CoolProducts class allowing to do something like
CoolProducts.call.including_in_name(string)
But I'm not sure where to start from.
Any ideas?
It will be difficult if you want any of your methods to be chainable or return ActiveRecord::Relation.
If you consider explicitly fetching the records when you're done chaining being ok, this should work:
class CoolProducts
def initialize(relation)
#relation = relation
end
def self.call(relation = Product.all)
new(relation).apply_scopes
end
attr_reader :relation
alias_method :fetch, :relation
def including_in_name(string)
tap { #relation = relation.where("name ILIKE ?", string) }
end
def apply_scopes
tap { #relation = relation.where(price: 123) }
end
end
Usage:
CoolProducts.call.including_in_name(string).fetch

How to query records with and without params in Rails?

I have model Places and I have the index method in a controller. I need to get all places via request
/places
And filter places via request with query
/places?tlat=xxxx&tlong=xxxx&blat=xxxxx&blong=xxxx
What the best way to get this records? Should I check an existence of each param or are there Rails way?
#places = if params[tlat]&&params[blat]....
Places.all.where("lat > ? AND long > ? AND lat < ? AND long < ?", tlat, tlong, blat, blong)
else
Places.all
If you want to set WHERE clauses depending on params, you can use Ursus' code which is fine.
However, if you need to apply those WHERE clauses only if a set of params are present, you can use the following:
#places = Place.all
if params[:blat].present? && params[:tlat].present?
#places = #places.where(blat: params[:blat], tlat: params[:tlat])
end
# etc.
You could use an array of arrays to pair the associated params, kind of like what Ursus did.
I'd do something like this if possible. Important to note the this is just one query, composed dynamically.
#places = Place.all
%i(tlat tlong blat blong).each do |field|
if params[field].present?
#places = #places.where(field => params[field])
end
end
IMO, truly the "Rails way" (but actually just the "Ruby way") would be to extract this long conditional, and the query itself, out to their own private method. It becomes much easier to understand what's going on in the index action
class MyController < ApplicationController
def index
#places = Place.all
apply_geo_scope if geo_params_present?
end
private
def geo_params_present?
!!(params[:tlat] && params[:blat] && params[:tlong] && params[:blong])
end
# A scope in the model would be better than defining this in the controller
def apply_geo_scope
%i(tlat tlong blat blong).each do |field|
#places = #places.where(field => params[field])
end
end
end

Ignore parameters that are null in active record Rails 4

I created a simple web form where users can enter some search criteria to look for venues e.g. a price range. When a user clicks "find" I use active record to query the database. This all works very well if all fields are filled in. Problems occur when one or more fields are left open and therefore have a value of null.
How can I work around this in my controller? Should I first check whether a value is null and create a query based on that? I can imagine I end up with many different queries and a lot of code. There must be a quicker way to achieve this?
Controller:
def search
#venues = Venue.where("price >= ? AND price <= ? AND romance = ? AND firstdate = ?", params[:minPrice], params[:maxPrice], params[:romance], params[:firstdate])
end
You may want to filter out all of the blank parameters that were sent with the request.
Here is a quick and DRY solution for filtering out blank values, triggers only one query of the database, and builds the where clause with Rails' ActiveRecord ORM.
This approach safeguards against SQL-injection, as pointed out by #DanBrooking. Rails 4.0+ provides "strong parameters." You should use the feature.
class VenuesController < ActiveRecord::Base
def search
# Pass a hash to your query
#venues = Venue.where(search_params)
end
private
def search_params
params.
# Optionally, whitelist your search parameters with permit
permit(:min_price, :max_price, :romance, :first_date).
# Delete any passed params that are nil or empty string
delete_if {|key, value| value.blank? }
end
end
I would recommend to make method in Venue
def self.find_by_price(min_price, max_price)
if min_price && max_price
where("price between ? and ?", min_price, max_price)
else
all
end
end
def self.find_by_romance(romance)
if romance
where("romance = ?", romance)
else
all
end
end
def self.find_by_firstdate(firstdate)
if firstdate
where("firstdate = ?", firstdate)
else
all
end
end
And use it in your controller
Venue
.find_by_price(params[:minPrice], params[:maxPrice])
.find_by_romance(params[:romance])
.find_by_firstdate(params[:firstdate])
Another solution to this problem, and I think a more elegant one, is using scopes with conditions.
You could do something like
class Venue < ActiveRecord::Base
scope :romance, ->(genre) { where("romance = ?", genre) if genre.present? }
end
You can then chain those, which would work as an AND if there is no argument present, then it is not part of the chain.
http://guides.rubyonrails.org/active_record_querying.html#scopes
Try below code, it will ignore parameters those are not present
conditions = []
conditions << "price >= '#{params[:minPrice]}'" if params[:minPrice].present?
conditions << "price <= '#{params[:maxPrice]}'" if params[:maxPrice].present?
conditions << "romance = '#{params[:romance]}'" if params[:romance].present?
conditions << "firstdate = '#{params[:firstdate]}'" if params[:firstdate].present?
#venues = Venue.where(conditions.join(" AND "))

How to add exception handling to my before_action

I have a before_action method like this:
def current_user
#current_user ||= User.find(:id => session[:id])
end
And I call a method like this:
def get_food user
food = Food.find(:id => user.id)
end
This is fine, but I want to add exception handling.
When the user is nil I want to use #current_user:
def get_food user
food = Food.find(if user is nil i want to use #current_user.id)
end
Of course, I can write it like this:
def get_food user
if user.nil?
food = Food.find(#current_user.id)
else
food = Food.find(user.id)
end
Or, is this the best way?
def get_food user
food = Food.find(user == nil? #current_user.id : user.id)
end
I'm curious is there a better way than adding a simple if statement inside the param?
The shortest one lines I can think of are something like this:
Food.find((user || current_user).id)
Food.find(user.try(:id) || current_user.id)
Food.find(user ? user.id : current_user.id)
Not sure if this is really an impovement in readability. I would prefer something like this:
def get_food(user)
user ||= current_user
Food.find(user.id)
end
You can use ternary operator to make it one line:
user ? Food.find(user.id) : Food.find(#current_user.id)
How about arrays
food = Food.where(id: [#current_user.try(:id),user.id]).first
You can try this:
food = Food.find(user.nil? ? #current_user.id : user.id)
What about default parameters?
def get_food(user = #current_user)
food = Food.find(user.id)
end
It will work if you call it without the parameter
something.get_food # notice the method is called with no params
If you want it working also if you pass nil, you should also add:
def get_food(user = #current_user)
food = Food.find((user || #current_user).id)
end
However is strange that foods and users have the same ids...
Maybe the correct query is:
food = Food.find_by_user_id((user || #current_user).id)
or, if users have more than just one food:
foods = Food.where(user: (user || #current_user)) # rails 4, :user => (user || #current_user) for rails 3
Food.find(user.id rescue #current_user.id)

Rails 3: Search method returns all models instead of specified

What I'm trying to do: I have a model "Recipe" in which I defined a method "search" that takes an array of strings from checkboxes (I call them tags), and a single string. The idea is to search the db for recipes that has anything in it's 'name' or 'instructions' that contains the string, AND also has any of the tags matching it's 'tags' property.
Problem: The search method return all the recipes in my db, and doesn't seem to work at all at finding by the specific parameters.
The action method in the controller:
def index
#recipes = Recipe.search(params[:search], params[:tag])
if !#recipes
#recipes = Recipe.all
end
respond_to do |format|
format.html
format.json { render json: #recipe }
end
end
The search method in my model:
def self.search(search, tags)
conditions = ""
search.present? do
# Condition 1: recipe.name OR instruction same as search?
conditions = "name LIKE ? OR instructions LIKE ?, '%#{search[0].strip}%', '%#{search[0].strip}%'"
# Condition 2: if tags included, any matching?
if !tags.empty?
tags.each do |tag|
conditions += "'AND tags LIKE ?', '%#{tag}%'"
end
end
end
# Hämtar och returnerar alla recipes där codition 1 och/eller 2 stämmer.
Recipe.find(:all, :conditions => [conditions]) unless conditions.length < 1
end
Any ideas why it return all records?
if you are using rails 3, then it is easy to chain find conditions
def self.search(string, tags)
klass = scoped
if string.present?
klass = klass.where('name LIKE ? OR instructions LIKE ?', "%#{string}%", "%#{string}%")
end
if tags.present?
tags.each do |tag|
klass = klass.where('tags LIKE ?', "%#{tag}%")
end
end
klass
end
When you do
search.present? do
...
end
The contents of that block are ignored - it's perfectly legal to pass a block to a function that doesn't expect one, however the block won't get called unless the functions decides to. As a result, none of your condition building code is executed. You probably meant
if search.present?
...
end
As jvnill points out, it is in general much nicer (and safer) to manipulate scopes than to build up SQL fragments by hand

Resources