I'm playing with my first has_many through relationship. I have three models. Here they are.
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :pools
has_many :games,
through: :pools
end
~
class Pool < ActiveRecord::Base
has_many :users
has_many :games,
through: :users
end
~
class Game < ActiveRecord::Base
belongs_to :user
belongs_to :pool
end
Should this set up allow me to do the following in IRB?
u=User.first
u.games
When I do that, I get the following error.
SystemStackError: stack level too deep
from /Users/ShiftedRec/.rvm/rubies/ruby-2.0.0-p353/lib/ruby/2.0.0/irb/workspace.rb:86
Maybe IRB bug!
I should be able to do u.games, and u.pools?
it would also be nice to do a pool.users and pool.games.
How would I have to change my model set up so I can get access to those methods..?
I've been reading around but the has_many through is kind of confusing.
Here is my schema as well
ActiveRecord::Schema.define(version: 20140408165647) do
create_table "games", force: true do |t|
t.integer "pool_id"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "games", ["pool_id", "user_id"], name: "index_games_on_pool_id_and_user_id", unique: true
create_table "pools", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id" #vestige of old approach
end
create_table "users", force: true do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
I appreciate the help!
You were getting the error because your associations are defined incorrectly. Rails goofed up and went in recursion.
Update your models Pool and User as below:
class Pool < ActiveRecord::Base
has_many :games
has_many :users,
through: :games
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :games
has_many :pools,
through: :games
end
You are trying to create M-M Relationship between User and Pool through Game.
Also, remove user_id from pools table as you already have association through join table games.
Also, since you are on rails console(not IRB) after making above changes, reload the rails environment in console using reload! command.
Read more about has-many-through-associations in Rails guides.
Related
i'm learning Rails and i'm doing an exercise to practice associations and migration files.
Currently, trying to make a models between users, auction item, and bids.
So far for the migrate files I have the following:
class CreateItem < ActiveRecord::Migration
def change
create_table :auction do |t|
t.string :item_name
t.string :condition
t.date :start_date
t.date :end_date
t.text :description
t.timestamps
end
end
end
class CreateBids < ActiveRecord::Migration
def change
create_table :bids do |t|
t.integer :user_id
t.integer :auction_id
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.string :username
t.string :password_digest
t.timestamps
end
end
end
These are the following models:
class Bid < ActiveRecord::Base
belongs_to :bidder, class_name: "User", foreign_key: "bidder_id"
belongs_to :auction
end
class User < ActiveRecord::Base
has_many :bids
has_many :auctions, :foreign_key => 'bidder_id'
has_secure_password
end
class Auction < ActiveRecord::Base
belongs_to :seller, class_name: "User", foreign_key: :user_id
has_many :bids
has_many :bidders, through: :bids
end
Any suggestions or opinions? I'm currently trying to test the tables but auctions doesn't seem to be working...
Specifically, my auction table can't seem to find a user_id and therefore a user doesn't have any auctions.
foreign_key refers to the _id (by default) or any unique attribute used to associate the models.
I can't see bidder model, you need to replace them with user_id as they are associated to user model.
Refer for more details belongs_to
class CreateBids < ActiveRecord::Migration
def change
create_table :bids do |t|
t.integer :user_id **do not think this is correct**
t.integer :auction_id **or this one**
t.timestamps
end
end
end
You want to use something more along the lines of the following
class CreateGames < ActiveRecord::Migration[5.0]
def change
create_table :games do |t|
t.integer :total_time
t.references :version, foreign_key: true **#this is how a foreign key should be declared**
t.integer :total_points
t.timestamps
end
end
end
Alternatively, if you want to change things in future migrations you can always add a reference:
def change
add_reference :levels, :version, foreign_key: true
end
OK, so it's been years since I've written any ruby code, and my design may be incorrect. With that in mind, I'm writing a small utility to clone project entities in TargetProcess via REST. Target Process has a data model that allows for several types of parent:child relationships:
project:epic:feature:user_story
project:feature:user_story
project:user_story
However, all the entities are nearly identical from a data structure perspective, so it seemed to make sense to use STI and use models to define the relationships and inheritance. I've created a new Rails app with only these models to verify the error I'm getting when I attempt to associate an Epic with a Feature:
ActiveModel::MissingAttributeError: can't write unknown attribute `epic_id`
Here are the models:
class TargetProcessEntity < ActiveRecord::Base
end
class Project < TargetProcessEntity
has_many :epics
has_many :features
has_many :user_stories
end
class Project < TargetProcessEntity
has_many :epics
has_many :features
end
class Epic < TargetProcessEntity
belongs_to :project
has_many :features
end
class Feature < TargetProcessEntity
belongs_to :project
belongs_to :epic
has_many :user_stories
end
class UserStory < TargetProcessEntity
belongs_to :feature
belongs_to :project
end
Here is the schema:
ActiveRecord::Schema.define(version: 20150929122254) do
create_table "epics", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "target_process_entity_id"
t.integer "project_id"
end
add_index "epics", ["project_id"], name: "index_epics_on_project_id"
add_index "epics", ["target_process_entity_id"], name: "index_epics_on_target_process_entity_id"
create_table "features", force: :cascade do |t|
t.integer "project_id"
t.integer "epic_id"
t.integer "target_process_entity_id"
end
add_index "features", ["epic_id"], name: "index_features_on_epic_id"
add_index "features", ["project_id"], name: "index_features_on_project_id"
add_index "features", ["target_process_entity_id"], name: "index_features_on_target_process_entity_id"
create_table "projects", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "target_process_entity_id"
end
add_index "projects", ["id"], name: "index_projects_on_id"
add_index "projects", ["target_process_entity_id"], name: "index_projects_on_target_process_entity_id"
create_table "target_process_entities", force: :cascade do |t|
t.string "type", null: false
t.string "name"
t.text "description"
t.integer "source_remote_id"
t.float "numeric_priority"
t.integer "owner"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "cloned_remote_id"
t.string "resource_type"
t.integer "project_id"
end
create_table "user_stories", force: :cascade do |t|
t.integer "project_id"
t.integer "feature_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "target_process_entity_id"
end
add_index "user_stories", ["feature_id"], name: "index_user_stories_on_feature_id"
add_index "user_stories", ["project_id"], name: "index_user_stories_on_project_id"
add_index "user_stories", ["target_process_entity_id"], name: "index_user_stories_on_target_process_entity_id"
end
While Epic and Feature both have a project_id, an instance of Feature does not have an epic_id attribute; attempting to assign an epic to feature blows up:
[20] pry(main)> epic = Epic.new
=> #<Epic:0x007fcab6c80590
id: nil,
type: "Epic",
name: nil,
description: nil,
source_remote_id: nil,
numeric_priority: nil,
owner: nil,
created_at: nil,
updated_at: nil,
cloned_remote_id: nil,
resource_type: "Epic",
project_id: nil>
[21] pry(main)> feature = Feature.new
=> #<Feature:0x007fcab6d3ba48
id: nil,
type: "Feature",
name: nil,
description: nil,
source_remote_id: nil,
numeric_priority: nil,
owner: nil,
created_at: nil,
updated_at: nil,
cloned_remote_id: nil,
resource_type: "Feature",
project_id: nil>
[22] pry(main)> epic.save
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "target_process_entities" ("type", "resource_type", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["type", "Epic"], ["resource_type", "Epic"], ["created_at", "2015-10-02 15:18:13.351578"], ["updated_at", "2015-10-02 15:18:13.351578"]]
(4.6ms) commit transaction
=> true
[23] pry(main)> feature.epic = epic
ActiveModel::MissingAttributeError: can't write unknown attribute `epic_id`
from /Users/kcallahan/.rbenv/versions/2.0.0-p647/lib/ruby/gems/2.0.0/gems/activerecord-4.2.4/lib/active_record/attribute.rb:138:in `with_value_from_database'
[24] pry(main)>
I realize it is extremely possible I am either doing something wrong or have made a poor design decision; any input is hugely appreciated as I've not been able to find anything on this and have been banging my head against it for days!
OK, I got it working almost by mistake! I added the xxx_id columns to the target_process_entities table. I assumed that the STI tables would be able to respond to the relationship definitions, though my understanding of the inner workings of STI and relationships are rusty and incomplete at best...
I may be wrong but it looks like your Feature table is a join table for many to many relationship between Project and Epic.
If that's the case, your models might look like this
class Project < TargetProcessEntity
has_many :features
has_many :epics, through: :features
end
class Epic < TargetProcessEntity
has_many :features
has_many :projects, through: :features
end
class Feature < TargetProcessEntity
belongs_to :project
belongs_to :epic
has_many :user_stories
end
the source is implied if you use the same name
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
I'm building a rails app where I have models users, images, and image_pairs. I want each image_pair to have 2 images, named :before_image and :after_image.
So:
users have many images (many to one)
users have many image_pairs (many to one)
images may have only one image_pair
image_pairs always have 2 images
I have everything working, except I can't call #image.image_pair. I get:
PG::UndefinedColumn: ERROR: column image_pairs.image_id does not exist
How can I set this up so I can get an image_pair from its image?
schema.rb (some irrelevant fields removed)
create_table "image_pairs", force: true do |t|
t.integer "before_image_id"
t.integer "after_image_id"
end
add_index "image_pairs", ["before_image_id"], name: "index_image_pairs_on_before_image_id", using: :btree
add_index "image_pairs", ["after_image_id"], name: "index_image_pairs_on_after_image_id", using: :btree
add_index "image_pairs", ["user_id"], name: "index_image_pairs_on_user_id", using: :btree
create_table "images", force: true do |t|
t.integer "user_id"
t.integer "image_pair_id"
end
add_index "images", ["image_pair_id"], name: "index_images_on_image_pair_id", using: :btree
add_index "images", ["user_id"], name: "index_images_on_user_id", using: :btree
create_table "users", force: true do |t|
t.string "name"
end
user.rb
class User < ActiveRecord::Base
has_many :images, dependent: :destroy
has_many :image_pairs, dependent: :destroy
end
image.rb
class Image < ActiveRecord::Base
belongs_to :user
has_one :image_pair
end
image_pair.rb
class ImagePair < ActiveRecord::Base
belongs_to :user
belongs_to :before_image, :class_name => "image"
belongs_to :after_image, :class_name => "image"
end
You might add a field to image_pairs called image_id that points to the original image id. Or rename the before_image to image_id and add an alias or a method if you want to call it by the name before_image.
def before_image
self.image_id
end
I have an existing app with devise running. User and log in and out etc. Thought I would like to get localhost:3000/users/15 changed to localhost:3000/users/ruby-boy so I have installed https://github.com/norman/friendly_id
I have ran rails generate friendly_id but when I ran rails generate scaffold user name:string slug:string:uniq, it say I already got a user.rb file so ok I've added: rails g migration AddSlugToUser slug:string:uniq and rails g migration AddNameToUser name:string. So I've added those two columns to my users table.
In my controllers I've replaced:
User.find(params[:id])
with:
User.friendly.find(params[:id])
Then ran this in console (rails c)
User.find_each(&:save)
But User.find_each(&:save) gave:
(0.1ms) commit transaction
(0.0ms) begin transaction
=> nil
I think I've followed the docs "eye for eye". Have I missed anything as the links wont change.
PS: I have edited the user.rb per website on github.
My user.rb:
class User < ActiveRecord::Base
extend FriendlyId
friendly_id :name, use: :slugged
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :plan
has_one :profile
attr_accessor :stripe_card_token
def save_with_payment
if valid?
customer = Stripe::Customer.create(description: email, plan: plan_id, card: stripe_card_token)
self.stripe_customer_token = customer.id
save!
end
end
end
Schema for users table:
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "plan_id"
t.string "stripe_customer_token"
t.string "slug"
t.string "name"
end
Add :name to your sign up form and then validate for uniqueness in your User model
Class User
validates :name, uniqueness: true
This way same name can't be created more than once and you'll avoid avoid weird slugs.
These are my tables:
create_table :messages do |t|
t.integer :type
t.string :text
t.datetime :sent_date
t.string :sender
t.timestamps
end
create_table :users do |t|
t.integer :phone
t.string :fullname
t.string :profile_image
t.timestamps
end
create_table :send_tos do |t|
t.string :receiver
t.belongs_to :message
t.boolean :is_received
end
and these are the model classes:
class User < ActiveRecord::Base
has_many :send_tos, :foreign_key => 'receiver'
has_many :messages, :foreign_key => 'sender'
end
class Message < ActiveRecord::Base
belongs_to :user, :foreign_key => 'sender'
has_many :send_tos
end
class Send_to < ActiveRecord::Base
belongs_to :user, :foreign_key => "receiver"
belongs_to :message
end
When I run these commands on rails console:
m = Message.new
m.save
s = Send_to.new
s.message = m
s.save
m.send_tos
After command m.send_tos I get this error:
RuntimeError: Circular dependency detected while autoloading constant
SendTo
Why am I getting this error? What should I do to change it?
Change the name of the Send_to class to SendTo. This follows the naming convention that Rails assumes for its relationships. The name of the file it is in should be send_to.rb