I am starting to learn more advanced associations, and polymorphic join table looks very interesting, but I've come to a bad limitation.
My models:
school_class.rb:
class SchoolClass < ActiveRecord::Base
has_many :class_of_schools, as: :school_model, dependent: :destroy
has_many :basic_primary_schools, through: :class_of_schools
has_many :basic_shugenja_schools, through: :class_of_schools
has_many :basic_monk_schools, through: :class_of_schools
end
class_of_school.rb:
class ClassOfSchool < ActiveRecord::Base
belongs_to :school_model, polymorphic: true
belongs_to :school_class
end
basic_primary_School.rb:
class BasicPrimarySchool < ActiveRecord::Base
has_many :class_of_schools, as: :school_model, dependent: :destroy
has_many :school_classes, through: :class_of_schools
end
shugenja_school and monk_schools, has the same association as basic_primary_school.
And join model it self:
class CreateClassOfSchools < ActiveRecord::Migration
def change
create_table :class_of_schools do |t|
t.integer :school_class_id
t.integer :school_model_id
t.string :school_model_type
t.timestamps null: false
end
end
end
It joins well on the school side, I can make school.class_schools, and I get the array of school_classes associated. But on the other side I can`t do the same. In fact when I check school_class.classes_of schools I get empty array.
I make associations in my seed file by function like this:
def join_schools_with_classes(school_object_name, school_classes_array)
school_object_name.all.each do |school|
school_classes = school_classes_array[school.name]
school_classes.each do |class_name|
school_class = SchoolClass.find_by(name: class_name)
school.class_of_schools.create( school_class_id: school_class.id)
end
end
end
My question:
How can I make this association works both ways? So I can call ClassSchool.first.class_of_schools returns, all objects associated to this object. And still be able to call BasicPrimarySchool.first.school_classes to get associated school_class objects.
Just remove as: :school_model in SchoolClass relation
has_many :class_of_schools, dependent: :destroy
EDIT
change the relation in SchoolClass to
has_many :basic_primary_schools, through: :class_of_schools, source: :school_model, source_type: "BasicPrimarySchool"
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 have three Models: Deal, Zipcode, DealIncludeZipcode.
Now, the association looks like below:-
Deal Model:
class Deal < ActiveRecord::Base
has_many :deal_include_zipcodes, dependent: :destroy
has_and_belongs_to_many :zipcodes, dependent: :destroy
accepts_nested_attributes_for :deal_include_zipcodes,:reject_if => :reject_include_zipcodes, allow_destroy: true
private
def reject_include_zipcodes(attributes)
if attributes[:deal_id].blank? || attributes[:zipcode_id].blank?
if attributes[:id].present?
attributes.merge!({:_destroy => 1}) && false
else
true
end
end
end
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
class DealIncludeZipcode < ActiveRecord::Base
belongs_to :deal
belongs_to :zipcode
end
Now in view I have a checkbox,on unchecking it I can select multiple zipcode to select from DealIncludeZipcode.But when I save the data it is not saving.
I have used migration for joining Zipcode and Deal Model in which my exclude zipcode functionality is working correctly.
Please provide a soloution.I have tried various method but didn't got succeed.
The whole point of has_and_belongs_to_many is that you don't have a model which joins the two parts.
class Deal < ActiveRecord::Base
has_and_belongs_to_many :zipcodes
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
Would join through a "headless" table called deals_zipcodes. If you want to have a join model you need to use has_many :through instead.
class Deal < ActiveRecord::Base
has_many :deal_zipcodes, dependent: :destroy
has_many :zipcodes, through: :deal_zipcodes
end
class DealZipcode < ActiveRecord::Base
belongs_to :deal
belongs_to :zipcode
end
class Zipcode < ActiveRecord::Base
has_many :deal_zipcodes, dependent: :destroy
has_many :deals, through: :deal_zipcodes
end
I think Max's right. So your migration should be
create_table :deals do |t|
t.string :name
...
end
create_table :zipcodes do |t|
t.string :zipcode
...
end
create_table :deals_zipcodes do |t|
t.belongs_to :deal, index: true
t.belongs_to :zipcode, index: true
end
And your models should be
class Deal < ActiveRecord::Base
has_and_belongs_to_many :zipcodes
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
You should probably take a look at the ActiveRecord guide, where you'll find more explanation.
I'm not sure how to create this, I'd like to create a many-to-many polymorphic association.
I have a question model, which belongs to a company.
Now the question can has_many users, groups, or company. Depending on how you assign it.
I'd like to be able to assign the question to one / several users, or one / several groups, or the company it belongs to.
How do I go about setting this up?
In this case I would add a Assignment model which acts as an intersection between questions and the entities which are assigned to it.
Create the table
Lets run a generator to create the needed files:
rails g model assignment question:belongs_to assignee_id:integer assignee_type:string
Then let's open up the created migration file (db/migrations/...__create_assignments.rb):
class CreateAssignments < ActiveRecord::Migration
def change
create_table :assignments do |t|
t.integer :assignee_id
t.string :assignee_type
t.belongs_to :question, index: true, foreign_key: true
t.index [:assignee_id, :assignee_type]
t.timestamps null: false
end
end
end
If you're paying attention here you can see that we add a foreign key for question_id but not assignee_id. That's because the database does not know which table assignee_id points to and cannot enforce referential integrity*. We also add a compound index for [:assignee_id, :assignee_type] as they always will be queried together.
Setting up the relationship
class Assignment < ActiveRecord::Base
belongs_to :question
belongs_to :assignee, polymorphic: true
end
The polymorpic: true option tells ActiveRecord to look at the assignee_type column to decide which table to load assignee from.
class User < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
class Group < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
class Company < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
end
Unfortunately one of the caveats of polymorphic relationships is that you cannot eager load the polymorphic assignee relationship. Or declare a has_many :assignees, though: :assignments.
One workaround is:
class Group < ActiveRecord::Base
has_many :assignments, as: :assignee
has_many :questions, through: :assignments
def assignees
assignments.map(&:assignee)
end
end
But this can result in very inefficient SQL queries since each assignee will be loaded in a query!
Instead you can do something like this:
class Question < ActiveRecord::Base
has_many :assignments
# creates a relationship for each assignee type
['Company', 'Group', 'User'].each do |type|
has_many "#{type.downcase}_assignees".to_sym,
through: :assignments,
source: :assignee,
source_type: type
end
def assignees
(company_assignees + group_assignees + user_assignees)
end
end
Which will only cause one query per assignee type which is a big improvement.
I have this relationship between users, teams
class CreateTeamsUsers < ActiveRecord::Migration
def change
create_table :teams_users, :id => false do |t|
t.references :user
t.references :team
t.timestamps
end
end
end
class User < ActiveRecord::Base
has_and_belongs_to_many :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :users
end
The issue is that I want to add extra attribute in HABTM,attribute name is "user_name"
How to do this?
Instead of HABTM you'd use has_many and has_many :through.
class User < ActiveRecord::Base
has_many :memberships
has_many :team, through: :membership
end
class Membership < ActiveRecord::Base # This would be your old 'join table', now a full model
belongs_to :user
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
Short version, you can't do what your're trying to do without a little refactoring. Here is how I would do it (apologies if there's syntax issues, I'm doing this from memory I haven't tested the code but the principle is sound)
Create a new model to represent "membership" of a team (maybe call it "Membership") and the associated migration to create the table:
class Membership
belongs_to :team
belongs_to :user
end
Then change your team and user models to use this new model:
class User
has_many :memberships
has_many :teams, through: :memberships
end
class Team
has_many :memberships
has_many :users, through: :memberships
end
Once you've refactored this far, adding additional columns / attributes to "memberships" is easy because you can just treat it like any other model.
in rails 3 i have 2 models: User,Event. User has_many Event through events_staffs and Event has_many Event through events_staffs.
class Staff < ActiveRecord::Base
has_many :events_staffs
has_many :events, through: :events_staffs
end
class Event < ActiveRecord::Base
has_many :events_staffs, dependent: :destroy
has_many :staffs, through: :events_staffs
end
i wish that an Event have an author and some members where author and members are record of staffs table.
I wish I could do in the console something like this:
e=Event.first #ok it works
e.author=Staff.first
e.members=Staff.all - [Staff.first]
is possible to do it?
SOLUTION
#Event model
has_many :events_staffs, dependent: :destroy
has_many :members, through: :events_staffs, source: :staff #http://stackoverflow.com/a/4632456/1066183
#http://stackoverflow.com/a/13611537/1066183
belongs_to :author,class_name: 'Staff'
#migration:
class AddForeignKeyToEventsTable < ActiveRecord::Migration
def change
change_table :events do |t|
t.integer :author_id
end
end
end
Yes, quite easy. Add an author_id integer column to your Event model, then update your code as follows:
class Event < ActiveRecord::Base
has_many :events_staffs, dependent: :destroy
has_many :staffs, through: :events_staffs
belongs_to :author, class_name: 'Staff'
end
As for members, I'd create another join table similar to your staffs_events table, but for members. Within that model you'd need to do the same belongs_to association as with Event where you specify the class_name for the member.