I'm new to Ruby on Rails and I'm trying to build a relationship between the classes Club, Sponsor and Match.
The relationhip has to be like:
One Club has zero to many Sponsors
One Sponsor has zero to many Matches
One Match has zero to many Sponsors
My models look like this
class Match < ApplicationRecord
belongs_to :team
has_many :matchplayers
has_many :players, through: :matchplayers
has_many :sponsors
end
class Club < ApplicationRecord
has_many :teams
has_many :sponsors
accepts_nested_attributes_for :teams, :reject_if => :all_blank, :allow_destroy => true
end
class Sponsor < ApplicationRecord
belongs_to :club
end
and my migrations file for the Sponsor model looks like this:
class CreateSponsors < ActiveRecord::Migration[5.1]
def change
create_table :sponsors do |t|
t.text :name
t.text :url
t.text :imgUrl
t.references :club, foreign_key: true
t.timestamps
end
add_reference :matches, :sponsor, index: true
add_foreign_key :matches, :sponsor
end
end
I have no problems retrieving sponsors for each club instance but I'm having trouble retrieving the sponsors associated with each match.
In my matches_controller.rb I have this
def show
#match = Match.find(params[:id])
render :json => #match.to_json(:include => [:players, :sponsors])
end
But when I try to run it the script fails. with the error message "no such column: sponsors.match_id" as the script tries to run the following SQL statement
SELECT "sponsors".* FROM "sponsors" WHERE "sponsors"."match_id" = ?
What I'd really like it to do would be to run the following statement
SELECT "sponsors".*
FROM "sponsors"
LEFT JOIN "matches"
ON "matches"."sponsor_id" = "sponsors"."id"
WHERE "matches"."id" = ?
And placing the resulting array into the output JSON's "sponsors" attribute.
I have been looking into the different Active Record association types and I feel like the type of association I need for this task is looser than the ones described in the documentation.
You need many-to-many relationship. In rails where are 2 ways to do this. You can read about this here. In general you will need to add has_and_belongs_to_many to Sponsor and to Match. And create 'join-model' which will contain match_id + sponsor_id. In this way ActiveRecord will be able to create suitable SQL query due to 'join-table'.
Related
I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)
I am having a hard time using polymorphic associations in a joint table where one option could lead to the association of two instances of the same table. I am relatively new to programming so I hope my question makes sense.
Basically, I have 3 models where my aim is to make associations between different products:
Product
ExtrenalProduct
Integration (polymorphic)/joint table
The Integration table will link 2 Products or 1 Product and 1 ExternalProduct
Here is my migration file:
class CreateIntegrations < ActiveRecord::Migration[5.1]
def change
create_table :integrations do |t|
t.references :main_product
t.belongs_to :integratable, polymorphic: true
t.timestamps
end
add_index :integrations, [:integratable_id, :integratable_type]
add_foreign_key :integrations, :products, column: :main_product_id, primary_key: :id
end
end
Here is my Integration Model:
class Integration < ApplicationRecord
belongs_to :integratable, polymorphic: true
belongs_to :main_product, class_name: 'Product'
end
Here is my ExternalProduct Model:
class ExternalProduct < ApplicationRecord
has_many :integrations, as: :integratable
end
Here is my Product Model:
has_many :integrations, class_name: 'Integration', foreign_key: 'main_product_id', dependent: :destroy
has_many :integrations, as: :integratable
My question is regarding the way I can query all Integrations from #product. As for now, I have to build a custom method:
def all_integrations
Integration.where(main_product_id: id).or(Integration.where(integratable_type: 'Product', integratable_id: id))
end
I would like to be able to do a: #product.integrations (which currently retrieves an empty array)
Any clue on what I am doing wrong or how I could make this code DRY? I feel I am missing something.
Thanks for reading!
(I'm translating the code as I write, so I apologize for any mistakes!)
I'm trying to implement a bidirectional self referential relation involving the Associates table and a join table Property. I tried following this post but something is still wrong.
The way it's supposed to work is, an associate can be the proprietor of zero or more associates, and consequently an associate may have zero or more proprietors (makes more sense in the context of the application).
So I created the Property model:
class CreateProperties < ActiveRecord::Migration
def change
create_table :properties do |t|
t.integer :proprietor_id
t.integer :property_id
t.timestamps null: false
end
end
end
So the table contains only the ids of one proprietor and one property, both associates, per entry.
Following the tutorial linked above, I came to this configuration:
Associate.rb:
...
has_many :properties
has_many :related_properties, :through => :properties
has_many :proprietors, :class_name => "Property", :foreign_key => "proprietor_id"
has_many :related_proprietors :through => :proprietors, :source => :associate
...
Property.rb:
belongs_to :associate
belongs_to :related_properties, :class_name => "Associate"
However when I try to use these relations (<% #associate.related_properties.each do |property| %>), I get this error:
PG::UndefinedColumn: ERROR: column properties.related_properties_id does not exist
LINE 1: ... INNER JOIN "propriedades" ON "associados"."id" = "proprieda...
^
: SELECT "associates".* FROM "associates" INNER JOIN "properties" ON "associates"."id" = "properties"."related_properties_id" WHERE "properties"."associate_id" = $1
Basically, the column names are wrong in the generated SQL: properties.related_properties_id should be properties.proprietor_id, and properties.associate_id should be properties.proprietor_id as well.
What have I done wrong, and how can I fix this code to get the correct relations?
You need to setup two seperate associations since the foreign key on Property depends on what the Associates role is.
class Associate
# defines relations where Associate is the "owning" party
has_many :properties_as_proprietor,
class_name: 'Property',
foreign_key: 'proprietor_id'
has_many :properties,
through: :properties_as_property,
source: :property # what to select on Property
# defines relations where Associate is the "owned" party
has_many :properties_as_property,
class_name: 'Property',
foreign_key: 'property_id'
has_many :proprietors,
through: :properties_as_proprietor,
source: :proprietor # what to select on Property
end
class Property
belongs_to :proprietor, class_name: 'Associate'
belongs_to :property, class_name: 'Associate'
end
This follows on from my previous question
I have a user model with two self joins, seller and buyer.
I have an categories model, a seller has_and_belongs_to_many categories, as does a buyer.
How do I create the migration so I can do seller.categories and categories.buyers etc...
I thought it would be something like I have below but it doesn't work...
def change
create_table :categories_sellers do |t|
t.references :category
t.references :user
end
add_foreign_key :categories_sellers, :users, column: :trainer_id
add_index :categories_users, [:category_id, :seller_id]
add_index :categories_users, :seller_id
end
end
To answer your question, it looks like you just need to change t.references :user to t.references :seller.
That said, I would highly suggest modeling your project as such:
module User
extend ActiveSupport::Concern
has_many :category_users, as: :user
has_many :categories, through: :category_users
# include any common methods for buyers and sellers,
# basically your current User model
end
class Buyer < ActiveRecord::Base
include User
end
class Seller < ActiveRecord::Base
include User
end
class CategoryUser < ActiveRecord::Base
belongs_to :category
belongs_to :user, polymorphic: true
end
class Category < ActiveRecord::Base
has_many :category_users
has_many :buyers, through: :category_users, source: :user, source_type: 'Buyer'
has_many :sellers, through: :category_users, source: :user, source_type: 'Seller'
end
I know that may require some changes that you didn't anticipate, but in doing that, you get more natural methods such as:
category.buyers
category.sellers
buyer.categories
seller.categories
Under the hood, your join table will have columns like:
id -- the row id
category_id -- the category id, of course
user_id -- the id of the Buyer or Seller
user_type -- one of "Buyer" or "Seller" (or any other type you deem as "user")
To run the migrations:
User doesn't need one, it's not a model.
Buyer and Seller are pretty straightforward, old User model + Buyer/Seller model.
Category doesn't need one as it already exists.
CategoryUser:
def change
create_table :category_users do |t|
t.references :category
t.references :user, polymorphic: true
end
add_index :category_users, :category_id
add_index :category_users, [:category_id, :user_id]
add_index :category_users, [:category_id, :user_id, :user_type]
end
I haven't checked this personally but should be right, or close. The overall principle, though, is to make use of polymorphic associations to make a more natural association between "some kind of user" (whether it be a Buyer, Seller, or any other type of user you come up with) and a category. Then, you don't need to replicate the same sort of associations over and over again because the models slightly vary.
Here's more details on this approach:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Trying to resolve my issue, I've found a lot of common confusion surrounding has_many :through and has_and_belongs_to_many (which I'd like to avoid using). Reading through most of that on Stackoverflow makes me think I've set this up correctly:
class Product < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :products, :through => :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :product
belongs_to :category
end
And my migration. I've seen several ways to go about this, and this way seems valid:
class CreateCategorizations < ActiveRecord::Migration
def change
create_table :categorizations, :id => false do |t|
t.references :product
t.references :category
end
add_index :categorizations, [:category_id, :product_id], :unique => true
end
end
And in my categories controller, I'm trying to create an ActiveRecord relation through this association:
class CategoriesController < ApplicationController
def show
#category = Category.find(params[:id])
#products = Product.where(:category => #category)
end
end
If I simply do #category.products, everything is fine, and I get an array/collection of a number of products included in that category. But I don't want an array. If I try to use where I get this error:
ActiveRecord::StatementInvalid - PG::UndefinedColumn: ERROR: column products.category does not exist
Is there a more complex joins statement I should make to get an AR relation of products in a certain category? I'm sure it should work the way I'm attempting but obviously I've gone wrong somewhere.
Update
I've been able to create a relation with this code:
#products = Product.where(:id => #category.product_ids)
Is this the correct way to do this? I still feel like my "column does not exist error" indicates I've done something incorrectly elsewhere and need to identify it.
ActiveRecord::StatementInvalid - PG::UndefinedColumn: ERROR: column
products.category does not exist
The error indicates that the column category doesn't exist in the products table. This is because you are trying to query with an non-existed column here in this line #products = Product.where(:category => #category)
Also, I would simply write it as #products = #category.products, but as you want the result as AR relation your current approach(#products = Product.where(:id => #category.product_ids)) looks fine to me.