Reputation: 237
There are all kinds of references out there to building a Has_Many through relationship in Ruby on Rails, but very little information on how to handle that relationship.
I have a Users table with the following variables:
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
Then I have a Wikis table:
t.string "title"
t.datetime "published_at"
t.datetime "created_at"
t.datetime "updated_at"
t.text "body"
And, I have a Relationships table to join the two:
t.integer "wiki_id"
t.integer "user_id"
t.boolean "creator_created"
t.boolean "collaborator"
The Relationships Class belongs_to each of the other classes:
belongs_to :users
belongs_to :wikis
The other two have has_many, through code:
class User < ActiveRecord::Base
has_many :relationships
has_many :wikis, through: :relationships
and vice versa.
So my question is, now what? How can I call these various classes and their variables? user.wiki doesn't seem to work, or user.relationship.wiki; even relationship.created_creator pulls error messages!
In particular, I want to be able to find out if a user created a wiki so that that user may edit that wiki. I've tried to create a method for this in the user model:
def creator?(wiki, user)
Relationship.where(wiki_id: wiki.id, user_id:user.id).creator_created
end
When I call it from a policy file, I get the error:
undefined method `creator_created' for # <ActiveRecord::Relation::ActiveRecord_Relation_Relationship:0xbb19b84>
Can anyone help me use my has_many, through relationship? How can I refer to a wiki that is related to a user, or to the creator_created field that is related to both?
Thank you!
Upvotes: 1
Views: 116
Reputation: 47548
A has_many
relationship returns a collection. Rails uses the plural form of the association name to access the related objects.
To get a user's wikis, just do:
user.wikis
To find whether a user created a wiki, you'll need to use the Relationship:
user.wikis.where(relationships: {creator_created: true})
This shows how you can chain scopes onto associations.
This can be further simplified by adding a scope to the Wiki
model:
scope :creator_created, -> { where(relationships: {creator_created: true}) }
which allows:
user.wikis.creator_created
It can be quite helpful to add .to_sql
to the end of the expression to see the generated SQL statement.
Much of this is covered in the Rails Guide Active Record Query Interface
EDIT
The error message "NameError: uninitialized constant User::Wikis" occurs due to an error in the Relationship
model. belongs_to
associations use the singular form to specify the related model, so change them to:
belongs_to :user
belongs_to :wiki
Upvotes: 2
Reputation: 10073
.where(...)
returns a collection of model objects.
def creator?(wiki, user)
# Raises error, as a model instance method (creator_created) is
# called on a collection:
Relationship.where(wiki_id: wiki.id, user_id:user.id).creator_created
end
In the code above, an attempt is being made to call .creator_created
on a collection of Relationship
objects (assuming objects are found matching the conditions given to .where(...)
).
.creator_created
is a method of a single Relationship
object. It's not possible to call it on a collection of Relationship
s.
Modifying the given creator?
method to take this into account would give:
def creator?(wiki, user)
relationships = Relationship.where(wiki_id: wiki.id, user_id: user.id)
if relationships.empty?
raise "No matching relationships found for wiki: '#{wiki}'"
end
# Use the first element from the relationships collection
relationship = relationships[0]
relationship.creator_created
end
However, given the name of that method, I suspect this might be a more readable solution:
def creator?(wiki, user)
user.relationships.find_by(wiki_id: wiki.id).creator_created
end
Compare .find_by(...)
to .where(...)
. find_by
returns only a single model instance (if a match is found). .find_by
docs and .where
docs have example code.
Upvotes: 0
Reputation: 3760
try:
Relationship.where(wiki_id: wiki.id, user_id:user.id, creator_created: true)
Upvotes: 0