rails 3 association without table - ruby-on-rails

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.

Related

Making two model from one model

I am building doctor-appointment site. I have User model and I want to divide it into two parts: physician and patient. I have tried:
class User < ActiveRecord::Base
end
class Appointment < ActiveRecord::Base
belongs_to :physician, class_name: "User"
belongs_to :patient, class_name: "User"
end
class Physician < User
has_many :appointments
has_many :patients, through: :appointments, source: "User"
end
class Patient < User
has_many :appointments
has_many :physicians, through: :appointments, source: "User"
end
PROBLEM: how to get physician_id and patient_id column in appointsments table?
I've chosen this way, because when Physician and Patient models are separated, the registration will be problem (I think so...).
I think you're on the right path and only need to run a migration to add references to physician and patient on appointments table.
Although, you wouldn't need to add the source attribute to either the patients or physician has_many through anymore as those would be inferred by Rails!
I think I also prefer this approach of having the Physician and the Patient model, although not for registration purposes, but for single responsibility!
Let me know if that works
add_reference :appointments, :patient, foreign_key: true
add_reference :appointments, :physician, :foreign_key: true
These two migrations should add foreign keys to your table. Check the docs to make sure my syntax is absolutely correct, though.
In addition to the changes dpalazzari suggested I believe you will want to make the following changes:
class Appointment < ActiveRecord::Base
belongs_to :physician, class_name: "Physician", inverse_of: :appointments
belongs_to :patient, class_name: "Patient", inverse_of: :appointments
end
class Physician < User
has_many :appointments, foreign_key: :physician_id, inverse_of: :physician
has_many :patients, through: :appointments, source: "User"
end
class Patient < User
has_many :appointments, foreign_key: :patient_id, inverse_of: :patient
has_many :physicians, through: :appointments, source: "User"
end

Is the first has_many redundant? - Ruby on Rails Active records associations

Rails 4.2 newbie:
2 Questions;
1) Is the first has_many redundant? Since its name is a plural of Save Class?
can I have only:
has_many :savers, through: :saves, source: :saver
Or even better;
has_many :savers, through: :saves
If the answer is yes, where can I set "dependent: :destroy"?
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, class_name: "Save", foreign_key: "saver_id", dependent: :destroy
has_many :savers, through: :saves, source: :saver
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end
2) This is the typical blog model, where user can 'save' posts posted by another user to their timeline. Does this model make use best practices? Specially in db performance, doing a Join to get posts saved by a User. The 'Save' table that will have 100MM rows?
Lets first alter your example a bit to make the naming less confusing:
class User
has_many :bookmarks
has_many :posts, through: :bookmarks
end
class Post
has_many :bookmarks
has_many :users, through: :bookmarks
end
class Bookmark
belongs_to :user
belongs_to :post
end
Lets have a look at the query generated when we do #user.posts
irb(main):009:0> #user.posts
Post Load (0.2ms) SELECT "posts".* FROM "posts" INNER JOIN "bookmarks" ON "posts"."id" = "bookmarks"."post_id" WHERE "bookmarks"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
Now lets comment out has_many :bookmarks and reload:
class User
# has_many :bookmarks
has_many :posts, through: :bookmarks
end
irb(main):005:0> #user.posts
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :bookmarks in model User
So no, the first has_many is not redundant - in fact its the very core of how has_many through: works. You setup a shortcut of sorts through another relation.
Note in has_many :posts, through: :bookmarks :bookmarks is the name relation we are joining through. Not the table which contains the joins.
To fix your original code you would need to do:
class Post < ActiveRecord::Base
has_many :saves, dependent: :destroy
has_many :savers, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :saver, class_name: "User"
belongs_to :post # A join table with only one relation is pretty worthless.
validates :saver_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts
has_many :saves, dependent: :destroy
has_many :posts, through: :saves
end
Note that you don't need half the junk - if you have has_many :savers, through: :saves ActiveRecord will look for the relation saver by itself. Also you only want to use dependent: destroy on the join model - not on the post relation as that would remove all the posts a user has "saved" - even those written by others!
Teaching Rails myself, I want to learn the professional way to use the framework and following Rails guidelines best practices. That's not easy, because I usually find answers that 'just works'
I'll try to answer myself and maybe it could be useful for Rails Newbies:
Using has_many through, association, Rails firstly infers the association by looking at the foreign key of the form <class>_id where <class> is the lowercase of the class name, in this example; 'save_id'.
So, if we have the column name 'save_id', we will have the following simplified model:
class Post < ActiveRecord::Base
belongs_to :user
has_many :saves, through: :saves
end
class Save < ActiveRecord::Base
belongs_to :savers, class_name: "User"
validates :save_id, presence: true
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
...
end

Rails 4 has_many :through association with :source

I was following the answer laid out at the link below to set up a many_to_many relationships on my Rails 4 app. (New to rails, here.)
Implement "Add to favorites" in Rails 3 & 4
I have Users and Exercises, and I want users to be able to have Favorite Exercises. I created a join table called FavoriteExercise with user_id and exercise_id as columns. I've got it populating, and it seems to be working fine, but I'm not able to use it to call directly to my favorites. 
Meaning, I want to type:
user.favorite = #list of exercises that have been favorited
I get this error when I try to load that list in my browser:
SQLite3::SQLException: no such column: exercises.favorite_exercise_id:
SELECT "exercises".* FROM "exercises" INNER JOIN "favorite_exercises"
ON "exercises"."favorite_exercise_id" = "favorite_exercises"."id"
WHERE > "favorite_exercises"."user_id" = ?
My models:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, source: :exercises
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorites
has_many :favorited_by, through: :favorite_exercises, source: :exercises
class FavoriteExercise < ActiveRecord::Base
has_many :exercises
has_many :users
I just tried switching FavoriteExercise to 'belongs_to' instead of 'has_many, because it seems maybe that's the way that should go? but then I get this error:
uninitialized constant User::Exercises
Just trying to figure out how to set up the tables and associations so I can call .favorites on a user and get all their favorites.
If you want the list of exercises of the user and at the same, the list of favorite exercise of the user, then I think your join table should just be users_exercises wherein it will list all the exercises by the users. To list the favorite exercises, just add a boolean field indicating if the exercise is a user favorite and add a :scope to get all the favorite exercises.
So in your migration file:
users_exercises should have user_id, exercise_id, is_favorite
Then in your model:
class User < ActiveRecord::Base
has_many :workouts
has_many :users_exercises
has_many :exercises, through: :users_exercises
scope :favorite_exercises, -> {
joins(:users_exercises).
where("users_exercises.is_favorite = ?", true)
}
class Exercise < ActiveRecord::Base
has_many :workouts, :through => :exercises_workouts
has_many :users_exercises
has_many :users, through: :users_exercises
class UsersExercise < ActiveRecord::Base
belongs_to :exercise
belongs_to :user
You just need to simplify your model logic as follow:
class User < ActiveRecord::Base
has_many :workouts
has_many :exercises
has_many :favorite_exercises
has_many :favorites, through: :favorite_exercises, class_name: "Exercise"
class Exercise < ActiveRecord::Base
belongs_to :user
has_many :workouts, :through => :exercises_workouts
has_many :favorite_exercises
has_many :favorited_by, through: :favorite_exercises, class_name: "User"
class FavoriteExercise < ActiveRecord::Base
belongs_to :favorited_by
belongs_to :favorite
Then you can call user.excercises or excercise.users in your User/Excercise instance.
user.excercises = #list of exercises that have been favorited
Is that the many-to-many relationship you want?

Add extra field in Rails habtm joins

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.

Designing Two Models to Have A Complex Association

I have two tables, users and groups. An user owns a group and can be apart of multiple groups. A group belongs to one user and can have many users.
Thus for my user model I have
has_and_belongs_to_many :groups
has_many :groups
While for my group model I have
has_and_belongs_to_many :users
belongs_to :user
I also have a join table in my migrations..
def change
create_table :groups_users, :id => false do |t|
t.integer :group_id
t.integer :user_id
end
end
My question is does this make sense? I feel like I'm doing something wrong by having has_many and belongs_to on top of has_and_belongs_to_many.
The way I would approach this, and this is my own personal methodology, is with 3 tables/models like so:
group_user.rb
class GroupUser < ActiveRecord::Base
attr_accessible :user_id, :group_id
belongs_to :group
belongs_to :user
end
group.rb
class Group < ActiveRecord::Base
attr_accessible :owner_id
validates_presence_of :owner_id
has_many :group_users
has_many :users, through: :group_users
end
user.rb
class User < ActiveRecord::Base
attr_accessible :some_attributes
has_many :group_users
has_many :groups, through: :group_users
end
Then, whenever you create a Group object, the User that created it would have its id placed in the owner_id attribute of Group and itself into the GroupUser table.
Another option, so as to not have multiple foreign keys pointing to the same relationship, is to use a join model and then add a flag on the join model to denote if the user is the owner.
For example:
class User < ActiveRecord::Base
has_many :memberships
has_many :groups, through: :memberships
has_many :owned_groups, through: memberships, conditions: ["memberships.owner = ?", true], class_name: "Group"
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
#This model contains a boolean field called owner
#You would create a unique constraint on owner, group and user
end
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
has_one :owner, through: :memberships, conditions: ["memberships.owner = ?", true], class_name: "User"
end

Resources