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}
Related
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")
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
I get this error.
undefined method 'recent' for #
My codes are
User controller
#users = User.find_by_username(params[:id]).all_following.recent
User model
scope :recent, lambda { |n = 10| order("last_active_at DESC").limit(n) }
if using lambda , should it be taking arguement ?? if you know exactly what the limit ,don't need to use lambda
try looking this guide on
13.2
http://guides.rubyonrails.org/active_record_querying.html#working-with-scopes
From the gem documentation all_following is an array. This array may even be non-homogeneous.
The recent scope is really a method that can be called on an ActiveRecord.
I'm afraid the two don't match up and that's why the error says you can't call recent on an instance of an Array.
I hope that helps.
I don't think this is possible using scope, because the all_following method will return an array, so it will not even look in the User model to try to find the scope. Another reason you should not use scope is that scope is for fetching items from the database in a particular way that you want to reuse, not sorting things. To get the functionality you want, I would add a method to the Array class like this:
class Array
def recent(limit = 10)
self.sort_by { |users| users[:last_active_at] }[0..(limit-1)]
end
end
Then you can call #users = User.find_by_username(params[:id]).all_following.recent(3) in your controller, and pass in any value you want for the limit value. If you leave off the limit value, such as with #users = User.find_by_username(params[:id]).all_following.recent, then it will use 10 as the default.
Can somebody explain what this method does and what I can pass to it?
scoped(options = nil)
Returns an anonymous scope.
And also what the scope method does? I don't understand after reading the documentation.
In ActiveRecord, all query building methods (like where, order, joins, limit and so forth) return a so called scope. Only when you call a kicker method like all or first the built-up query is executed and the results from the database are returned.
The scoped class method also returns a scope. The scope returned is by default empty meaning the result set would not be restricted in any way meaning all records would be returned if the query was executed.
You can use it to provide an "empty" alternative like in the query_by_date example by MurifoX.
Or you can use it to combine multiple conditions into one method call, like for example:
Model.scoped(:conditions => 'id < 100', :limit => 10, :order => 'title ASC')
# which would be equivalent to
Model.where('id < 100').limit(10).order('title ASC')
The scope class method allows you to define a class method that also returns a scope, like for example:
class Model
scope :colored, lambda {|col|
where(:color => col)
}
end
which can be used like this:
Model.colored
The nice thing with scopes is that you can combine them (almost) as you wish, so the following is absolutely possible:
Model.red.where('id < 100').order('title ASC').scoped(:limit => 10)
I also strongly suggest reading through http://guides.rubyonrails.org/active_record_querying.html
I have used it in the past.When you make chained calls to the ActiveRecord query interface like this:
Model.where(:conditions).where(:more_conditions).where(:final_conditions)
Each one of them is already scoped, making the chain work without any problems. But let's say you have something like this:
Model.query_by_date(date).query_by_user(user).query_by_status(status)
scope :query_by_date, lambda { |date|
case date
when "today"
where(:date => Date.today)
when "tomorrow"
where(:date => Date.tomorrow)
else
# Any value like '' or 0 or Date.whatever
end
}
This would cause an error if the date param is not today or tomorrow. It would pick the last value and try to chain this query with the next one query_by_user, resulting in a undefined method default_scoped? for ''. But if you put a scoped method in the else condition, it would work without any flaws, because you are saying to activerecord that you pass through this method/named scope and didn't make any calls to where/find/other activerecord methods, but returned a scoped object, so you can continue chaining queries and stuff.
It would be this way in the end.
else
scoped
end
Hope you understand this simple example.
So I'm trying to learn how to use memcache. I installed it in my system. I'm running it. I installed the dalli gem.
All that seems to be just fine.
Lets say I'd like to cache my users table.
I put this in my User.rb file:
def self.all_cached
Rails.cache.fetch('User.all') { all }
end
Then in my controller file, I used to have:
#users = User.where(:group_id => current_user.group_id)
So now I'd like to have something like:
#users = User.all_cached.where(:group_id => current_user.group_id)
I'm getting a no method error for where... Any ideas for how I should accomplish this?
Based on your comment there, I take it you are getting an error like:
undefined method `where' for #<Array:0x00000004d92520>
That's because where works on a model, but when you do User.all, it returns basically an array, and there is no where method defined for an array.
You may want to use the find_all method for enumerables (and arrays) instead (as seen here: http://www.ruby-doc.org/core/classes/Enumerable.html#M001484), or even try a different approach all together. That's your choice.
Here is the example they give to give you an idea off the bat of how it would work:
(1..10).find_all {|i| i % 3 == 0 } #=> [3, 6, 9]