"Flexible"(?) Rails Database - sql

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

Related

How to add an AND condition to SQL query

I am trying to add an AND condition where active = true is also incorporated within an SQL query inside of rails.
Schema.rb
...
create_table 'relationships', force: :cascade do |t|
t.integer 'follower_id'
t.integer 'followed_id'
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.boolean 'active', default: true
t.index ['followed_id'], name: 'index_relationships_on_followed_id'
t.index %w[follower_id followed_id], name: 'index_relationships_on_follower_id_and_followed_id', unique: true
t.index ['follower_id'], name: 'index_relationships_on_follower_id'
end
...
user.rb
...
def feed
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id AND active = true"
View.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id).includes(:user)
end
...
However the addition of AND active = TRUE does not seem to work to pull followed_ids with follower_id as self and where the active boolean is true.
Managed to solve it by assigning an active within the query.
...
def feed
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id AND active = :active"
View.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id, active: true).includes(:user)
end
...

How to search my DB for specific m to n relations saved in a jointable

I am using a postgreSQL DB on my Rails app. I have established m-to-n relationships between Slots and Hashtags. The last thing I need is to find Slots who match specific Hashtags given per url params
Here is the schema.rb
create_table "hashtags", force: :cascade do |t|
t.string "value"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "hashtags_slots", id: false, force: :cascade do |t|
t.bigint "hashtag_id", null: false
t.bigint "slot_id", null: false
t.index ["hashtag_id"], name: "index_hashtags_slots_on_hashtag_id"
t.index ["slot_id"], name: "index_hashtags_slots_on_slot_id"
end
create_table "slots", force: :cascade do |t|
t.string "slot_name"
t.string "file_path"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["slot_name"], name: "index_slots_on_slot_name"
end
I want to show the Index of the slots filled with slots who have one of the hashtags that is given through the params[:hashtags]. The value of Hashtags can contain 1 or more Strings like so:
hashtags = "NetEnt PlayNGo Novomatic"
This is inside the index action of the Slot controller
if(params[:slot_name])
#slots = Slot.where(slot_name: params[:slot_name]).paginate(page: params[:page])
elsif(params[:hashtags])
#slots = slotsWithAtLeastOneOfThose(params[:hashtags])
else
#slots = Slot.all.paginate(page: params[:page])
end
How should a method
slotsWithAtLeastOneOfThose(hashtags)
slots = ...
return slots
end
look like to give me all the Slotsthat have at least one hashtag in the hashtags variable
Consider those relations:
Slot table:
id: 1 slot_name: Book of Dead
id: 2 slot_name: Big Win Cat
id: 3 slot_name: Big Bad Wolf
Hashtag table:
id: 1 value: PlayNGo
id: 2 value: Fun
id: 3 value: NetEnt
id: 4 value: MicroGaming
id: 5 value: NotFun
Hashtags_Slots table would look like this:
slot_id hashtag_id
1 1
1 2
2 2
2 3
2 5
3 4
3 5
And now the method
slotsWithAtLeastOneOfThose("PlayNGo NetEnt")
should give me all the Slots with the Hashtag PlayNGo and NetEnt
In this case
id: 1 slot_name: Book of Dead
id: 2 slot_name: Big Win Cat
Please check below query for getting slots with particular hash tag value
if(params[:slot_name])
#slots = Slot.where(slot_name: params[:slot_name]).paginate(page: params[:page])
elsif(params[:hashtags])
#slots = Slot.joins(:hashtags).where("hashtags.value LIKE ?", "%#{params[:hashtags]}%")
else
#slots = Slot.all.paginate(page: params[:page])
end

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.

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

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)