collection_singular_ids in case of a has_many trough with source - ruby-on-rails

I just have found a strange behavior of Active Record, where I am not sure if I am the problem or Active Record ;-).
I am using: Rails 4, Ruby 2.0
Here the models:
class User < ActiveRecord::Base
has_many :conversation_participants, dependent: :destroy
has_many :conversations, through: :conversation_participants
end
class ConversationParticipant < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
end
class Conversation < ActiveRecord::Base
has_many :conversation_participants, dependent: :destroy
has_many :participants, through: :conversation_participants, source: :user
end
Pretty straight forward besides the has_many :participants, which refers to user over source:.
Normally, it is possible to call collection_singular_ids on an has_many associated object. Like in this case:
User.find(1).conversation_ids
This works perfectly, I get back an array of conversations ids. But from the other side:
Conversation.find(1).participant_ids
I alway getting back nil. When I go back to the standard for the has_many:
class Conversation < ActiveRecord::Base
has_many :conversation_participants, dependent: :destroy
has_many :users, through: :conversation_participants
end
Conversation.find(1).user_ids
will work fine.
When I look into the resulting queries, I see what the reason could be:
has_many :users creates:
SELECT "users".id FROM "users" INNER JOIN "conversation_participants" ON "users"."id" = "conversation_participants"."user_id" WHERE "conversation_participants"."conversation_id" = $1 [["conversation_id", 29]]
has_many :participants creates:
SELECT "users".* FROM "users" INNER JOIN "conversation_participants" ON "users"."id" = "conversation_participants"."user_id" WHERE "conversation_participants"."conversation_id" = $1 [["conversation_id", 29]]
in the participants case I get a SELECT "users".* instead SELECT "users".id
(using class_name: "User" has also not helped)
Bug or human error?

Related

Dependant destroy generates wrong sql query

I have m:n association with counter cache. I want delete user and remove appropriate records from the users_items association. The sql query which should remove records from users_items association is wrong.
class User < ApplicationRecord
has_many :users_items, class_name: 'UsersItem', dependent: :destroy
has_many :items, through: :users_items
end
class Item < ApplicationRecord
has_many :users_items, class_name: 'UsersItem', dependent: :destroy
has_many :users, through: :users_items
end
class UsersItem < ApplicationRecord
belongs_to :user, counter_cache: true
belongs_to :item, counter_cache: true
end
u = User.last
u.destroy
(0.3ms) BEGIN
UsersItem Load (0.4ms) SELECT "users_items".* FROM "users_items" WHERE "users_items"."user_id" = $1 [["user_id", 41]]
UsersItem Destroy (1.9ms) DELETE FROM "users_items" WHERE "users_items"."" IS NULL
(0.3ms) ROLLBACK
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: zero-length delimited identifier at or near """")
LINE 1: DELETE FROM "users_items" WHERE "users_items"."" IS NULL
The problem was with the users_items table. The table was created without primary key. It started working when I added the primary id.
Myth

How to achieve has many through relation between users

I am building matching system between users, originally it was has and belongs to many association, but due to some validations and additional data i need for that join table, i need to have model for that one, and change it to has many through.
has_and_belongs_to_many(:likes,
class_name: :User,
join_table: :user_likes,
foreign_key: :user_liker_id,
association_foreign_key: :user_liked_id)
has_and_belongs_to_many(:likeds,
class_name: :User,
join_table: :user_likes,
foreign_key: :user_liked_id,
association_foreign_key: :user_liker_id)
This is old implementation which needs to be converted to has_many through. As you can see, with this one i can retrieve all users which i liked and users which liked me. I have created UserLike model but i don't have any success with making it workable inside user model. What i already tried is following:
user.rb
has_many :user_likes
has_many :likes, through: :user_likes, foreign_key: :user_liked_id
has_many :likeds, through: :user_likes, foreign_key: :user_liker_id
user_like.rb
class UserLike < ApplicationRecord
belongs_to :user
end
This implementation raises following exception:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) "like" or :likes in model UserLike. Try 'has_many :likes, :through => :user_likes, :source => '. Is it one of user or user_likes?
I don't know what i am missing here, any help would be appreciated :)
When creating a join table that joins the same table twice you need two separate associations since the user can be in either foreign key:
class User < ApplicationRecord
has_many :likes_as_liker,
class_name: 'Like',
foreign_key: 'liker_id'
has_many :likes_as_liked,
class_name: 'Like',
foreign_key: 'liked_id'
end
class Like < ApplicationRecord
belongs_to :liker, class_name: 'User'
belongs_to :liked, class_name: 'User'
end
With our two has_many associations done we can start creating indirect associations:
class User < ApplicationRecord
has_many :likes_as_liker,
class_name: 'Like',
foreign_key: 'liker_id'
has_many :likes_as_liked,
class_name: 'Like',
foreign_key: 'liked_id'
has_many :likers,
through: :likes_as_liked
has_many :liked_users,
through: :likes_as_liker,
source: :liked
end
Which works perfectly:
irb(main):002:0> u.likers
User Load (2.8ms) SELECT "users".* FROM "users" INNER JOIN "likes" ON "users"."id" = "likes"."liker_id" WHERE "likes"."liked_id" = $1 LIMIT $2 [["liked_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
irb(main):003:0> u.liked_users
User Load (1.3ms) SELECT "users".* FROM "users" INNER JOIN "likes" ON "users"."id" = "likes"."liked_id" WHERE "likes"."liker_id" = $1 LIMIT $2 [["liker_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy []>
As per the description mentioned in the post you are trying to find the users which have liked my post and the users whom I have liked.
Below mentioned code does not tell rails that likes and likeds are actually users.
has_many :user_likes
has_many :likes, through: :user_likes, foreign_key: :user_liked_id
has_many :likeds, through: :user_likes, foreign_key: :user_liker_id
Modify it to below
has_many :user_likes
has_many :likes, through: :user_likes, foreign_key: :user_liked_id, source: :liked
has_many :likeds, through: :user_likes, foreign_key: :user_liker_id, source: :liked
Best Approach is specifying class along with class name so that rails know in which class is mapped to this foreign key when specifying customized relation names
When your association name is different than the name used at the :through you have to define the source parameter. If you look at the exception message it explicitly asks you to do it.
Hope it answers the question.

Audited: Associated audits with HABTM relation

I've got model user and role
class User < ApplicationRecord
rolify strict: true
has_many :roles, through: :users_roles
has_associated_audits
class Role < ApplicationRecord
has_and_belongs_to_many :users, join_table: :users_roles
audited associated_with: :users, join_table: :users_roles
When I create a new role, I've got the error:
2.4.4 :373 > User.first.add_role Role.pi, ProjectRequest.find(319)
User Load (0.7ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
ProjectRequest Load (0.6ms) SELECT `project_requests`.* FROM `project_requests` WHERE `project_requests`.`id` = 319 LIMIT 1
Role Load (0.6ms) SELECT `roles`.* FROM `roles` WHERE `roles`.`name` = 'pi' AND `roles`.`resource_type` = 'ProjectRequest' AND `roles`.`resource_id` = 319 ORDER BY `roles`.`id` ASC LIMIT 1
(0.2ms) BEGIN
SQL (0.6ms) INSERT INTO `roles` (`name`, `resource_type`, `resource_id`, `created_at`, `updated_at`) VALUES ('pi', 'ProjectRequest', 319, '2018-06-19 11:40:13', '2018-06-19 11:40:13')
(54.3ms) ROLLBACK
NoMethodError: undefined method `primary_key' for User::ActiveRecord_Associations_CollectknowProxy:Class
I don't really now whats the problem, did I wrongly specified something?
Look like there is issue with association if it is has_and_belongs_to_many :users and join_table is users_roles on that case in roles table also it should be has_and_belongs_to_many :roles, join_table: :users_roles, and this will make HABM relationship.
I've resolved the problem by this tutorial:
http://blog.flatironschool.com/why-you-dont-need-has-and-belongs-to-many/
What I did was that I removed the HABTM relation and created model for the joining table.
class User < ApplicationRecord
rolify strict: true
has_many :users_roles
has_many :roles, through: :users_roles, dependent: :destroy
has_associated_audits
class Role < ApplicationRecord
has_many :users_roles
has_many :users, through: :users_roles, dependent: :destroy
audited associated_with: :users
class UsersRole < ApplicationRecord
# audited associated_with: :role
audited associated_with: :user
belongs_to :user
belongs_to :role
end
Now when the audits are created with change at the UsersRole instance.
The problem is that, the change's contains only the ids of destroyed columns, so you cannot figure, what it was.
This problem is partially solved there: https://github.com/collectiveidea/audited/issues/72#issuecomment-398756380

Rails self join with has_many through relationship

Users can follow other Users through FollowingRelationship
I would like to be able to say
User.first.followings and it returns a list of Users
This is not working:
Class User
has_many :following_relationships
has_many :followings, through: :following_relationships, foreign_key: :following_id, source: :user
end
Class FollowingRelationship
attr_accessible :following_id, :follower_id
belongs_to :followings, class_name: "User"
end
User.first.followings gives this in console:
SELECT "users".* FROM "users" INNER JOIN "following_relationships" ON "users"."id" = "following_relationships"."user_id" WHERE "following_relationships"."user_id" = 1
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: following_relationships.user_id: SELECT "users".* FROM "users" INNER JOIN "following_relationships" ON "users"."id" = "following_relationships"."user_id" WHERE "following_relationships"."user_id" = 1
Anyone see what piece I am missing?
I think the problem is that FollowingRelationship model doesn't have any relation with User.
It should have belongs_to :user since you specify source: :user, shouldn't it?
I don't quite understand your models and relations between them but belongs_to :followings looks rather strange.
belongs_to uses singular form since it can't belong to more than one object.
UPDATE 1
To be more clear.
I think you should have
class FollowingRelationship
…
belongs_to :user
belongs_to :follower
end
That means followings_relationships table should have user_id and follower_id columns.
UPDATE 2
After some conversation we have figured it out.
Here is the code you should have
class User < ActiveRecord::Base
has_many :fade_relationships, foreign_key: :faded_id
has_many :fadings, through: :fade_relationships, source: :fading
end
class FadeRelationship < ActiveRecord::Base
attr_accessible :faded_id, :fading_id
belongs_to :fading, class_name: "User"
end

Has many :through a Polymorphic association not aliasing type column with Squeel 1.1.1

I've got a few classes:
class Membership < ActiveRecord::Base
belongs_to :group
belongs_to :member, polymorphic: true
end
class User < ActiveRecord::Base
has_many :memberships, as: :member, dependent: :destroy
has_many :groups, through: :memberships
end
class Package < ActiveRecord::Base
has_many :memberships, as: :member, dependent: :destroy
has_many :groups, through: :memberships
end
and
class Group < ActiveRecord::Base
has_many :memberships, dependent: :destroy, inverse_of: :member
has_many :packages, through: :memberships, source: :member, source_type: 'Package'
has_many :users, through: :memberships, source: :member, source_type: 'User'
end
A Users and Packages can have membership in a Group. All of this appears to be ok until I try to get the Users that belong to a Group. In the console, with an instance of a Group g:
[5] pry(main)> g.users
User Load (3.3ms) SELECT "USERS".* FROM "USERS" INNER JOIN "MEMBERSHIPS" ON
"USERS"."ID" = "MEMBERSHIPS"."MEMBER_ID" WHERE "MEMBERSHIPS"."GROUP_ID" = :a1 AND
"USERS"."MEMBER_TYPE" = 'User' [["group_id", 10000]]
OCIError: ORA-00904: "USERS"."MEMBER_TYPE": invalid identifier:
SELECT "USERS".*
FROM "USERS"
INNER JOIN "MEMBERSHIPS" ON "USERS"."ID" = "MEMBERSHIPS"."MEMBER_ID"
WHERE "MEMBERSHIPS"."GROUP_ID" = :a1
AND "USERS"."MEMBER_TYPE" = 'User'
It's easy to see the error - the Users table doesn't have the Member_Type column, it resides on the Membership table.
Why is the column not being aliased appropriately? Anyone else having this problem?
Update
This is an issue with Squeel - removing it from my Gemfile and the association behaves properly.
Logged an issue - https://github.com/ernie/squeel/issues/288

Resources