I'm building my first Rails engine and getting pretty confused already defining the associations between two models. For the reason of ease, let's say the name of the engine is Blorg, having two models Article and Author.
blorgh/article.rb
module Blorgh
class Article < ApplicationRecord
belongs_to :author, class_name: 'Blorg::Author',
foreign_key: 'blorg_author_id', optional: true
blorgh/author.rb
module Blorgh
class Author < ApplicationRecord
has_many :articles, class_name: 'Blorg::Article'
schema
create_table "blorgh_authors", force: :cascade do |t|
t.string "name"
t.boolean "inactive", default: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
create_table "blorgh_articles", force: :cascade do |t|
t.string "title"
t.bigint "blorgh_author_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["blorgh_author_id"], name: "index_blorgh_article_on_author_id"
If I try to create an article or count the articles of an author via rails c, I get those errors:
article = Blorgh::Article.new(title: 'New Article')
article.save # expect true
# ==> NoMethodError: private method `attribute' called for #<Blorgh::Article:0x00007fdc3fad4d50>
author = Blorgh::Author.create # worked
author.articles.count # expect 0
# ==> ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column blorgh_articles.author_id does not exist
Does anyone know how I can achieve those in-engine associations correctly?
The second error ("column blorgh_articles.author_id does not exist") is because Rails assumes the foreign key on a has_many relationship is classname_id, which is author_id in your example. You correctly set the foreign key on the belongs_to side, but you need to specify on both sides of the relationship. So:
module Blorgh
class Author < ApplicationRecord
has_many :articles, class_name: 'Blorg::Article', foreign_key: 'blorg_author_id'
...
Related
I've been researching friendship models using roles, custom associations, etc. But I haven't been able to connect my project to the concepts in a clear way.
I want a "User" to be able to create an event I'm calling a "Gather". A User can also attend a Gather created by other Users. By attending a Gather, the "User" can also be a "Gatherer".
The list of Gatherers will technically be considered friends of the "creator". This is how far I've gotten:
Models:
User
Gather
Gatherer (?)
User
class User < ApplicationRecord
has_many :gathers_as_creator,
foreign_key: :creator_id,
class_name: :Gather
has_many :gathers_as_gatherer,
foreign_key: :gatherer_id,
class_name: :Gather
end
Gather
class Gather < ApplicationRecord
belongs_to :creator, class_name: :User
belongs_to :gatherer, class_name: :User
end
My question is, do I need to a join table, such as Gatherer, to allow multiple attendees and then later pull a friend list for the user/creator ?
Gatherer
belongs_to :gather_attendee, class_name: "User"
belongs_to :attended_gather, class_name: "Gather"
Here's what I think that schema would look like:
create_table "gatherers", force: :cascade do |t|
t.bigint "attended_gather_id"
t.bigint "gather_attendee_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["attended_gather_id"], name: "index_gatherers_on_attended_gather_id"
t.index ["gather_attendee_id"], name: "index_gatherers_on_gather_attendee_id"
end
Help, my head is spinning trying to understand the connections and how to proceed.
Previous planning:
Schema:
create_table "activities", force: :cascade do |t|
t.string "a_type"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "gatherers", force: :cascade do |t|
t.bigint "attended_gather_id"
t.bigint "gather_attendee_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["attended_gather_id"], name: "index_gatherers_on_attended_gather_id"
t.index ["gather_attendee_id"], name: "index_gatherers_on_gather_attendee_id"
end
create_table "gathers", force: :cascade do |t|
t.integer "creator_id"
t.integer "activity_id"
t.text "gather_point"
t.boolean "active"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "interest_gathers", force: :cascade do |t|
t.string "gather_id"
t.string "interest_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "interests", force: :cascade do |t|
t.string "i_type"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "img"
t.string "first_name"
t.string "last_name"
t.string "state"
t.string "city"
t.string "bio"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
add_foreign_key "gatherers", "gathers", column: "attended_gather_id"
add_foreign_key "gatherers", "users", column: "gather_attendee_id"
end
class User < ActiveRecord::Base
has_many :gatherers, foreign_key: gather_attendee_id
has_many :attended_gathers, through: :gatherers
has_many :created_gathers, foreign_key: :creator_id, class_name: "Gather"
end
class Gather < ActiveRecord::Base
has_many :gatherers, foreign_key: :attended_gather_id
has_many :attendees, through: :gatherers, source: :gather_attendee
belongs_to :creator, class_name: "User"
end
class Gatherer < ActiveRecord::Base
belongs_to :gather_attendee, class_name: "User"
belongs_to :attended_gather, class_name: "Gather"
end
The naming here is not great. When naming your models choose nouns as models represent the actual things in your buisness logic - choosing verbs/adverbs makes the names of your assocations very confusing.
class User < ApplicationRecord
has_many :gatherings_as_creator,
class_name: 'Gathering',
foreign_key: :creator_id
has_many :attendences
has_many :gatherings,
through: :attendences
end
# think of this kind of like a ticket to an event
# rails g model Attendence user:references gathering:references
class Attendence < ApplicationRecord
belongs_to :user
belongs_to :gathering
end
# this is the proper noun form of gather
class Gathering < ApplicationRecord
belongs_to :creator,
class_name: 'User'
has_many :attendences
has_many :attendees,
though: :attendences,
class_name: 'User'
end
My question is, do I need to a join table, such as Gatherer, to allow multiple attendees and then later pull a friend list for the user/creator ?
Yes. You always need a join table to create many to many assocations. Gatherer is a pretty confusing name for it though as that's a person who gathers things.
If you want to get users attending Gatherings created by a given user you can do it through:
User.joins(attendences: :groups)
.where(groups: { creator_id: user.id })
You're on the right track.
If I understand what you're looking for correctly, you want a Gather to have many Users and a User to have many Gathers (for the attending piece). So you need a join table like this (this is similar to your gatherers table, but is in a more conventional Rails style):
create_join_table :gathers, :users do |t|
t.index [:gather_id, :user_id]
t.index [:user_id, :gather_id]
end
And then you'd want your User model to be like this:
class User < ApplicationRecord
has_many :gathers_as_creator, foreign_key: :creator_id, class_name: "Gather"
has_and_belongs_to_many :gathers
end
class Gather < ApplicationRecord
belongs_to :creator, class_name: "User"
has_and_belongs_to_many :users
end
(You can change the name of that :users association if you really want, by specifying extra options -- I just like to keep to the Rails defaults as much as I can.)
That should be the bulk of what you need. If you want to pull all the friends of a creator for a specific gather, you would just do gather.users. If you want to pull all of the friends of a creator across all their gathers, that will be:
creator = User.find(1)
friends = User.joins(:gathers).where(gathers: { creator: creator }).all
I've got two models Wallet and FakeWallet associated with identifier and external_id like below:
#models
class Wallet < ApplicationRecord
has_one :fake_wallet, foreign_key: :identifier, primary_key: :external_id
end
class FakeWallet < ApplicationRecord
belongs_to :wallet, foreign_key: :identifier, primary_key: :external_id, optional: true
end
When trying to add a new record via Wallet.last.fake_wallet.create!(balance: 7890) I'm getting an error:
NoMethodError (undefined method `create!' for nil:NilClass)
If foreign key is not standard this action is not possible or did I wrongly bind the models? Below my schema:
#schema.rb
create_table "wallets", force: :cascade do |t|
t.integer "wallet_type"
t.string "external_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "fake_wallets", force: :cascade do |t|
t.decimal "balance", default: "0.0"
t.string "identifier"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
The standard method built has_one doesn't ever return an ActiveRecord::Relation, so this kind of chaining does not work. Instead has_one adds dedicated methods to your model for this, named create_<name> and create_<name>!.
So in your example, you would instead call…
Wallet.last.create_fake_wallet!(balance: 7890)
For more information, here is the relevant documentation for the methods has_one creates.
I am trying to make an association that is not working.
I have the following scope:
ActiveRecord::Schema.define(version: 2020_04_05_125812) do
create_table "accounts", force: :cascade do |t|
t.string "social_network"
t.string "name_account"
t.integer "person_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["person_id"], name: "index_accounts_on_person_id"
end
create_table "lists", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "lists_people", id: false, force: :cascade do |t|
t.integer "list_id", null: false
t.integer "person_id", null: false
end
create_table "people", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "posts", force: :cascade do |t|
t.string "post_text"
t.date "date"
t.string "link"
t.integer "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_posts_on_account_id"
end
add_foreign_key "accounts", "people"
add_foreign_key "posts", "accounts"
end
I wish I could consult, for example:
I want to consult the person named "Test_name"
That person belongs to lists, which is a collection of people. In addition, that person has accounts and those accounts have Posts.
class List < ApplicationRecord
has_and_belongs_to_many :people
end
class Person < ApplicationRecord
has_and_belongs_to_many :lists
end
class Account < ApplicationRecord
has_many :posts
belongs_to :person
end
class Post < ApplicationRecord
belongs_to :account
end
How could I have a return like the one below:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
All the queries I make, either return the wrong type, or return only the list.
Problems such as:
List | Name | social_network
1 | Test_name | facebook
2 | Test_name | twitter
1 | Second_name | twitter
And I don't want to see the data "second_name"
I try this:
#lists = List.from(
Person.left_outer_joins(:list).where('people.name LIKE ?', "Renata Rempel"),
:list
)
but, doesn't works =/
To start off with you want to setup a many to many association between Person and List. This can be done with has_and_belongs_to_many but there are many reasons why has_many through: may be a better choice. The primary one is that it will let you add features like keeping track of banned users or when a user joined a list.
# rails g model list_membership member:belongs_to user:belongs_to
class ListMembership < ApplicationRecord
belongs_to :member, class_name: 'Person'
belongs_to :list
end
We then have to fix the foreign key in the association:
class CreateListMemberships < ActiveRecord::Migration[6.0]
def change
create_table :list_memberships do |t|
t.belongs_to :list, null: false, foreign_key: true
t.belongs_to :member, null: false, foreign_key: { to_table: :people }
t.timestamps
end
# can be a good idea to add a compound index
# add_index [:list_id, :member_id], unique: true
end
end
class Person < ApplicationRecord
has_many :list_memberships, foreign_key: :member_id
has_many :lists, through: :list_memberships
has_many :accounts
has_many :posts, through: :accounts
end
class List
has_many :list_memberships
has_many :members,
through: :list_memberships
end
Your from query will not work as your actually selecting rows from people but you just alias the table lists. That won't magically select the right data! If you really wanted to use from you would do:
List.from(
List.joins(:members).where("people.name LIKE ?", "Renata Rempel"),
:lists
).eager_load(members: :posts)
If you want to create a bunch of lists with a random number of members in your seed file you can just do:
ids = 10.times.map do
Person.create!(name: Faker::Name.name).id
end
lists = 10.times.do
List.create!(member_ids: ids.sample(2))
end
Three models Professor, Expertise & ExpertisesProfessor (the join table). I would like to use a has_many activerecord structure but when I call Expertise.professors.all I get an error
*NoMethodError (undefined method `professors' for Class:0x000000000a1ddda0) *
I want to be able to call Expertise.professors and Professor.expertise ???
I am comfortable with using HABTM instead of "has_many through" but for my project I prefer to use the the " has_many through " relationship so please if I could get solutions along those lines only if possible .
**professor.rb**
class Professor < ApplicationRecord
has_many :expertise_professors
has_many :expertises, through: :expertise_professors
end
**expertise.rb**
class Expertise < ApplicationRecord
has_many :expertise_professors
has_many :professors, through: :expertise_professors
end
**expertises_professor.rb**
class ExpertisesProfessor < ApplicationRecord
belongs_to :expertise
belongs_to :professor
end
My Schema File
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_12_18_191008) do
create_table "expertises", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "expertises_professors", id: false, force: :cascade do |t|
t.integer "expertise_id", null: false
t.integer "professor_id", null: false
end
create_table "professors", force: :cascade do |t|
t.string "name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
end
Any ideas what I have missed ?
You can not call Expertise.professors. You first need to load the single record or object of the Expertise like
expertise = Expertise.first
And then you can get all professors
expertise.professiors.all
Same way you can get all expertises for specific professor.
I have two models: University and Market
A University belongs to a single Market, and a Market can have many universities. For example, An instance of Market like Boston might have University instances like MIT, Harvard, Boston University, etc.
I want to be able to do something like this in console:
University.first.market.name
But I get the following error:
NoMethodError: undefined method `market' for #
I can get the market id from University.first.market_id, but I can't get the name from market.name.
Here is how I have my models set up:
class University < ApplicationRecord
belongs_to :markets
class Market < ApplicationRecord
has_many :universities
end
And here is my schema - I think with the market_id integer column and index correctly implemented (?)
create_table "universities", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "full_name"
t.integer "market_id"
t.index ["market_id"], name: "index_universities_on_market_id"
end
Here's my markets schema:
create_table "markets", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
end
What am I doing incorrectly?
In a one-to-many relationship, the "belongs_to" end must be singular, as below:
class University < ApplicationRecord
belongs_to :market
end
class Market < ApplicationRecord
has_many :universities
end
Let me know if that helps