Rails 4: How to include associated objects conditionally - ruby-on-rails

Here is what I am trying to do:
Product.first.reviews.includes(:comments).where('comments.reply_to_id=?', nil)
So basically I want to load the product reviews along with any associated comments.
Comments belong to the review, not on the product.
Here is the error:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'comments.reply_to_id' in 'where clause': SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`product_id` = 8 AND (comments.reply_to_id=NULL)
From schema.rb:
create_table "comments", force: :cascade do |t|
...
t.integer "reply_to_id", limit: 4
end

#pavan is right. But you can use Hash syntax, which finds it out automatically.
Product
.first
.reviews
.includes(:comments)
.where(comments: { reply_to_id: nil })

From the API
If you want to add conditions to your included models you’ll have to
explicitly reference
You need to include .references at the end of the query when you are using includes with conditions
Product.first.reviews.includes(:comments).where('comments.reply_to_id=?', nil).references(:comments)

Related

Rails 5.1: Foreign key mismatch for old rails schema

Context: On Rails 5.1 the default for Primary Keys changed from Int to BigInt (PR > https://github.com/rails/rails/pull/26266).
I am working on a new Rails 5.1 app which reuses some models from another Rails 4.0 app.
When I run the specs, I have a script that basically loads the Rails 4.0 app's schema.rb from its repo, to create a temp database, so I can use those "external" models (from that other DB) when running tests.
The issue is that given the other app created the schema.rb file on Rails 4.0, all Foreign Keys are integers.
The Rails 4.0 schema file looks like this:
create_table "companies", force: :cascade do |t|
t.string "name"
end
create_table "users", force: :cascade do |t|
t.string "name",
t.integer "company_id", limit: 4, null: false
end
add_foreign_key "users", "companies"
Therefore, when I run tests on the new Rails 5.1 app, it loads that other app schema (the one added above), but when creating the Companies table, it sets the Primary Key as BigInt instead of integer. But the Foreign Key is an integer.
That mismatch is messing up with MySQL:
ActiveRecord::MismatchedForeignKey: Column company_id on table
users has a type of int(11). This does not match column id on
companies, which has type bigint(20). To resolve this issue,
change the type of the company_id column on users to be :integer.
(For example t.integer company_id).
I know I could just change all Foreign Keys from that schema to be a BigInt. But I want to try avoiding that solution, given there are quite a lot of Foreign Keys, and in multiple DBs.
Any ideas on how to solve this issue? Or any thoughts?
You can specify an integer type for the primary key for the new table; see this answer. That would give you the opportunity to migrate to bigint primary keys without being rushed into it.
You can specify the type as integer, for example:
t.references :company, type: :integer, foreign_key: true, null: false

rails4 pluck with order and limit

In my sidebar I display the freshly created user profiles. Profile belongs_to user and user has_one_profile. I realized that I only use 3 columns from the profile table so it would be better to use pluck. I also have a link_to user_path(profile.user) in the partial, so somehow I have to tell who the user is. At the moment I'm using includes, but I don't need the whole user table. So I use to many columns both from the user and the profile tables.
How can I optimize this with pluck? I tried a few versions, but always got some error (most of the time profile.user is not defined).
My current code:
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3) if user_signed_in?
end
create_table "profiles", force: :cascade do |t|
t.integer "user_id", null: false
t.string "first_name", null: false
t.string "last_name", null: false
t.string "company", null: false
t.string "job_title", null: false
t.string "phone_number"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "avatar"
t.string "location"
end
Okay let's explain three different way to accomplish what you are looking for.
First of all there is a difference in includes and joins
Includes just eager load the association with all of the specified columns for associations. It does not allow you to query or select multiple columns from both table. It what joins do . It allow you to query both tables and select columns of your choice.
def set_sidebar_users
#profiles_sidebar = Profile.select("profiles.first_name,profiles.last_name,profiles.id,users.email as user_email,user_id").joins(:user).order("profile.created_at desc").limit(3) if user_signed_in?
end
It will return you the Profiles relation which has all of the columns you provided in select clause. You can get them just like you do for profile object e-g
#profiles_sidebar.first.user_email will give you user email for this profile.
This approach is best if you want to query on multiple tables or wanna select multiple columns from both table.
2.Pluck
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3).pluck("users.email,profiles.first_name") if user_signed_in?
end
Pluck is just used to get columns from multiple associations but it does not allow you to use the power of ActiveRecord. It simply returns you the array of selected columns in same order.
like in the first example you can get the user for profile object with #profiles_sidebar.first.user But with pluck you cannot because it's just a plain array. So that's why your most of the solutions raise error profile.user is not defined
Association with selected columns.
Now this is option three. In first solution you can get multiple columns on both tables and use the power of ActiveRecord but it does not eager load the associations. So it will still cost you N+1 queries if you loop through the association on returned result like #profiles_sidebar.map(&:user)
So if you wanna use includes but want to use selected columns then you should have new association with selected columns and call that association.
e-g
In profile.rb
belongs_to :user_with_selected_column,select: "users.email,users.id"
Now you can include it in above code
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user_with_selected_column).limit(3) if user_signed_in?
end
Now this will eager load users but will select only email and id of user.
More information can be found on
ActiveRecord includes. Specify included columns
UPDATE
As you asked about the pros for pluck so let's explain it.
As you know pluck returns you the plain array. So it does not instantiate ActiveRecord object it simply returns you the data returned from database.
So pluck is best to use where you don't need ActiveRecord Objects but just to show the returned data in tabular form.
Select returns you the relations so you can further query on it or call the model methods on it's instances.
So if we summaries it we can say
pluck for model values, select for model objects
More informations can be found at http://gavinmiller.io/2013/getting-to-know-pluck-and-select/

How to limit the number of INSERT records with Populator in Rails

I have a table I am trying to fill to eventually create a pretty network diagram. One of the tables is called User and the other is called relationships. I'd like to fill them out with about 10 entries each. The relationship is one user to many relationships.
The model I am using is
ActiveRecord::Schema.define(version: 20150603200530) do
create_table "relationships", force: true do |t|
t.integer "source"
t.integer "target"
t.integer "value"
end
create_table "users", force: true do |t|
t.string "name"
t.integer "group"
end
end
I am using the populate gem. It is very cool
The relationship table is related to the user table as relationship.source=user.id. I would like to generate randome values for 'source', 'target' and 'value' of between 1 and 10.
in a file called populate.rake (in lib/assets) so far I have:
namespace :db do
desc "Erase and fill database"
task :populate => :environment do
require 'populator'
require 'faker'
[User, Relationship].each(&:delete_all)
User.populate 100 do |user|
user.name = Populator.words(1..3).titleize
Relationship.populate 1..10 do |relationship|
relationship.source = 1..10
relationship.target = 1..10
relationship.value = 1..10
end
end
end
end
but I get the error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: too many terms in compound SELECT: INSERT INTO "relationships" ("id", "source", "target", "value") VALUES (1, 9, 10, 6).... etc.
This error seems to come from SQLite which has a limit of how many records you can insert with one INSERT statement.
You can try to reduce the number of records per INSERT by setting the populator option :per_query:
User.populate(100, :per_query => 10)

How can I delete duplicates based on two attributes?

My model is really simple:
create_table "stack_items", force: true do |t|
t.integer "stack_id"
t.integer "service_id"
t.text "description"
end
I need to remove duplicate StackItem records that have the same stack_id and service_id. However if one of the dupes has anything in the description field, I have to keep that one, and delete the other duplicate.
StackItem.group(:stack_id, :service_id).order("count_id desc").where("COUNT(*) > 1")
So far I've tried to just grab the duplicates but it's saying I cannot count within a where statement.
ActiveRecord::StatementInvalid: PG::GroupingError: ERROR: aggregate functions are not allowed in WHERE
How can I achieve this using Rails 4 and ActiveRecord? My database is Postgresql.

Can't add record to join table with composite unique index in Rails 3.2

IMPORTANT - READ EDIT BELOW FOR UPDATE ON THE ISSUE
I'm getting what I think is a bogus error when I try to add a new record to a join table with a unique composite key index on SQLite3. Note that for all (manual) tests I've done, the database has been completely rebuilt through db:drop followed by db:migrate.
The error:
ActiveRecord::RecordNotUnique
SQLite3::ConstraintException: columns adventurer_id, item_id are not unique:
INSERT INTO "adventurers_items" ("adventurer_id", "item_id") VALUES (1, 68)
The code that generates the error:
class Adventurer < ActiveRecord::Base
after_create :set_starting_skills
after_create :set_starting_items
has_and_belongs_to_many :items
has_and_belongs_to_many :skills
# automatically add starting skills on creation
def set_starting_skills
self.skills = self.profession.starting_skills
end
# automatically add starting items on creation
def set_starting_items
self.items = self.profession.items
end
The migration creating the join table adventurers_skills:
class AdventurersItems < ActiveRecord::Migration
def change
create_table :adventurers_items do |t|
t.integer :item_id, :null => false
t.integer :adventurer_id, :null => false
end
add_index :adventurers_items, :item_id
add_index :adventurers_items, :adventurer_id
add_index :adventurers_items, [:adventurer_id, :item_id], :unique => true
The table exists and is completely empty. Why is my application failing to insert this record due to the uniqueness constraint? I also have the same error with an equivalent table "adventurers_skills" -- am I doing something wrong architecturally?
EDIT
The system is trying to add the same item/skill twice. When I change the private method to this:
def set_starting_skills
skills = profession.starting_skills
end
It doesn't attempt to create anything in the join table. But reverting the first line to self.skills as below attempts to create the same skill TWICE
def set_starting_skills
self.skills = profession.starting_skills
end
returns
(0.4ms) INSERT INTO "adventurers_skills" ("adventurer_id", "skill_id") VALUES (4, 54)
(4.9ms) INSERT INTO "adventurers_skills" ("adventurer_id", "skill_id") VALUES (4, 54)
SQLite3::ConstraintException: columns adventurer_id, skill_id are not unique:
INSERT INTO "adventurers_skills" ("adventurer_id", "skill_id") VALUES (4, 54)
(3.2ms) rollback transaction
There is only one skill returned for profession.starting_skills:
1.9.3-p194 :022 > Profession.find(7).starting_skills.each {|x| puts x.id}
54
So the real question has become: why is Rails trying to add this HABTM record twice?
You need to put the callback declaration (after_create :set_starting_skills) after the relation declaration (has_and_belongs_to_many :skills).
i.e. The ordering of the lines in the model is significant else you get this bug.
This is madness, and there is a GitHub issue for it.

Resources