Strange query when calling has_one - ruby-on-rails

Problem: Message class with a has_many :through relationship and a has_one relationship both to the User class. Getting a strange query when I try to use the has_one relationship.
I have a Message class with a has_many relationship to a User through message_memberships. Each instance of Message as well as having users linked to it through the has_many through relationship has a creator who is also a User.
Due to laziness I started to log the creators id in a creator_id column on the Message instance (schema below). Every time a message was created I would add the creators id to the column. Every time I wanted to reference the creator I would call User.find(message.creator_id). I was looking in to creating a link between the creator column and a user but I cannot find the right implementation.
The problem I am having is with referencing the :creator_id column in the has_one relationship. I would assume something like this would work
Message.rb
has_one :creator, -> { where id: :creator_id }, class_name: 'User'
But the query that gets called whenever I look for message.creator is this
SELECT "users".* FROM "users" WHERE "users"."message_id" = ? AND "users"."id" = 'creator_id' LIMIT 1 [[nil, 26]]
I am not entirely sure where the WHERE "users"."message_id" = ?. Without it, it looks like the query would be fine. I have no idea how to stop it from happening.
Schema
create_table "messages", force: true do |t|
t.string "title"
t.string "contents"
t.datetime "created_at"
t.datetime "updated_at"
t.string "token"
t.integer "creator_id"
t.string "link"
end
Any help would be greatly appreciated

Try this setup:
class Message
belongs_to :creator, class_name: '::User', foreign_key: :creator_id
end
class User
has_many :authored_messages, class_name: '::Message', inverse_of: :creator
end

Related

How can i make rails models belongs_to using multiple foreign key

recently I have a migration that adds a user_id column to the watch_events tables. and thus I want to change the watch_event models to handle belongs_to but with multiple approach
create_table 'users', force: :cascade do |t|
t.integer 'id'
t.integer 'customer_id'
end
create_table 'watch_events', force: :cascade do |t|
t.integer 'customer_id'
t.integer 'user_id'
end
previously
class WatchEvent < ApplicationRecord
belongs_to :user, foreign_key: :customer_id, primary_key: :customer_id
end
what I want:
if watch_event.customer_id is present, i want to use belongs_to :user, foreign_key: :customer_id, primary_key: :customer_id
if watch_event.customer_id is not present, i want to use normal belongs_to :user
how can I achieve this on the watch_event model?
I do not think that Rails supports 'fallback foreign keys' on associations. However, you can write a simple wrapper for your problem. First, relate your WatchEvent class twice to the user model, using your two keys and two 'internal' association names (:user_1 and :user_2). Then, add a 'virtual association reader' (user) and a 'virtual association setter' (user=(new_user)):
class WatchEvent < ApplicationRecord
belongs_to :user_1,
class_name: 'User',
foreign_key: :customer_id
belongs_to :user_2,
class_name: 'User',
foreign_key: :user_id
def user
user_1 || user_2
end
def user=(new_user)
self.user_1 = new_user
end
end
With this solution, the requirements "use customer_id to find user" and "use user_id as fallback foreign key if customer_id is nil or doesn't yield a result" is satisfied. It happens in the association reader method user. When there is a reader method, you'll need a setter method, which is user=(). Feel free to design the setter's internals as required, mine is just a suggestion.
BTW: You may need to re-add the declaration of the foreign primary_key. I omitted that for clarity.
If I understand your question correctly, then what you are looking for is a Polymorphic association.
If you see the code below, what it basically does is create two columns in the watch_events table, watcher_type and watcher_id. And the belongs_to :watcher then uses the watcher_type column to identify which model it should associate to,
create_table 'watch_events', force: :cascade do |t|
t.references 'watcher', polymorphic: true, null: false
end
class WatchEvent < ApplicationRecord
belongs_to :watcher, polymorphic: true
end

Associating multiple existing records

I'm creating an application where one user becomes the account_manager of an account. What I want to do is to add other users to the account. A user can only have one account but an account can have many users.
class Account < ActiveRecord::Base
has_many :users
belongs_to :account_manager, :class_name => 'User', :foreign_key => 'account_manager_id'
end
class User < ActiveRecord::Base
has_one :account
What I'm totally stuck on is having a place where the account manager can either select the user from a dropdown, type in their name, or use some other type of selection. If I try to do this in console each new user I add replaces the last instead of adding to it. here is my schema for accounts:
create_table "accounts", force: :cascade do |t|
t.integer "user_id"
t.integer "account_manager_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I've tried using collection_select but I think that is only for :though associations. I'm also thinking I probably need a join table but I don't know how to set it up. The thing that is tripping me up most is that I won't be creating new objects, I only want to add existing users to existing accounts. I'm just looking for someone who can talk through this with me.
In your User model:
class User < ActiveRecord::Base
belongs_to :account
end
While in your question, you are writing has_one. You can't write has_many in one model, and has_one in other model. There needs to be belongs_to in one model.
Edit:
The model that belongs_to, always saves the foreign keys in its table. So users would save account_id in it. In order to get all the users of an account, you would simply do:
Account.first.users # As an account `has_many` users

Rails JOINS with a twist

I have been trying and failing for 2 days now :) to get a list of ideas (posts basically) with likes. Order Desc preferably.
I have scaffolded ideas and users which work fine.
Likes (socialization gem) gives me the headache.
I can add likes and retrieve them. And I can also find out how many likes a specific idea has: idea.likers(User).count
and find out whether a user likes a specific idea: user.likes?(idea)
But I can't do agregates because of the non-standard field names which prohibit me from making a JOIN.
create_table "likes", force: :cascade do |t|
t.string "liker_type"
t.integer "liker_id" (this is/should be user_id)
t.string "likeable_type"
t.integer "likeable_id" (this is/should be idea_id)
t.datetime "created_at"
end
add_index "likes", ["likeable_id", "likeable_type"], name: "fk_likeables"
add_index "likes", ["liker_id", "liker_type"], name: "fk_likes"
Models:
like.rb - empty
user.rb - acts_as_liker
idea.rb - acts_as_likeable
Is there a way to join likes and ideas eg somehow matching liker_id to user_id? Or shall I rename the fields in the table (liker_id to user_id and likeable_id to idea_id)...? And also add these:
like.rb
belongs_to :user
belongs_to :idea
idea.rb
has_many :likes, dependent: :destroy
user.rb
has_many :likes, dependent: :destroy
Thanks in advance!
To specify a different column as foreign key which gets used in joins, you could add foreign_key: ... option to belongs_to as follows:
# app/models/like.rb
belongs_to :user, foreign_key: :liker_id
belongs_to :idea, foreign_key: :likeable_id
See referenced documentation on belongs_to.
You can also specify join conditions yourself as follows:
Idea.joins('inner join likes on ideas.id = likes.likeable_id').where(...)

Writing a migration for a HABTM relation with supplied class names and a belongs_to

I have a relational model between users and courses ("courses" as in "I'm taking a course in statistics"). A class has one teacher (a User) and many students (also Users); a user can teach many classes but also be enrolled in many classes.
I believe the correct way to set up this relationship in my models would be:
class User < ActiveRecord::Base
has_many :taught_courses, class_name: "Course"
has_and_belongs_to_many :enrolled_courses, class_name: "Course"
end
class Course < ActiveRecord::Base
belongs_to :teacher, class_name: "User"
has_and_belongs_to_many :students, class_name: "User"
end
But I have no idea how to set this up in my database (I'm new at this).
Edit:
I have a migration that amounts to this:
def change
create_table :courses_users do |t|
t.belongs_to :course
t.belongs_to :user
end
create_table :courses do |t|
t.string :name
t.integer :teacher_id
t.timestamps
end
end
But when I try to create a new a user in the console, I get an error about user_id:
irb(main):007:0> u.taught_courses.create(name: "Foo bar")
(0.0ms) SAVEPOINT active_record_1
(0.1ms) ROLLBACK TO SAVEPOINT active_record_1
ActiveRecord::UnknownAttributeError: unknown attribute: user_id
Where should this user_id be? Or should I be more specific about my foreign keys?
Edit 2
It turns out I also had to specify foreign_key: "teacher_id" on the has_many relation between Users and Courses for it to work.
Sorry for the misleading question.
The relational model seems reasonable to me. Database setup is discussed in 3.3.2 of http://guides.rubyonrails.org/association_basics.html#updating-the-schema. If there's a particular portion of that section that you find confusing, it would help you asked a more narrow question.
For example, the above section includes the following sample migration. You could explain how this example does not seem to apply to your case.
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration
def change
create_table :assemblies_parts, id: false do |t|
t.integer :assembly_id
t.integer :part_id
end
end
end
The significance of the id: false clause is explained in the last paragraph of the aforementioned section, as follows:
We pass id: false to create_table because that table does not
represent a model. That's required for the association to work
properly. If you observe any strange behavior in a
has_and_belongs_to_many association like mangled models IDs, or
exceptions about conflicting IDs, chances are you forgot that bit.

Making ActiveRecord join model attributes available in query results

Given User and Book models, I've created a join model, ViewedBook, that contains additional attributes. Below is the essence of what I've come up with:
create_table "users"
t.string "username"
end
create_table "books"
t.string "title"
t.integer "user_id"
t.date "authored_date"
end
create_table "books_viewings"
t.integer "book_id"
t.integer "user_id"
t.boolean "finished"
t.date "last_viewed_date"
end
class User
belongs_to :book_viewing
has_many :authored_books,
:class_name => "Book",
:source => :book
has_many :book_viewings
has_many :viewed_books :through => :book_viewings
:order => "book_viewings.last_viewed_date DESC"
has_many :finished_books :through => :book_viewings
:conditions => "book_viewings.finished = TRUE",
:order => "book_viewings.last_viewed_date DESC"
end
class Book
belongs_to :user
has_one :author, :class_name => "User"
end
class BookViewing
belongs_to :book
belongs_to :user
end
I think this works for most of the queries I need to create. However, I want each of the Book objects returned by user.viewed_books to include the finished attribute as well. Further, I will have additional queries like Book.best_sellers that I would also like to scope to a given user so that they also include the finished attribute.
From my limited exposure to ActiveRecord, it appears there's probably an elegant way to manage this and generate efficient queries, but I have yet to find an example that clarifies this scenario.
EDIT: to clarify, the other queries I'm looking for will not themselves be restricted to books that have been finished, but I need to have the finished attribute appended to each book if it exists for the given book and scoped user in book_viewings.
See my answer here https://stackoverflow.com/a/8874831/365865
Pretty much, no there isn't a specific way to do this, but you can pass a select option to your has_many association. In your case I'd do this:
has_many :books, :through => :book_viewings, :select => 'books.*, book_viewings.finished as finished'

Resources