Rails - how to select all records by ID range - sql

I am trying to do a query for all cities (selecting only their name attribute) by their ID, and I want to be able to specify a range of ID's to select. My code is below:
def list_cities(start, stop)
cities = City.all(order: 'name ASC', id: start..stop, select: 'name')
cities.map { |city| "<li> #{city.name} </li>" }.join.html_safe
end
However, I get an error:
Unknown key: id
My implementation in my view is:
<%= list_cities(1,22) %>
This is a helper method to be put in all views, so I am not putting the logic in a particular controller.
My schema for this model is:
create_table "cities", :force => true do |t|
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "neighborhoods"
t.string "name"
t.integer "neighborhood_id"
end
When I ran the method in my console, I got:
City Load (0.9ms) SELECT name FROM "cities" WHERE ("cities"."id" BETWEEN 1 AND 3) ORDER BY name ASC
=> ""
I know it's not an issue of having an empty database since it worked with the following version of the method:
def list_cities(start, stop)
cities = City.all(order: 'name ASC', limit: stop - start, select: 'name')
cities.map { |city| "<li> #{city.name} </li>" }.join.html_safe
end
However, this method returns only the first 'n' records and not a range like I want.
When trying a simpler query in the console:
1.9.3p385 :009 > City.where(:id => 1..4)
City Load (0.9ms) SELECT "cities".* FROM "cities" WHERE ("cities"."id" BETWEEN 1 AND 4)
=> []
I figured out why it was happening...
I did City.all in my console and realized that my cities started with id "946" because I had seeded multiple times and the ID's were not what I thought they were! The solution offered was correct!

City.where(:id => start..stop).order('name ASC').select(:name)

You can turn your query to the following:
cities = City.all(order: 'name ASC', conditions: { id: start..stop }, select: 'name')

Speransky Danil's answer should work perfectly. you can try this too:
City.find((start..stop).to_a,:select=>:name,:order=>'name ASC')

Related

How to search-query ActiveRecord Associations (.joins) with SQL ILIKE

I am trying to query multiple Active Record-Models by passing query-params to a Controller. Within my tales_controller.rb I have the following index-method:
def index
#tales_count = Tale.all.count
if params[:search]
#tales = Tale.joins(:category)
.where('category.title ILIKE ?', "%#{params[:search]}%")
.where(
'title ILIKE :search OR
subtitle ILIKE :search OR
short_description ILIKE :search', search: "%#{params[:search]}%"
)
else
#tales = Tale.all
end
render template: 'tales/index'
end
Now, I can't seem to figure out the correct solution to this problem, as for the most part PG throws an error, saying: PG::AmbiguousColumn: ERROR: column reference "title" is ambiguous. I sense that this is due to the fact that I try to query the title-field on the Tale-, as well as on the Category-Model. However I am not able to fix this problem myself.
By providing the index-method with the right queries I expect to be able to query a couple fields on the Tale-Model (namely title, subtitle and short_description and potentially more), as well as the title-field on the Category-Model.
The Category-Model is referenced by the Tale-Model. This is what the schema.rb looks like:
create_table "tales", force: :cascade do |t|
t.string "public_id"
t.string "title"
t.string "subtitle"
t.string "player_count"
t.string "short_description"
t.datetime "published_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "category_id"
t.bigint "author_id"
t.index ["author_id"], name: "index_tales_on_author_id"
t.index ["category_id"], name: "index_tales_on_category_id"
end
EDIT: uhm, I just realized that by querying the way I currently do, I expect the category.title AND any of the other Tale-fields to carry the search-term. This is not want I intended, frankly.
Table name in Rails are by convention in the plural. So change this to read
.where('categories.title ILIKE ?', "%#{params[:search]}%")
and just for fun ILIKE in Postgres can be written as
.where('categories.title ~* ?', params[:search])

getting the most recent row, grouped by an attribute's value

I'm working on a questionnaire for users to fill out. Users can answer each question many times. Responses have a question_key to pair them with their question. I want to get the full set of a user's current responses without retrieving every response created by a user.
The responses table looks like this:
create_table "responses", :force => true do |t|
t.string "question_key", :null => false
t.text "value", :null => false
t.integer "user_id", :null => false
t.datetime "created_at"
t.datetime "updated_at"
end
Here's the relevant part of the response class:
class Response < ActiveRecord::Base
def self.current
recent.uniq {|response| response.question_key}
end
scope :recent, order('created_at DESC')
end
user.responses.current gets the set of responses, but it seems really inefficient. It's getting all of the responses ever created by a user, and then throwing away everything but the most recent one for each unique value of question_key
I did figure out a SQL query that does what I want, ordering the rows before doing GROUP BY. Is there a reasonable way to use this in Response::current?
SELECT *
FROM (
SELECT *
FROM responses
WHERE user.id = 6
ORDER BY created_at DESC ) as user_responses
GROUP BY question_key
You can perform a group by in active record by using the group method.
Responses.where(user_id: '123').order('created_at DESC').group('question_key')
So your code would be something like:
class Response < ActiveRecord::Base
scope :recent, order('created_at DESC')
def self.current
recent.group('question_key')
end
end
This solution relies on the ids being sequential:
class Response < ActiveRecord::Base
def self.current
where(id: group(:question_key).maximum(:id).values)
end
end
It results in two queries when getting the current responses for a user (user.responses.current), but it avoids returning all of the user's responses.
SELECT MAX(`responses`.`id`) AS maximum_id, question_key AS question_key FROM `responses` WHERE `responses`.`user_id` = 6 GROUP BY question_key
SELECT `responses`.* FROM `responses` WHERE `responses`.`user_id` = 6 AND `responses`.`id` IN (452, 447, 9, 225, 190, 230, 240)

Rails3.2 undefined method

I have a Model called Status, its handling a table with two columns Stat and Colour.
Since these columns are also Model methods I would expect the following to work without an error
#a = Status.where(:stat => "Operational")
#a.colour = "Green"
However when I call #a.colour I receive an error stating that the method 'colour=' does not exist.
I am calling #a.colour from within seeds. This is just a model, it does not have a controller with it.
What am i doing wrong?
--Edit--
Model
class Status < ActiveRecord::Base
end
schema
create_table "statuses", :force => true do |t|
t.string "stat"
t.string "colour"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Is this what you requested? I did not fully understand the request,
Kind Regards
I suppose Status.where() returns more than one record. So you are trying to call the color= method on an array which obviously does not exist!
So you need to iterate trough all found records, using
Status.where(:stat => "Operational").each do |a|
a.colour = "Green"
end
For more information check the Rails ActiveRcord Query Interface guide, it tells you:
If you’d like to add conditions to your find, you could just specify them in there, just like Client.where("orders_count = '2'"). This will find all clients where the orders_count field’s value is 2.

How do I convert this raw SQL to Arel query?

In a Rails(3.2) app, I have a class method on a Model like this:
def import(level, max = 10)
db = ActiveRecord::Base.connection
result = db.execute("SELECT word FROM levels WHERE level == #{level} AND word NOT IN (SELECT entry FROM words) limit #{max};");
It just imports 10 new words(create 10 records) at a time that do not exist as Word record yet.
The schema looks something like this:
create_table "levels", :force => true do |t|
t.string "word"
t.integer "level"
end
create_table "words", :force => true do |t|
t.string "entry"
t.integer "level", :default => 0
t.text "definition"
t.string "thesaurus", :default => "none"
end
I'm an SQL noob. Messing with rails dbconsole(sqlite3, I'm using sqlite3 on a server as well), I somehow came up with the raw sql query above. I sort of know that I can do the same thing with Arel. How am I supposed to construct the query with ActiveRecord?
The following (untested) should work. It uses pluck in the subquery.
Level.where(:level => level).where("word NOT IN (?)", Word.pluck(:entry)).limit(max)
#Gazler's solution looks like it works, but I'll provide an alternative using MetaWhere syntax which is a bit more concise:
Level.where(:level => level, :word.not_in => Word.pluck(:entry)).limit(max)

Model.find(:all, :conditions ...) on one field for one key-value from a hash?

I have a table of email messages like so:
create_table :emails do |t|
t.string :emailMessageId
t.datetime :date
t.string :subject
t.string :gmailMessageId
t.string :gmailThreadId
t.string :from_hash, :default => nil
t.text :to_hash, :default => nil
t.text :cc_hash, :default => nil
t.integer :contact_id
The email.rb model file says:
class Email < ActiveRecord::Base
serialize :from_hash, Hash
serialize :to_hash, Array
serialize :cc_hash, Array
end
Imagine that
:to_hash = {"name" => "john", "email" => "john#test.com"}
or an array of hashes
:to_hash = [ {"name" => "john", "email" => "john#test.com"}, {"name" => "bob", "email" => "bob#example.com"} ]
As an example, here is Email.first
#<Email id: 1, emailMessageId: "357", date: "2011-10-03 00:39:00", subject: nil,
gmailMessageId: nil, gmailThreadId: nil, from_hash: {"name"=>"melanie",
"email"=>"mel#test.com"}, to_hash: [{"name"=>"michie", "email"=>"mich#blah.com"},
{"name"=>"clarisa", "email"=>"clarisa#123.com"}], cc_hash: [{"name"=>"john",
"email"=>"john#test.com"}, {"name"=>"alex", "email"=>"alex#massimo.com"}], contact_id: 1,
created_at: "2011-10-03 00:39:00", updated_at: "2011-10-03 00:39:00">
Further imagine that my database has thousands of such records, and I want to pull all records keyed on :to_hash["email"]. In other words, I want to be able to find all records in the Email model that contain the email "john#test.com" despite the fact that the email value is within an array of hashes. How do I do this?
I tried variations on:
hash = {"name" => "john", "email" => "john#test.com"}
Email.find(:all, :conditions => ["to_hash = ?", hash]) # returns the following error
ActiveRecord::StatementInvalid: SQLite3::SQLException: near ",": syntax error: SELECT "emails".* FROM "emails" WHERE (to_hash = '---
- name
- john
','---
- email
- john#test.com
')
I also tried:
email = "john#test.com"
Email.find(:all, :conditions => ["to_hash = ?", email])
# => [], which is not an error, but not what I want either!
And finally:
email = "john#test.com"
Email.find(:all, :conditions => ["to_hash['name'] = ?", email])
# which, as expected, gave me a syntax error...
ActiveRecord::StatementInvalid: SQLite3::SQLException: near "['name']": syntax error: SELECT
"emails".* FROM "emails" WHERE (to_hash['name'] = 'john#test.com')
The simple answer is;
if you need to query something, you shouldn't serialize it.
Saying that, I think the answer is just
Email.all(:conditions => ["to_hash LIKE '%email: ?%'", "john#test.com"])
If you look at the database contents this should satisfy you.
But going forward you should look for a better solution.
Serialization is great for storing structured data that you never need to use in a sql query,
but just gets in the way if you do.
If you really need this kind of freeform data structure, I suggest you look at using MongoDB and Mongoid.
However, within the usual Rails world, I'd suggest the following;
class Email
has_many :email_recipients
def to_hash
email_recipients.map do |recipient|
{"name" => recipient.name, "email" => recipient.email}
end
end
end
class EmailRecipient
# with columns
# email_id
# name
# email
belongs_to :email
end
One possible way to do this with just regular Ruby is to use the select method and let ActiveRecord take care of deserialization.
emale = "john#test.com"
Email.find(:all).select { |m| m[:to_hash]["email"] === emale }
Another possible solution is to serialize the search hash and match the serialized hash exactly how it is saved in the database. This requires that the hash has all attributes, not just the e-mail. Some useful links to the code that makes this happen available here. You'll see that ActiveRecord uses YAML for serialization by default, so something like this could work.
search_hash = {"name" => "john", "email" => "john#test.com"}
encoder = ActiveRecord::Coders::YAMLColumn.new(Hash)
search_string = encoder.dump(search_hash)
Email.find(:all, :conditions => ["to_hash = ?", search_string])