Build search query from array in Rails - ruby-on-rails

I have a model Person and a datatable PersonCode. Now in my controller, I want to make a search for multiple codes. What would be the right way to do this?
I tried to define a class method on Person
def self.code_filter(codes)
joins(:PersonCode)
codes.each do |code|
where("rank > 1 AND person_code.code LIKE '%" + code +"%'")
end
But when I call this class method in my controller with, say ['Z','Q']
Person
.code_filter(my_array)
.another_query_method(some_value)
I get the the following error message: "undefined method `another_query_method' for ["Z", "Q"]:Array"
I kinda see why this isn't working but how can I make a correctly concatenated query with these SQL statements?

Because of each method.
This method returns the original Array object.
[1,2,3].each {|i| p i + 1}
returns [1,2,3].
Use Array#map and you'll get an array of relations

Related

Append an ActiveRecord::Relation object with single ActiveRecord object

I need to append a single ActiveRecord object onto a loaded ActiveRecord::Relation.
E.g:
class BooksController < ApplicationController
def index
#books = Book.where(author: User.find(params[:user_id]))
#books << Book.find_by_name("One More Book")
end
end
As shown above, I have tried ActiveRecord's << method, but it returns the error:
NoMethodError: undefined method `<<' for #<Book::ActiveRecord_Relation:0x007f9fbdc6db80>
Is there an elegant way of doing this?
Thanks!
As a temporary fix, I have done the following:
books = Book.where(author: User.find(params[:user_id])).pluck(:id)
books << Book.find_by_name("One More Book").id
#books = Book.where(id: books)
For obvious reasons, this is solution is far from ideal, but it does work while a better solution can be found.
You can use the or method introduced in Rails 5, although this only works with where (which returns an ActiveRecord::Relation object) and not with find_by_name (as it returns a Book object).
Book.where(author_id: params[:user_id])).or(Book.where(name: "One More Book"))
If the relationship is well defined this should also work:
Book.where(author: params[:user_id])).or(Book.where(name: "One More Book"))
Note that where could return more than one object. Using where + limit in an or is also not allowed.
If you want to append a single object, you can do it by plucking the ids, but this raises 3 queries instead of 1:
users_array = User.where(author: params[:user_id]) | [Book.find_by_name("One More Book")]
Book.where(id: users_array)
Alternatively, depending what you are doing afterwards, having the result in an array may be enough:
Book.where(author_id: params[:user_id])) | [Book.find_by_name("One More Book")]
Note the use of | (union), as I assume you don't want duplicates. Otherwise you can use +, but this only makes a different for the array and not for an ActiveRecord::Relation (there are no duplicates).
Since this is rails 5 you can use the or method
Books.where(author: User.find(params[:user_id])).or.where(name: "One More Book")

How to return only one object in "where" statement with Rails?

While creating a Rails app I have a common problem of finding a unique object in database that matches some conditions. And if there is more than one result, some kind of error needs to be triggered.
I do it like this right now:
results = ModelName.where(attr1: condition1, attr2: condition2)
raise "too many ModelName objects for condition" if results.count > 1
unique_result = results.first
But it seems too verbose for such a common task. It would be nice to only have to write something like this:
unique_result = ModelName.unique_where(attr1: condition1, attr2: condition2)
Is there a method that returns the record if it is unique or raises an exception if more than one record is found without manually extending ActiveRecord?
It seems like there's no such built-in method, so I created an ActiveRecord::Relation extension for this purpose:
module ActiveRecordRelationExtension
extend ActiveSupport::Concern
def single_object!
if self.many?
raise "More than one instance of #{self.klass.name} returned"
end
return self.first
end
end

Nice array from pluck

I have a model and I love the pluck method I can use. If I do this:
#x = AwesomeModel.all.pluck(:column_one, :column_two)
then I get a multidimensional array: #x[][]. With my sad skills, I work with them using the numbers:
#x[0][1]
how can I can use pluck or a similar method to access the array something like this:
#x[0][:column_two]
If you are concerned about the structure of what you get back from the db, you should simply do:
#x = AwesomeModel.all.select(:column_one, :column_two)
Then you'd keep the fast db query advantage + have AwesomeModel instances, but with only column_one and column_two filled
Or if you desire to do it manually:
#x = AwesomeModel.all.pluck(:column_one, :column_two).map do |array|
OpenStruct.new({column_one: array[0], column_two: array[1] }) }
end
Then you can use it like a regular model:
#x[0].column_one
# or even
#x[0][:column_two]
You could do
class ActiveRecord::Base
def self.pluck_hash(*args)
plucked = pluck(*args)
plucked.map {|ary| Hash[args.zip ary]}
end
end
AwesomeModel.all.pluck_hash(:column_one, :column_two)
#=> [{:column_one => 'value', :column_two => 'value}, {...}, ... ]
First of all, don't use .all.pluck, because it returns an array of values, and that makes you loose all the advantages of ActiveRecord::Relation.
Instead use AwsomeModel.method directly, it would create the query but not run it until you need it, AwsomeModel.select(:column_1, :column_2) would create a
select (awesome_models.column_1, awsome_models.column_2)
query, and the result would be an array of ActiveRecord::Relation objects, which are still chainable, and values are still under keys of the column name eg:
AwsomeModel.select(:column_1, :column_2).first.column_1
Instead of
AwesomeModel.all.pluck(:column_1, :column_2).first[0] # or .first.first

Rails 3 - Undefined method on certain collections

I have a search method written for my model Link.
I've been able to called this method without error until implementing voting. For example, these all work:
Link.search(params[:search])
current_user.links.search(params[:search])
current_account.links.search(params[:search])
The following does not work:
#links = current_user.votes.collect {|vote| vote.voteable}
#favorites = #links.search(params[:search])
and return this error:
undefined method `search' for #<Array:0x00000006919ac8>
I've done some testing, to see if my class is wrong, in the console:
links = user.votes.map {|vote| vote.voteable}
links.class
=> Array
links.first.class
=> Link
This should be no different than my working examples:
user.links.class
=> Array
user.links.first.class
=> Link
I thought maybe the error was from me calling search on an array and not a link. But in previous examples I'm also calling it on an array.
I'm using vote_fu to handle the voting thus the vote/voteable.
The search function or scope that you have defined is defined on the Link object and is usable in Link relations, but it is not defined on a simple array, which is what is getting returned from the first collect example. Here is a simple distinction:
class User
scope :search, lambda{ |name| where(name: name) }
end
User.search('Kombo').all # Returns an array of the SQL result run against the DB
User.all.search('Kombo') # NoMethodError: undefined method `search' for #<Array:0x000001079b15b0>
In your first example, Link.search(params[:search]), you are performing the equivalent of User.search.all, and User is a scoped ActiveRecord relation/object, which means it can continue to be combined with other scopes, like where, limit and group. In the second example, #links = current_user.votes.collect {|vote| vote.voteable}, collect is acting on such a relation and is returning a simple array which can no longer be acted upon with these scoped functions. The second example is like doing User.all.search.
It's confusing because both of these examples resolve to an Array eventually, but the difference is what is happening before that resolution to an Array, and when you are actually calling the search function. To get around this you'll have to actually call the search scope or function on an ActiveRecord object, like Link or an ActiveRecord Relation like current_user.links, but you won't be able to call it on a result. Just to clarify:
Link.search(params[:search]) # will work
Link.all.search(params[:search]) # will not work
current_user.links.search(params[:search]) # will work
current_user.links.all.search(params[:search]) # will not work
current_account.links.search(params[:search]) # will work
current_account.links.all.search(params[:search]) # will not work
When you call .collect you are implicitly calling .all, which breaks the scope chain. The following two commands are equivalent in that respect:
#links = current_user.votes.collect {|vote| vote.voteable}
#links = current_user.votes.all.collect {|vote| vote.voteable}

How to sort an array of objects by an attribute of the objects?

I'm performing this query in a controler and the 'Group' model has_many Users
#group= Group.find(params[:id])
#group is being used to render this partial (the partial dumps the users of a group into a table)
<%= render :partial=>"user_list", :locals=>{:users=>#group.users} %>
The local variable 'users' passed to the partial is an array of User objects;
- !ruby/object:User
attributes:
updated_at: 2011-01-04 21:12:04
firstname: Bob
lastname: Smith
id: "15"
group_id: "2"
created_at: 2010-11-26 12:54:45
How can the user array be sorted by 'lastname'? I've tried several different ways without any luck. Trying to sort by a object attribute inside an array in confusing me. Also, I don't undertand how I could do this with an :order in the query (how to :order not the Group but the Users of each group)?
Maybe I'm not referring to name of the object correctly ('User')? It seems like this should work be it produces a 'no method' error (or a dynamic constant assignment error if 'sort_by' is used without the !):
users.sort_by! {|User| User.lastname}
Thanks for any help.
I found a method that works from here. I don't understand what the "&" symbol is doing - maybe it's shorthad for "object" since in my case ":lastname" is an attribute of the object that make up the array.
users = users.sort_by &:lastname
note: I don't undertand why but a destructive version won't work. It produces a "undefined method `sort_by!' for #" errror:
users.sort_by! &:lastname
See http://ariejan.net/2007/01/28/ruby-sort-an-array-of-objects-by-an-attribute/
#users.sort! { |a,b| a.name.downcase <=> b.name.downcase }
It's probably because you've got |User| when User is a class. Try changing it to an un-used variable name, like u, to get:
users.sort_by! {|u| u.lastname}
The term between the pipes is the name of the variable each item is stored in, as it runs through, not the type of object you've got. This is the same as other Ruby blocks, like do || end, or any other {||}-style block.
You can try putting an order clause in the "has_many :users" line that you have in your Group model

Resources