I'm trying to use a belongs_to relationship inside a has_many as below.
In words: I want unique Reports that belong to a DownloadSchedule while being constrained by the client_id.
class DownloadSchedule < ActiveRecord::Base
serialize :custom_data
belongs_to :client
has_many :report_column_schedule_links
has_many :reports, -> { uniq where("report_column_schedule_links.client_id = ?", self.client.id) }, :through => :report_column_schedule_links
end
The error thrown is
Mysql2::Error: Unknown column 'report_column_schedule_links.client_id' in 'where clause': SELECT `reports`.* FROM `reports` WHERE (report_column_schedule_links.client_id = 1)
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'report_column_schedule_links.client_id' in 'where clause': SELECT `reports`.* FROM `reports` WHERE (report_column_schedule_links.client_id = 1)
Is this possible with a has_many or do I have to write a custom join? I'm using Rails 4.
[Update]
The structure for report_column_schedule_links is below.
create_table :report_column_schedule_links do |t|
t.integer :report_id
t.integer :report_column_id
t.integer :client_id
t.integer :schedule_id
t.integer :download_schedule_id
t.timestamps
end
You'll notice the MySQL error is on the statement
SELECT `reports`.* FROM `reports` WHERE (report_column_schedule_links.client_id = 1)
This statement isn't performing the join on the has_many.
Thanks,
Justin
Related
I have a Rails app with two models, joined by a third:
class Food < ApplicationRecord
has_many :shopping_list_items
has_many :users, through: :shopping_list_items
end
class ShoppingListItem < ApplicationRecord
belongs_to :user
belongs_to :food
end
class User < ApplicationRecord
has_many :shopping_list_items
has_many :foods, through: :shopping_list_items
end
The middle model, ShoppingListItem, has a few extra attributes, including priority which I'd like to update directly.
So for instance, I'd like to do something like:
r = current_user.shopping_list_items.where(food_id: 1).first
r.priority = "urgent"
r.save
The object looks fine until I try to save it, when I get a SQL error:
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: ...$1, "updated_at" = $2 WHERE "shopping_list_items"."" IS NULL
^
: UPDATE "shopping_list_items" SET "priority" = $1, "updated_at" = $2 WHERE "shopping_list_items"."" IS NULL):
I guess it's complaining about the absence of a primary key? Not sure how to fix this, since the rails docs say that join tables shouldn't have a primary key column...
I created the middle table with a migration like this, :
create_table :shopping_list_items, id: false do |t|
t.belongs_to :user
t.belongs_to :food
t.string :priority
t.integer :position
t.timestamps
end
I have to two models in the same namespace which have a habtm relation.
class Resource::Item < ApplicationRecord
has_and_belongs_to_many :resource_sets, foreign_key: 'resource_item_id', class_name: 'Resource::Set', table_name: 'resource_items_sets'
end
class Resource::Set < ApplicationRecord
has_and_belongs_to_many :resource_items, foreign_key: 'resource_set_id', class_name: 'Resource::Item', table_name: 'resource_items_sets'
end
The migration has been generated with rails g migration CreateJoinTableResourceItemsResourceSets resource_item resource_set
class CreateJoinTableResourceItemsResourceSets < ActiveRecord::Migration[5.2]
def change
create_join_table :resource_items, :resource_sets do |t|
# t.index [:resource_item_id, :resource_set_id]
# t.index [:resource_set_id, :resource_item_id]
end
end
end
So far everything looks great. The table resource_items_sets is being created with the two columns resource_item_id and resource_set_id.
This is the schema
create_table "resource_items_sets", id: false, force: :cascade do |t|
t.bigint "resource_item_id", null: false
t.bigint "resource_set_id", null: false
end
After creating an resource_item I get the following which is as expected.
pry(main)> Resource::Item.first.resource_sets
=> #<Resource::Set::ActiveRecord_Associations_CollectionProxy:0x3fdd08748004>
But doing the following throws an error. I was expecting 0.
pry(main)> Resource::Item.first.resource_sets.count
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column resource_items_sets.set_id does not exist
LINE 1: ...N "resource_items_sets" ON "resource_sets"."id" = "resource_...
^
: SELECT "resource_sets"."id" FROM "resource_sets" INNER JOIN "resource_items_sets" ON "resource_sets"."id" = "resource_items_sets"."set_id" WHERE "resource_items_sets"."resource_item_id" = $1 ORDER BY "resource_sets"."name" ASC
from /Users/username/.rvm/gems/ruby-2.6.0/gems/activerecord-5.2.2/lib/active_record/connection_adapters/postgresql_adapter.rb:677:in `async_prepare'
Caused by PG::UndefinedColumn: ERROR: column resource_items_sets.set_id does not exist
LINE 1: ...N "resource_items_sets" ON "resource_sets"."id" = "resource_...
Where does set_id come from when resource_set_id has been declare everywhere? How can I fix this issue? I want to keep the namespaces for both since I might end up creating items and set for more namespaces.
Thanks so much, guys!
You need to set the foreign keys on both sides of the join table because they can't be inferred in your case.
In this case the correct has_and_belongs_to_many calls should look like:
has_and_belongs_to_many :resource_items,
foreign_key: 'resource_set_id',
association_foreign_key: 'resource_item_id',
class_name: 'Resource::Item',
join_table: 'resource_items_sets'
end
and
class Resource::Item < ApplicationRecord
has_and_belongs_to_many :resource_sets,
foreign_key: 'resource_item_id',
association_foreign_key: 'resource_set_id',
class_name: 'Resource::Set',
join_table: 'resource_items_sets'
end
Note the added association_foreign_key option specifying the other foreign key in the join table.
Okay, setting aside the fact that asking how to "destroy orphans" on a chat board is just evil, here is my technical question:
I am making a simple blog-site, using Activerecord and Sinatra (and I am new to both). I have a "many to many" relationship between a Post model and a Tag model, and have been getting errors that I was not able to sort out. Finally, I arranged the models and migration so that I am not getting errors; however, when I destroy an instance of a tag, the the associations on the "posts_tags" table are left in place (existing as orphans), and I want to know how to destroy them (cue evil music).
Can anyone assist me with this?
Here are my migration for all three tables:
class CreatePostsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.string :title, default: 'title here'
t.string :content, default: 'content here'
t.belongs_to :blog, index: true
t.timestamps
end
end
end
class CreateTagTable < ActiveRecord::Migration[5.2]
def change
create_table :tags do |t|
t.string :name, unique: true
end
end
end
class CreatePostsTagsTable < ActiveRecord::Migration[5.2]
def change
create_table :posts_tags, :id => false do |t|
t.belongs_to :tag, index: true
t.belongs_to :post, index: true
# t.integer :post_id
# t.integer :tag_id
end
end
end
And here are my three models:
file-name: Post_Tag (I have named, and re-named this class and file-name over the course of a couple days).
class PostsTag < ActiveRecord::Base
# self.table_name = "posts_tags" #had to add this line for seed file not to give errors
belongs_to :post
belongs_to :tag
end
class Post < ActiveRecord::Base
belongs_to :blog
has_many :posts_tags
has_many :tags, :through => :posts_tags
has_many :comments, dependent: :destroy
end
class Tag < ActiveRecord::Base
has_many :posts_tags
has_many :posts, :through => :posts_tags
# has_and_belongs_to_many :posts, :through => :posts_tags
end
This configuration is not giving me error when I search for a post's tags, or a tag's posts, BUT when I do #tag = Tag.find_by_id(1) #tag.destroy the "posts_tags" table is left still having all of the tag_id == 1 rows still there. When I try to destroy them, I get errors (which I think is because that would also destroy the associations).
Does anyone know how to fix this? Is it okay to simply remove the PostsTag model all together?
The Activerecord documentation uses an example where the join-table is a single word, so I wasn't able to find the answer there.
I saw that some people were simply not making a model for the join-table. When I removed my join-table model, I got this error:
#post = Post.find_by_id(1)
#post.tags.each do |tag|
p tag.name
end
NameError at /
uninitialized constant Tag::PostsTag
Thanks for any help!
#
Phase Two of question:
After the tip that I could try adding this code to my models:
class Post < ActiveRecord::Base
has_many :posts_tags, dependent: :destroy
class Tag < ActiveRecord::Base
I am running this in my server/app file:
delete "/tag/destroy" do
body = JSON.parse request.body.read # body: {id: number} or {name: string}
#tag_to_destroy = Tag.where(body)[0]
#tag_to_destroy.destroy
redirect "/tag"
end
I am sending this request via postman:
http://localhost:4567/tag/destroy
body of request:
{
"id": 12
}
And I am getting this error:
(even though there is are rows in the posts_tags database with tag_id = 12, post_id = variousNumbers):
== Sinatra (v2.0.1) has taken the stage on 4567 for development with backup from Puma
Puma starting in single mode...
* Version 3.11.4 (ruby 2.5.1-p57), codename: Love Song
* Min threads: 0, max threads: 16
* Environment: development* Listening on tcp://localhost:4567
Use Ctrl-C to stop
D, [2018-05-07T10:54:19.604906 #99099] DEBUG -- : Tag Load (0.5ms) SELECT "tags".* FROM "tags" WHERE "t
ags"."id" = $1 [["id", 12]]
D, [2018-05-07T10:54:19.617955 #99099] DEBUG -- : (0.3ms) BEGIN
D, [2018-05-07T10:54:19.633736 #99099] DEBUG -- : PostsTag Load (1.5ms) SELECT "posts_tags".* FROM "pos
ts_tags" WHERE "posts_tags"."tag_id" = $1 [["tag_id", 12]]
D, [2018-05-07T10:54:19.642305 #99099] DEBUG -- : PostsTag Destroy (1.6ms) DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
D, [2018-05-07T10:54:19.642685 #99099] DEBUG -- : (0.2ms) ROLLBACK
2018-05-07 10:54:19 - ActiveRecord::StatementInvalid - PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL
^
: DELETE FROM "posts_tags" WHERE "posts_tags"."" IS NULL:
/Users/maiya/.rvm/gems/ruby-2.5.1/gems/activerecord-5.2.0/lib/active_record/connection_adapters/postgresql_adapter.rb:603:in `async_exec'
Question update:
The join-table migration was previously:
create_table :posts_tags :id => false do |t|
t.belongs_to :tag, index: true
# comment: the above line creates: t.integer :post_id
t.belongs_to :post, index: true
# comment: the above line creates: t.integer :tag_id
end
All is OK now with your migrations and models, you just need to change in the Post and Tag models:
has_many :posts_tags
to
has_many :posts_tags, dependent: :destroy
In this case after you destroy any tag or post all associated posts_tags are destroyed too, it will ensure referential integrity of your app and prevent errors.
Also, it is good idea to rollback migrations, add foreign_key: true option to t.belongs_to lines, and migrate it again. This option will provide referential integrity on db level.
UPDATE:
has_and_belongs_to_many (HABTM) doesn't work with through option, you mixes it up with has_many association. Please, read about it more in guides
To make it all working you need to:
remove :id => false option from CreatePostsTagsTable migration
remigrate or drop and recreate db
change has_and_belongs_to_many <...>, :through => :posts_tags to has_many <...>, :through => :posts_tags in both Tag and Post models
P.S. By convention all file names in ruby are in lower snake case, it should be `posts_tag.rb', not 'Posts_Tag.rb'
I have
class Job < ApplicationRecord
has_one :user, through: :jobs_user
has_one :jobs_user, dependent: :destroy
end
and the model for the join_table looks like this:
class JobsUser < ApplicationRecord
belongs_to :job
belongs_to :user
end
The migration was:
create_join_table :jobs, :shops do |t|
t.index :job_id
end
When I create a job and try to delete it fails :
j = Job.create(user: User.last)
j.destroy!
Job Load (0.3ms) SELECT "jobs".* FROM "jobs" ORDER BY "jobs"."id" DESC LIMIT 1
(0.2ms) BEGIN
JobsShop Load (0.3ms) SELECT "jobs_shops".* FROM "jobs_shops" WHERE "jobs_shops"."job_id" = 21365 LIMIT 1 [["job_id", 21365]]
SQL (0.7ms) DELETE FROM "jobs_shops" WHERE "jobs_shops"."" = NULL [[nil, nil]]
(0.2ms) ROLLBACK
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: DELETE FROM "jobs_shops" WHERE "jobs_shops"."" = NULL
^
: DELETE FROM "jobs_shops" WHERE "jobs_shops"."" = NULL
It seems I failed somewhere and it cannot find the column to destroy.
The answer can be found here : https://github.com/rails/rails/issues/25347#issuecomment-300067025
Active Record doesn't have built in support for composite primary keys
That means you can't manipulate a model whose corresponding table doesn't have a single-column primary key defined. That includes doing so through an association that uses said model.
So in my case, choosing create_join_table was not the right choice. Instead create a normal table.
create_table :users_jobs do |t|
t.integer :user_id
t.integer :job_id
# t.index :job_id
end
I'm not sure what's going on here. I have a scope I'm trying to create that works with my association:
class Subscription < ActiveRecord::Base
belongs_to :subscriber, :class_name => "User"
belongs_to :subscribable, :polymorphic => true
end
create_table :products do |t|
t.string :name
t.decimal :price
t.decimal :cost_per_unit
t.integer :user_id
end
create_table :subscriptions do |t|
t.string :name
t.decimal :price
t.decimal :cost_per_unit
t.integer :subscriber_id
t.integer :subscribable_id
t.string :subscribable_type
end
class Product < ActiveRecord::Base
has_many :subscriptions, :as => :subscribable, :dependent => :destroy
def self.lower_prices
Product.includes(:subscriptions).
where("products.price < subscriptions.price OR products.cost_per_unit < subscriptions.cost_per_unit" )
end
end
I'm trying to compare the lower price of the Product to the Subscription but this gives me the error:
ActiveRecord::StatementInvalid in Pages#subscribed_products
PGError: ERROR: missing FROM-clause entry for table "subscriptions"
LINE 1: ... WHERE (user_id != 2) AND (products.price < subscripti...
^
: SELECT COUNT(*) FROM "products" WHERE (user_id != 2) AND (products.price < subscriptions.price OR products.cost_per_unit < subscriptions.cost_per_unit)
What's wrong here?
The includes method doesn't do exactly what you think. Substitute joins for includes and it should Do What You Mean:
Product.joins(:subscriptions).
where("products.price < subscriptions.price OR products.cost_per_unit < subscriptions.cost_per_unit" )
or perhaps:
Product.includes(:subscriptions).joins(:subscriptions).
where("products.price < subscriptions.price OR products.cost_per_unit < subscriptions.cost_per_unit" )
joins translates to a JOIN in the resulting SQL query, so you can perform WHERE clauses on the joined table. include just asks Active Record to perform another query to select all the related records in the given table. If you do both together, Active Record creates a (rather long) all-in-one that both joins the two tables and uses the results to create both sets of objects.