How do I convert this raw SQL to Arel query? - sql

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)

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])

Rails HABTM query -- Article with ALL tags

I created two tables in my app (Rails 3):
def change
create_table :articles do |t|
t.string :name
t.text :content
t.timestamps
end
create_table :tags do |t|
t.string :name
t.timestamps
end
create_table :articles_tags do |t|
t.belongs_to :article
t.belongs_to :tag
end
add_index :articles_tags, :article_id
add_index :articles_tags, :tag_id
end
I want to be able to search for articles based on tags in two ways:
Articles with ANY of the given tags (union)
Articles with ALL of the given tags (intersection)
So, in other words, something that allows me to do this this:
tag1 = Tag.create(name: 'tag1')
tag2 = Tag.create(name: 'tag2')
a = Article.create; a.tags << tag1
b = Article.create; b.tags += [tag1, tag2]
Article.tagged_with_any(['tag1', 'tag2'])
# => [a,b]
Article.tagged_with_all(['tag1', 'tag2'])
# => [b]
The first one was relatively easy. I just made this scope on Article:
scope :tagged_with_any, lambda { |tag_names|
joins(:tags).where('tags.name IN (?)', tag_names)
}
The problem is the second. I have no idea how to do this, in ActiveRecord or SQL.
I figure that I might be able to do something icky like this:
scope :tagged_with_all, lambda { |tag_names|
new_scope = self
# Want to allow for single string query args
Array(tag_names).each do |name|
new_scope = new_scope.tagged_with_any(name)
end
new_scope
}
but I'm betting that's crazy inefficient, and it just smells. Any ideas about how to do this correctly?
As you said, that scope is crazy inefficient (and ugly).
Try with something like this:
def self.tagged_with_all(tags)
joins(:tags).where('tags.name IN (?)', tags).group('article_id').having('count(*)=?', tags.count).select('article_id')
end
The key is in the having clause. You may also want to have a look at the SQL division operation between tables.

"Flexible"(?) Rails Database

I'm not sure if the title is clear. I'm not too sure what the kind of answer I'm looking for is called. I've been searching and searching and can't seem to find the answer.
Here's what I want to do:
I want a user to create a workout consisting of x sets, x reps, and x type.
I know I can do for example;
create_table "workouts", :force => true do |t|
t.string "workout name"
t.integer "sets"
t.integer "reps"
t.string "type"
end
But in this method, it only allows one type of set to be written/workout.
I want the ability to add multiple types of sets/workout.
So for example;
workout name: monday morning
sets: 2 reps: 4 type: "bicep curl"
sets: 4 reps 23 type: "bench press"
etc..
If my explanation is too unclear or if maybe I'm just confused please feel free to point out. All help is appreciated. Thanks for viewing!
You should create a table workouts and workout_details and link them together:
create_table "workouts", :force => true do |t|
t.string :name
end
create_table "workout_details", :force => true do |t|
t.references :workout
t.integer :sets
t.integer :reps
t.string :type
end
And your models would look something like this:
class Workout < ActiveRecord::Base
has_many :workout_details
end
class WorkoutDetail < ActiveRecord::Base
belongs_to :workout
end
If you set it up like this, you create several workout details:
bicep_curl = WorkoutDetail.new
bicep_curl.type = 'bicep curl'
bicep_curl.sets = 2
bicep_curl.reps = 4
bicep_curl.save
bench_press = WorkoutDetail.new
bench_press.type = 'bench press'
bench_press.sets = 4
bench_press.reps = 23
bench_press.save
and add them to a workout:
workout = Workout.new
workout.name = 'monday morning'
workout.workout_details << bicep_curl
workout.workout_details << bench_press
workout.save
To retrieve your workout, you can do this:
workout = Workout.where(:name => 'monday morning').first
puts "workout name: #{workout.name}"
workout.workout_details.each do |wd|
puts "sets: #{wd.sets} reps: #{wd.reps} type: #{wd.type}"
end
Output:
workout name: monday morning
sets: 2 reps: 4 type: bicep curl
sets: 4 reps: 23 type: bench press

Rails - how to select all records by ID range

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')

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.