has_and_belongs_to_many relationship in ruby on rails - ruby-on-rails

I am trying to make a website for a tennis coach using ruby on rails. I am completely new to this and I am struggling with some of the terminology. The website has a login system where users can sign in and then sign up for different events that the tennis coach has created. So basically the users can go to many events and the events can have many users attending. I have made a has_and_belongs_to_many relationship between my users table and events table and the code is below:
Here is my migration:
class CreateEventsUsers < ActiveRecord::Migration
def change
create_table :events_users, :id => false do |t|
t.integer :event_id
t.integer :user_id
end
add_index :events_users, :event_id
add_index :events_users, :user_id
add_index :events_users, [:event_id, :user_id]
end
def self.down
drop_table :events_users
end
end
Here are my models:
class User < ActiveRecord::Base
has_and_belongs_to_many :events
end
class Event < ActiveRecord::Base
has_and_belongs_to_many :users
end
Here is my event form:
<li>
<%= link_to event.title, event %>
| <%= link_to "delete", event, method: :delete %>
| *insert sign up here*
</li>
Basically my question is, how would I make a form and a controller that would let a signed in user, sign up for one of the events in the database? I have been stuck on this for days. Any help would be greatly appreciated.

I would suggest using a has_many through instead of using has_and_belongs_to for scalability issues and is a suggested practice. Coming to your requirement, I did a similar thing but with users following other users and tried to adopt some of them for your requirement.
class Event < ActiveRecord::Base
has_many : Relationship
has_many : users, :through => :Relationships
end
class User < ActiveRecord::Base
has_many :Relationship
has_many :events, :through => :Relationships
//define other methods//
def follow!(event_id)
relationships.create!(event_id: event.id)
end
end
class Relationship < ActiveRecord::Base
attr_accessible :event_id
belongs_to :events
belongs_to :users
end
You need to create the relationship migration like this
class CreateRelationships < ActiveRecord::Migration
def change
create_table :relationships do |t|
t.integer :user_id
t.integer :event_id
t.timestamps
end
add_index :relationships, :user_id
add_index :relationships, :event_id
add_index :relationships, [:user_id, :event_id], unique: true
end
end
You can try adding a button on the events page view like this.
<%= form_for(current_user.relationships.build(evnt_id: #user.id)) do |f| %>
<div><%= f.hidden_field :event_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
Relationship controller can be something like this.
class RelationshipsController < ApplicationController
def create
#user = User.find(params[:relationship][:event_id])
current_user.follow!(#user)
respond_to do |format|
format.html { redirect_to #user }
end
end
end
This tutorial was very helpful for me in developing the application for users to follow other users. Hope it will help you too. Good luck !!
http://ruby.railstutorial.org/chapters/following-users#top

If you haven't discovered it already, you are in for a real treat if you checkout railscasts from Ryan Bates. Here is a tutorial on using checkboxes in a form with a HABTM association--actually he suggests, and you should consider it, moving to has_many, through relationship instead of HABTM.
Regardless, it should at least give you an idea of how you can make it work in your app. http://railscasts.com/episodes/17-habtm-checkboxes-revised. That episode may require a membership, however, the $9 bux/month is totally reasonable for what you're getting.

Related

associations without the user_id column

I created a FriendRequest.rb model in my Rails app with the following table columns.
create_table "friend_requests", force: true do |t|
t.integer "requesting_user_id"
t.integer "requested_friend_id"
t.datetime "created_at"
t.datetime "updated_at"
end
With relationships defined as you see below, I added this code to a /views/users/show.html.erb page show the friend requests that have been made for each user. However, I'm getting this error
PG::UndefinedColumn: ERROR: column friend_requests.user_id does not exist
because, I obviously didn't create a user_id column. Is there a way that I can make this code work by adding more information to the relationships? or should I scrap my work and do it differently?
<% for user in #user.friend_requests %>
<li><%= h user.name %></li>
<%= link_to "Add Friend", friend_requests_path(:friend_id => user), :method => :post %>
<% end %>
User.rb
has_many :friend_requests
FriendRequest.rb
belongs_to :user
Just change your has_many association for:
has_many :friend_requests, foreign_key: 'requesting_user_id'
By default, Rails will look for [model_name]_id in the other table, this is why it is looking for user_id, but by adding the foerign_key: option, you can override this default behaviour and tell Rails what is the name of the foreign_key you want to use.
You can use this configuration:
class User < ActiveRecord::Base
has_many :friend_requests
has_many :requesters, through: friend_requests
end
class FriendRequest < ActiveRecord::Base
belongs_to :requester, foreign_key: 'requesting_user_id'
belongs_to :requested, foreign_key: 'requested_friend_id'
validates :requester_id, presence: true
validates :requested_id, presence: true
end
Take a look at:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Look especially for the options :primary_key and :foreign_key

Rails model multiple has_many :through relationships output to json

I have the following data models and would like to render a json hash that includes information from each model. For example, client.id, client.name_first, client, name_last, every workout description for each client and each exercise description for each workout.
class Client < ActiveRecord::Base
belongs_to :account
belongs_to :trainer
has_many :programs
has_many :workouts, :through => :programs
end
class Workout < ActiveRecord::Base
has_many :programs
has_many :clients, :through => :programs
has_many :routines
has_many :exercises, :through => :routines
end
class Exercise < ActiveRecord::Base
has_many :routines
has_many :workouts, :through => :routines
end
My database migrations:
class CreateClients < ActiveRecord::Migration
def change
create_table :clients do |t|
t.integer :account_id
t.integer :trainer_id
t.string :name_first
t.string :name_last
t.string :phone
t.timestamps
end
end
end
class CreateWorkouts < ActiveRecord::Migration
def change
create_table :workouts do |t|
t.string :title
t.string :description
t.integer :trainer_id
t.timestamps
end
end
end
class CreateExercises < ActiveRecord::Migration
def change
create_table :exercises do |t|
t.string :title
t.string :description
t.string :media
t.timestamps
end
end
end
I am able to return the workouts for a particular client:
#client = Client.find(params[:id])
clients_workouts = #client.workouts.select('workouts.*,programs.client_id').group_by(&:client_id)
render json: clients_workouts
And I am able to return the exercises for a particular workout:
#workout = Workout.find(params[:id])
exercises_workouts = #workout.exercises.select('exercises.*, routines.workout_id').group_by(&:workout_id)
render json: exercises_workouts
However, I do not know how to return the data with information from all three tables (Client, Workout, Exercise) included (joined through Programs and Routines). Is this possible? And how is it done?
First, I'm not really sure what's happening in your query:
clients_workouts = #client.workouts.select('workouts.*,programs.client_id').group_by(&:client_id)
Is this not sufficient?
#client.workouts
Now, on to the answer... assuming I'm still following:
ActiveRecord offers a .to_json method, which is what's being implicitly called here. The explicit version would be e.g.
render json: clients_workouts.to_json
Knowing that, you can look up to_json in the api (here's some good documentation even though it shows as deprecated: http://apidock.com/rails/ActiveRecord/Serialization/to_json). But, basically, the answer is to start with the root object -- the client I believe -- and build the included objects and attributes/methods from there in the options hash.
render json: #client.to_json(include: { workouts: { include: :exercises } })
You can customize which attributes or methods are included from each related model if needed, just dig into the documentation a little. Have fun!
Very possible and their are different ways to optain this.
One, without any 3rd party library is to use includes, just as if you were solving an n+1 problem or…
Use a much cooler approach and use active model serializers
Active Model Serializers

Can not call my associate table in my view

Can not call my associate table in my view. have tried this. it is an application that only adds to the players. after the press "start game" and then he should come to the result view where the results of all players. then I will of course have the name of the "players" table and then the binding results from the "results" table. now in quire, I enter bidningen in the background as long as
view:
<% #playersname.each do |p|%>
<ul>
<li><%= p.name %></li>
<li><%= p.results.try(:result) %></li>
</ul>
<%end%>
Controller:
class ResultsController < ApplicationController
def index
#playersname = Player.all
end
end
Model:
class Result < ActiveRecord::Base
# attr_accessible :title, :body
has_many :players
end
migration:
class CreateResults < ActiveRecord::Migration
def change
create_table :results do |t|
t.string "result", :limit => 40
t.string "cal", :limit => 40
t.string "sum",:limit => 300
t.timestamps
end
end
end
The CreateResults migration miss the player_id column.
Do belongs_to :player in your Result class and correct the migration (or do another one).
[Edit] I wasn't clear enough : I think you inverted the relationship logic.
You actually did the "one result has several players".
I suppose you want that a player has several results ?
class Player
has_many :results
end
class Result
belongs_to :player
end
So you can do :
#myplayer = Player.all.first
#myplayer.results #it is an array of player's results
#myplayerresult = #myplayer.results.first
puts #myplayerresult.result
If you want a one-to-one relationship, consider replacing has_many :results by has_one :result and so you can do #myplayer.result to get your result.

Combining polymorphic association with many-to-many association... is this setup right?

I have a Genre model, and I want both videos to have many genres and profiles to have many genres. I also want genres to have many videos and genres to have many profiles. I understand the polymorphic and join table stuff, so I'm wondering if my code below will work as I intend it to. Also, I'd appreciate any advice on how to access things in my controller and views.
This is what I envision that the join table should look like (I don't think I need an elaborate :has :through association because all I need in the join table are the associations and nothing else, so the table won't have a model):
genres_videos_profiles:
-----------------------------------------------------
id | genre_id | genre_element_id | genre_element_type
Here's my genre.rb:
has_and_belongs_to_many :genre_element, :polymorphic => true
Here's video.rb:
has_and_belongs_to_many :genres, :as => :genre_element
Here's profile.rb:
has_and_belongs_to_many :genres, :as => :genre_element
Will this work as I intend it to? I'd like some feedback.
As far as I know HABTM associations can´t be polymorphic, I couldn´t find an example like yours in the API documentation. If you want only join tables, your code could look like this:
class Genre
has_and_belongs_to_many :videos
has_and_belongs_to_many :profiles
end
class Video
has_and_belongs_to_many :genres
end
class Profile
has_and_belongs_to_many :genres
end
And access it like Mike already wrote:
#genre.profiles
#profile.genres
#genre.videos
#video.genres
Migrations (for join tables only):
class CreateGenresVideosJoinTable < ActiveRecord::Migration
def self.up
create_table :genres_videos, {:id => false, :force => true} do |t|
t.integer :genre_id
t.integer :video_id
t.timestamps
end
end
def self.down
drop_table :genres_videos
end
end
class CreateGenresProfilesJoinTable < ActiveRecord::Migration
def self.up
create_table :genres_profiles, {:id => false, :force => true} do |t|
t.integer :genre_id
t.integer :profile_id
t.timestamps
end
end
def self.down
drop_table :genres_profiles
end
end
I think that has_and_belongs_to_many can be a bit difficult to follow when it comes to polymorphic (if it even works). So if you want to do the polymorhpic thing, then you can't use any "through" syntax:
class Genre < ActiveRecord::Base
has_many :genres_videos_profiles
end
class GenresVideosProfile
belongs_to :genre
belongs_to :genre_element, :polymorphic => true
scope :videos, where(:genre_element_type => "Video")
scope :profiles, where(:genre_element_type => "Profile")
end
And then you use it like:
# All genre elements
#genre.genres_videos_profiles.each do |gvp|
puts gvp.genre_element.inspect
end
# Only video genre elements
#genre.genres_videos_profiles.videos.each do |gvp|
puts gvp.genre_element.inspect
end
Check out that: http://blog.hasmanythrough.com/2006/4/3/polymorphic-through
For me it was perfect and clean!

eager loading with attributes in join model rails

My app uses a :has_many :through association, and I'm having trouble figuring out how to most efficiently load and display data from both ends of the association and the association itself.
Here are my classes:
class Person < ActiveRecord::Base
has_many :people_ranks
has_many :ranks, :through => :people_ranks
has_many :institutions_people
has_many :institutions, :through => :institutions_people
belongs_to :school
belongs_to :department
end
class Institution < ActiveRecord::Base
has_many :institutions_people
has_many :people, :through => :institutions_people
end
class InstitutionsPerson < ActiveRecord::Base
belongs_to :institution
belongs_to :person
end
and their corresponding models:
create_table :people, :force => true do |t|
t.string :name
t.string :degree
t.integer :year_grad
t.integer :year_hired
end
create_table :institutions, :force => true do |t|
t.string :name
t.integer :ischool
end
create_table :institutions_people, :id => false do |t|
t.integer :institution_id
t.integer :person_id
t.string :rel_type
end
I want to show a person's institution info with something like #person.year_hired, #person.institution.name, and #person.institution.institutions_people.rel_type (where rel_type is either "graduated" or "hired:), but I know that third part won't work. Using the following in the show bit in the person_controller:
#person = Person.find(params[:id], :include => [:school, :department, :institutions_people, :people_ranks, {:institutions_people => :institution}, {:people_ranks => :rank}])
gives me access to #person.institutions and #person.institutions_people, but how do I connect the rel_type attribute from the join to the person-institution relationship? (I'm coming from PHP and now how to build the SQL and loop through it there, but RoR has me stumped.)
I've looked for help under "eager loading" and "associations with :has_many :through", but I get answers about building the associations. My question is really about accessing the association's data after it exists. My app uses static data, and I'm not worried about the update, destroy, or create methods. Thank you for your help!
The way to access the data is through the institutions_people association. So, you would do something like:
me = Person.first
rel = me.institutions_people.first
And then in the view
<%= rel.rel_type %> from <%= rel.institution.name %>
Alternatively, you can give yourself a full list of institutions along with their info:
me = Person.first
And then in the view:
<% for ip in me.institutions_people %>
<%= ip.rel_type %> from <%= ip.institution.name %>
<% end %>

Resources