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