How do I model this relationship in Rails - ruby-on-rails

I'm a bit new to rails so if this is very basic please bear with me.
I am creating something like a chat application. My model looks like this (from schema.rb)
create_table "people", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "session_id"
end
create_table "sessions", :force => true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.integer "leader_id"
end
I've set up the basic relationship I want -- which is session_id is a foreign key in the people table as such:
class Person < ActiveRecord::Base
belongs_to :session
end
class Session < ActiveRecord::Base
has_many :people
end
I want the leader_id to now be the person who is the "host" of the session (therefore its a foreign key to exactly one person). How do I setup an additional association so I can do:
session = Session.find_by_id(1)
host = Person.new(:name => 'joebob')
session.leader = host

You can tell Rails that the leader_id is really a Person id by specifying the class name in the belongs_to association:
class Session < ActiveRecord::Base
belongs_to :leader, :class_name => "Person"
end

Related

Referencing model from one model instance

This is my Schema.rb
Schema.rb
ActiveRecord::Schema.define(version: 20170617073406) do
create_table "actions", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "posts", force: :cascade do |t|
t.string "post_id"
t.datetime "timestamp"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.string "user_id"
t.datetime "last_activity"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
These are my three model classes.
Action.rb
class Action < ApplicationRecord
belongs_to :post
has_many :users, :foreign_key => 'user_user_id'
end
Post.rb
class Post < ApplicationRecord
has_many :actions, :foreign_key => 'action_id'
end
User.rb
class User < ApplicationRecord
has_many :actions, :foreign_key => 'action_id'
end
I am trying to add an instance of Action object into Post model object.
post = Post.find(post_id=post_id)
current_action = post.actions.find_or_create_by(name: "like")
It gives me the following error:
SQLite3::SQLException: no such column: actions.action_id: SELECT "actions".* FROM "actions" WHERE "actions"."action_id" = ? AND "actions"."name" = ? LIMIT ?
I am new to Ruby on Rails and come from Django background. Please help me figure this out.
Action needs to be related to the user as many to many...
If you want a many-to-many association, you will need to use another table (i.e. a join model):
class Action < ApplicationRecord
belongs_to :post
has_many :user_actions
has_many :users, through: :user_actions
end
class User < ApplicationRecord
has_many :user_actions
has_many :actions, through: :user_actions
end
class UserAction < ApplicationRecord
belongs_to :action
belongs_to :user
end
The ID I want to store is longer than Integer can do.
You can specify your own id in the migration and avoid adding an extra action_id:
class CreateActions < ActiveRecord::Migration[5.1]
def change
create_table :actions, id: false do |t|
t.string :id, null: false
t.timestamps
end
add_index :actions, :id , unique: true
end
end
With the above setup, you don't need to specify any foreign_key in Post either, ActiveRecord will use defaults (i.e. action_id):
class Post < ApplicationRecord
has_many :actions
end
A note about associations and foreign keys (and why you got that error):
Whenever you create an association, the foreign_key must be created in the table with the belongs_to side, since ActiveRecord will look for that key there.
Even If you don't specify a belongs_to, a has_many reference to that table will still look for that foreign_key.
So, when you add
has_many :actions, :foreign_key => 'action_id'
you are telling ActiveRecord to look for action_id column in actions table, but that columns has not being created in actions table.
In the proposed solution, the foreign keys are on the join table model (i.e. UserActions), so you must create a migration to include them:
rails g migration CreateUserActions user:references action:references
Solution:
Run migrations in command line:
rails generate migration add_post_to_actions post:belongs_to
rake db:migrate
Then update:
class Action < ApplicationRecord
belongs_to :post
# ...
end
class Post < ApplicationRecord
self.primary_key = 'post_id'
has_many :actions
# ...
end
class User < ApplicationRecord
self.primary_key = 'user_id'
# ...
end
Explanation:
1st line would add post_id column to actions table, and then index it with foreign constraint
The above migrations are independent of the contents of your current model files. You can even delete your models/action.rb or models/user.rb, and you'll see that the migrations would still even run without problems, because migrations only "do" stuff on the actual current database. The migration files also do not even care about whatever is written in your schema.rb, although it will update that schema.rb each time you run a migration (after the database has already been migrated/updated).

Active Record how to add records to has_many :through association in rails

I have quite the Problem with my has_many :through association.
The models look like this:
class User < ActiveRecord::Base
has_many :roles
has_many :datasets, through: :roles
has_secure_password
end
class Role < ActiveRecord::Base
belongs_to :user
belongs_to :dataset
end
class Dataset < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
end
I want to add a record to roles every time a new Dataset is created. It should
be created with the new Dataset, an existing User 'Admin' and the column name of Role should be set to 'Admin'.
I tried everything I found on Stackoverflow but nothing works for me.
My create method in the DatasetController looks this:
def create
#dataset = Dataset.new(dataset_params)
#dataset.save
#user = User.find_by(name: 'Admin')
#dataset.users << #user
##dataset.roles.create(user: #user, dataset: #dataset, name: 'Admin')
respond_with(#dataset)
end
I tried both the << operator and the create method.
the first results in:
ActiveRecord::UnknownAttributeError in DatasetsController#create
unknown attribute: dataset_id
the second in:
ActiveModel::MissingAttributeError in DatasetsController#create
can't write unknown attribute `user_id
Does anyone know why I get these errors?
my schema.rb:
create_table "datasets", force: true do |t|
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "roles", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "name"
t.string "mail"
t.string "password_digest"
t.datetime "created_at"
t.datetime "updated_at"
end
Your Role model needs columns to refer to which User and Dataset it belongs. Without these it has no idea who belongs to who.
So you simply need to create a migration to add these columns:
class AddRefererColumnsToRole < ActiveRecord::Migration
def change
add_column :roles, :user_id, :integer
add_column :roles, :dataset_id, :integer
end
end

How to set up my model-relations properly

I have 3 models: Hacks, Votes, Users.
A user can create many hacks.
Each user should be able to vote on each hack ONCE (Rating of 1-5. Rating should be updateable in case of a missclick or whatever).
I thought about the following relations:
Hack.rb
belongs_to :user
User.rb
has_many :hacks
Votes.rb
belongs_to :user
belongs_to :hack
Is that correct or am I missing something?
I thought about getting all the votes like this later on:
Hack.first.votes
What kind of foreign-keys do I have to set up?
In my schema.rb I already successfully set my users<=>hack relation up, without any foreign keys.
ActiveRecord::Schema.define(version: 20141019161631) do
create_table "hacks", force: true do |t|
t.string "url"
t.string "name"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", force: true do |t|
t.string "email", null: false
t.string "crypted_password", null: false
t.string "salt", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "role"
end
end
Thank you very much in advance!
I think this is what you want to have.
class User.rb
has_many :votes
has_many :hacks, through: :votes
end
class Vote.rb
belongs_to :user
belongs_to :hack
end
class Hack.rb
has_many :votes
end
With this, a hack has many votes through a user.
foreign keys:
votes table: user_id, hack_id
You should be able to do hack.votes
EDIT:
I edited the model to reflect a normal has many through relationship
user -> vote <- hack
a user has many votes
a user has many hacks through votes
a hack has many votes
foreign keys live in the votes table. You can use the following when creating the votes table to indicate the foreign key
t.references user
t.references hack

find by reference

so i have 2 models:
create_table "holders", :force => true do |t|
t.string "faceid"
t.integer "badges_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "holders", ["badges_id"], :name => "index_holders_on_badges_id"
create_table "badges", :force => true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
i need 2 things:
to get all the badges of a certain faceid holder
to get all the holders of a certain badge.
i know its really noobs question but until now i didnt work with references so i dont really understood from the literature how to make the connection.
You actually need a many to many association on your holder and badge models. So you have to options either use has many :through or use has_and_belongs_to_many. The difference between the two can be found here. I am taking the example for has_many :through.
You need to create three models.
class Holder < ActiveRecord:Base
has_many :badges_holders
has_many :badges, :through => :badges_holders
end
class Badge < ActiveRecord:Base
has_many :badges_holders
has_many :holders, :through => :badges_holders
end
class BadgesHolder < ActiveRecord:Base
belongs :badge
belongs :holder
end
And your migration files needs to be:
create_table "holders", :force => true do |t|
t.string "faceid"
t.timestamps
end
add_index "holders", ["badges_id"], :name => "index_holders_on_badges_id"
create_table "badges", :force => true do |t|
t.string "name"
t.text "description"
t.timestamps
end
create_table "badges_holders", :force => true do |t|
t.integer "holder_id"
t.integer "badge_id"
t.timestamps
end
Now you can easily use Holder.find_by_faceid('xyz').badges to find the all hedges held by the holder whose faced is xyz. And Badge.first.holders to get all the holders for the first bedge.
For your question HABTM will be a good option as you do not need any extra field in the join table, so you can just use has_and_belongs_to_many in both of your models and you don't need BadgesHolder model in that case. And for the migration of the join table, replace first line with create_table "badges_holders", :id => false, :force => true do |t| a and remove t.timestamps as the join table for HABTM should not have any other column than the foreign keys.
If it's some Ruby on Rails, you must have 2 models :
class Holder < ActiveRecord:Base
has_many :badges
end
class Badge < ActiveRecord:Base
belongs_to :holder
end
Your entry called badges_id should not be in your holders table ; you should have a holder_id on your "badges" table.
Then, you can simply call
Holder.find_by_faceid('foobar').badges
and
Badge.find(1337).holder
If your badge can belongs to many holders, then you have to write a has_and_belongs_to_many relation.

Unable to define Active Record Associations properly

I have got the following models,
class City < ActiveRecord::Base
belongs_to :state
belongs_to :country
end
class State < ActiveRecord::Base
belongs_to :country
has_many :cities
end
class Country < ActiveRecord::Base
has_many :states
has_many :cities, :through => :state
end
This is my schema.rb,
create_table "cities", :force => true do |t|
t.string "name"
t.string "state_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "countries", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "states", :force => true do |t|
t.string "name"
t.string "country_id"
t.datetime "created_at"
t.datetime "updated_at"
end
This is my seed data,
country_in = Country.create(name: 'India')
state_ap = country_in.states.create(name: 'Andhra Pradesh')
state_mh = country_in.states.create(name: 'Maharashtra')
city_hyd = state_ap.cities.create(name: 'Hyderabad')
state_ap.cities.create([{name: 'Tirupathi'}, {name: 'Visakhapatnam'}])
state_mh.cities.create([{name: 'Mumbai'}, {name: 'Pune'}, {name: 'Thane'}])
Problem
When I try to find all cities under "India" using
country_in.cities
I get this error : ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :state in model Country
When I try to find which country the city "Hyderabad" is, using
city_hyd.country , I get nil
Why are the links between cities and countries not present?
Are my associations wrong is there something else I missed out on?
The missing link here is the following (see the "Rails Guides for Associations"):
class Country < ActiveRecord::Base
has_many :states
has_many :cities, :through => :states
end
class City < ActiveRecord::Base
belongs_to :state
def country
state ? state.country : nil
end
end
My changes were the following:
has_many :through needs the plural form, not the singular.
If you use belongs_to in City, there needs to be a state_id defined in the table (which would be redundant). So I have replaced it through a getter method. A corresponding setter method is not possible.
I hope that now works for you.

Resources