Rails simple user discussion self reference - ruby-on-rails

I want to create a simple discussion board, a discussion will contain a title, content, a user_id and a topic_id.
To allow a user to reply to a discussion I need to self reference the original discussion, though I'm not sure how to do this.
This is my migration
class CreateDiscussions < ActiveRecord::Migration
def change
create_table :discussions do |t|
t.text :post
t.references :user, index: true
t.references :topic, index: true
t.string :title
t.references :discussion, index: true
t.timestamps
end
end
end
and my discussion model;
class Discussion < ActiveRecord::Base
belongs_to :user
belongs_to :topic
belongs_to :discussion
end
a user will create a disussion, another user can reply to that discussion in which I'll store the original discussion_id so I can do something like #d = Discussion.where discussion_id: nil to find the top-level discussions, and
#d.each do |d|
#replies = Discussion.where discussion_id: d.id
end
is this along the right track?
(I don't know how to properly implement the names as described here http://guides.rubyonrails.org/association_basics.html#self-joins)
( I've just realised that it's probably better/easier separated into two models, discussions and replies, but I'd still like to know how to do it in a single model)

You can structure your Discussion class as follows:
class Discussion < ActiveRecord::Base
belongs_to :user
belongs_to :topic
has_many :replies, class_name: "Discussion", foreign_key: "replied_to_id"
belongs_to :master_discussion, class_name: "Discussion", foreign_key: "replied_to_id"
end
Migration file:
class CreateDiscussions < ActiveRecord::Migration
def change
create_table :discussions do |t|
t.text :post
t.references :user, index: true
t.references :topic, index: true
t.string :title
t.integer :replied_to_id, index: true
t.timestamps
end
end
end
You can now use #discussion.replies to access replies belonging to a discussion and #discussion.master_discussion to access the discussion record to which this reply belongs
EDIT
changed:
t.references :discussion, index: true
to:
t.integer :replied_to_id, index: true
I tested and it's all working:
[11] pry(main)> #discussion = Discussion.create(post:"first post", title: "first discussion")
=> #<Discussion id: 3, post: "first post", title: "first discussion", replied_to_id: nil, created_at: "2014-10-23 20:15:38", updated_at: "2014-10-23 20:15:38">
[12] pry(main)> #discussion.replies << Discussion.create(post: "post to first thread",title: "reply to first thread")
=> [#<Discussion id: 4, post: "post to first thread", title: "reply to first thread", replied_to_id: 3, created_at: "2014-10-23 20:17:06", updated_at: "2014-10-23 20:17:06">]
[13] pry(main)> #discussion.replies
=> [#<Discussion id: 4, post: "post to first thread", title: "reply to first thread", replied_to_id: 3, created_at: "2014-10-23 20:17:06", updated_at: "2014-10-23 20:17:06">]
[14] pry(main)> #reply = #discussion.replies.first
[15] pry(main)> #reply.master_discussion
Discussion Load (0.2ms) SELECT "discussions".* FROM "discussions" WHERE "discussions"."id" = ? LIMIT 1 [["id", 3]]
=> #<Discussion id: 3, post: "first post", title: "first discussion", replied_to_id: nil, created_at: "2014-10-23 20:15:38", updated_at: "2014-10-23 20:15:38">

Related

Rails 5 update has_many through associates models

I have following models
class TeamPlayer < ApplicationRecord
belongs_to :team
belongs_to :player
belongs_to :role
end
class Team < ApplicationRecord
has_many :team_players
has_many :players, :through => :team_players
end
class Role < ApplicationRecord
has_many :team_players
has_many :players, :through => :team_players
end
class Player < ApplicationRecord
has_many :team_players
has_many :teams, :through => :team_players
has_many :roles, :through => :team_players
end
Basically, I want to assign different roles to different players in a team.
id team_id player_id role_id
2 1 2 1
3 3 2 1
4 1 1 2
What should it look like in my teams_controller.rb to add new player with a role, to update a player with new role and to remove that player from my team?
This is only the start of a possible solution and it is pretty similar to what you have with some model and database validations added. Some of these validations ensure the uniqueness of every three-way relationship (FilledTeamRole), so either the error of attempting to create a duplicate record would need to be handled or you could filter the possible ids of each class that could be selected so that a duplicate cannot be created.
A complete solution would depend on what other associations you want between the Team, Player and Role classes other than one that requires all three. For example, do you want/need an association between Team and Player where a relationship exists between only those two classes without the necessity of a Role (TeamPlayer id: 1, team_id: 1, player_id: 1). If those relationships are desired, then additional code will be needed to achieve this, which I have and can provide as a suggestion.
As far as what your controller would look like, you could use the filled_team_roles controller (or perhaps create a dashboard controller), provide instance variables #teams, #players and #roles to populate drop-down menus for each class within a form to create the filled_team_roles relationship. You could also have additional forms within each of the other classes where, using two drop-downs instead of three with the third value the selected model id of the class whose controller the form is in (e.g. the edit action in the players_controller with drop-downs for team and role)
~/app/models/team.rb
class Team < ApplicationRecord
has_many :filled_team_roles, dependent: :destroy
validates :name, uniqueness: { scope: [:sport, :city] }
scope :by_name_asc, -> { order(name: :asc) }
end
~/app/models/player.rb
class Player < ApplicationRecord
has_many :filled_team_roles, dependent: :destroy
validates_uniqueness_of :ssn
scope :by_name_asc, -> { order(last_name: :asc, first_name: :asc) }
end
~/app/models/role.rb
class Role < ApplicationRecord
has_many :filled_team_roles, dependent: :destroy
validates_uniqueness_of :name
scope :by_name_asc, -> { order(name: :asc) }
end
~/app/models/filled_team_role.rb
class FilledTeamRole < ApplicationRecord
belongs_to :team
belongs_to :player
belongs_to :role
validates :team_id, presence: true
validates :player_id, presence: true
validates :role_id, presence: true
validates :team_id, uniqueness: { scope: [:player_id, :role_id] }
end
~/db/migrate/20170127041000_create_team.rb
class CreateTeam < ActiveRecord::Migration[5.0]
def change
create_table :teams do |t|
t.string :name
t.string :sport
t.string :city
t.string :state
t.string :country
t.timestamps null: false
end
add_index :teams, [:name, :sport, :city], unique: true
end
end
~/db/migrate/20170127041100_create_player.rb
class CreatePlayer < ActiveRecord::Migration[5.0]
def change
create_table :players do |t|
t.string :first_name
t.string :last_name, index: true
t.string :full_name_surname_first
t.string :ssn, index: { unique: true }
t.timestamps null: false
end
end
end
~/db/migrate/20170127041200_create_role.rb
class CreateRole < ActiveRecord::Migration[5.0]
def change
create_table :roles do |t|
t.string :name, index: { unique: true }
t.timestamps null: false
end
end
end
~/db/migrate/20170127051300_create_filled_team_role.rb
class CreateFilledTeamRole < ActiveRecord::Migration[5.0]
def change
create_table :filled_team_roles do |t|
t.timestamps null: false
t.references :team
t.references :role
t.references :player
end
add_index :filled_team_roles,
[:team_id, :player_id, :role_id],
unique: true,
name: 'index_filled_team_roles_unique_combination_of_foreign_keys'
end
end
~/db/seeds.rb
Team.create(name: 'Los Angeles Dodgers', sport: 'baseball', city: 'Los Angeles', state: 'CA', country: 'United States')
Team.create(name: 'New York Yankees', sport: 'baseball', city: 'New York', state: 'NY', country: 'United States')
Team.create(name: 'Chicago Cubs', sport: 'baseball', city: 'Chicago', state: 'IL', country: 'United States')
Team.create(name: 'St. Louis Cardinals', sport: 'baseball', city: 'St. Louis', state: 'MO', country: 'United States')
Player.create(first_name: 'Max', last_name: 'Walker', full_name_surname_first: 'Walker, Max', ssn: '123-45-6789')
Player.create(first_name: 'Homer', last_name: 'Winn', full_name_surname_first: 'Winn, Homer', ssn: '234-56-7890')
Player.create(first_name: 'Will', last_name: 'Steel', full_name_surname_first: 'Steel, Will', ssn: '345-67-8901')
Player.create(first_name: 'Lucky', last_name: 'Snag', full_name_surname_first: 'Snag, Lucky', ssn: '456-78-9012')
Role.create(name: 'pitcher')
Role.create(name: 'catcher')
Role.create(name: 'first baseman')
Role.create(name: 'second baseman')
Role.create(name: 'shortstop')
Role.create(name: 'third baseman')
Role.create(name: 'right fielder')
Role.create(name: 'center fielder')
Role.create(name: 'left fielder')
FilledTeamRole.create(team_id: 1, player_id: 1, role_id: 1)
FilledTeamRole.create(team_id: 2, player_id: 2, role_id: 2)
FilledTeamRole.create(team_id: 3, player_id: 3, role_id: 3)
FilledTeamRole.create(team_id: 4, player_id: 4, role_id: 4)

Ruby on Rails - Polymorphic Association with Categories

I'm currently trying to implement a Category model to my application. I'm trying to design it in a way that Users can have many Categories, and so can Groups.
The problem I'm running into is that I also want to be able to just have a normal list of Categories without them being assigned to any User or Group.
I was referencing rubyonrails.org/association_basics.
class CreateCategories < ActiveRecord::Migration[5.0]
def change
create_table :categories do |t|
t.string :name
t.text :description
t.references :categorizable, polymorphic: true, index: true
t.timestamps
end
end
end
class Category < ApplicationRecord
belongs_to :categorizable, :polymorphic => true
end
class User < ApplicationRecord
has_many :categories, :as => :categorizable
end
class Group< ApplicationRecord
has_many :categories, :as => :categorizable
end
I'm trying to create a new Category through rails c, but whenever I try to save, it rolls back my transaction probably because I'm missing some condition.
Category(id: integer, name: string, description: text, created_at: datetime, updated_at: datetime)
Category.create( :id => 1, :name => 'Category_1', :description => '' )
begin transaction
rollback transaction
I also feel like there is a better way to create a new Category, as I shouldn't be setting the id manually.
Thanks for your help.
In rails 5, whenever you define a belongs_to association, it is required to have the associated record present by default. You would see this when you look at the errors after trying to create the category object
category = Category.create(:name => 'Category_1', :description => '' )
category.errors.full_messages.to_sentence
If you want to be able to save a record without the belongs_to association, you would have to specify it explicitly
class Category < ApplicationRecord
belongs_to :categorizable, polymorphic: true, required: false
end
If you try to create a new category and see the error is that there needs to exists a categorizable record in order for the category to be created, an easy way to do it is to put the new object itself as the categorizable one and it should do the trick.
$ category = Category.new
=> #<Category id: nil, name: nil, description: nil, categorizable_type: nil, categorizable_id: nil, created_at: nil, updated_at: nil>
$ category.save
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> false
$ category.errors.full_messages
=> ["Categorizable must exist"]
$ category = Category.new(categorizable: category)
=> #<Category id: nil, name: nil, description: nil, categorizable_type: "Category", categorizable_id: nil, created_at: nil, updated_at: nil>
$ category.save
(0.1ms) begin transaction
SQL (1.3ms) INSERT INTO "categories" ("categorizable_type", "created_at", "updated_at") VALUES (?, ?, ?) [["categorizable_type", "Category"], ["created_at", 2017-01-15 00:08:55 UTC], ["updated_at", 2017-01-15 00:08:55 UTC]]
(0.7ms) commit transaction
=> true
This should help, Rails Cast on Polymorphic
https://www.youtube.com/watch?v=6l9EAuev16k
You can create polymorphic records with this...
`#category = #categorizable.User.new`
`#category = #categorizable.Group.new`
So you do not need the id.

ActiveRecord::Associations::CollectionProxy is empty before saving and reloading

I'm curious why my has_many :through association is not populated until after my object is saved and reloaded. It seems like all the data should be there for the joins to be constructed.
Schema:
ActiveRecord::Schema.define(version: 20140821223311) do
create_table "cats", force: true do |t|
t.string "name"
t.integer "human_id"
end
create_table "houses", force: true do |t|
t.string "address"
end
create_table "humen", force: true do |t|
t.string "name"
t.integer "house_id"
end
end
Models:
class Cat < ActiveRecord::Base
belongs_to :human, inverse_of: :cats
has_one :house, through: :human
has_many(
:siblings,
through: :house,
source: :cats
)
end
class Human < ActiveRecord::Base
has_many :cats, inverse_of: :human
belongs_to :house, inverse_of: :humans
end
class House < ActiveRecord::Base
has_many :humans, inverse_of: :house
has_many :cats, through: :humans
end
I've saved an instance of House and Human in the db. The seed file looks like this:
h = House.create(address: "123 Main")
Human.create(house_id: h.id, name: "stu")
I've been testing with this:
c = Cat.new(human_id: 1, name: "something awesome")
p c.siblings # => #<ActiveRecord::Associations::CollectionProxy []>
c.save
p c.siblings # => #<ActiveRecord::Associations::CollectionProxy []>
c.reload
p c.siblings # => #<ActiveRecord::Associations::CollectionProxy [#<Cat id: 2, name: "gizmo", created_at: "2014-08-21 22:37:07", updated_at: "2014-08-21 22:37:07", human_id: 1>]>
Any help on this would be greatly appreciated :)
here's a github repo if you want to play with it:
https://github.com/w1zeman1p/association_wat
It seems CollectionProxy is a bit too lazy. If you do:
c = Cat.new(human_id: 1, name: "something awesome")
c.save
c.siblings
You get what you expect.
So it might be the CollectionProxy being cached from your first call to .siblings before the cat is saved.

How to create has_many :through associations with presence validations using Rails 4, seeds.rb, and the console?

I am using a has_many :through association between a Musician and an Instrument through a MusiciansInstrument join table:
class Musician < ActiveRecord::Base
has_many :musicians_instruments
has_many :instruments, through: :musicians_instruments
end
class Instrument < ActiveRecord::Base
has_many :musicians_instruments
has_many :musicians, through: :musicians_instruments
validates :instrument_name, presence: true
end
class MusiciansInstrument < ActiveRecord::Base
belongs_to :musician
belongs_to :instrument
validate :began_playing, presence: true
end
I want to make sure that I know how many years each Musician has been playing each Instrument, so I have a required began_playing datetime field in my MusiciansInstrument table.
I would like to do this without accepts_nested_attributes_for if possible.
My schema.rb(minus timestamps):
create_table "musicians_instruments", force: true do |t|
t.integer "musician_id"
t.integer "instrument_id"
t.datetime "began_playing"
end
create_table "instruments", force: true do |t|
t.string "instrument_name"
end
create_table "musicians", force: true do |t|
end
My question is what is how to best create objects using this association when making seeds and querying objects in the console. I also need to get FactoryGirl making these, but first things first.
1) In the console (using the approach proposed in this Stack Overflow)
musician = Musician.new
#=> #<Musician id: nil, user_id: nil, created_at: nil, updated_at: nil>
instrument = Instrument.new(:instrument_name => 'Bass Guitar')
#=> #<Instrument id: nil, created_at: nil, updated_at: nil, instrument_name: "Bass Guitar">
instrument.save!
musician.musicians_instruments << MusiciansInstrument.create(:instrument => instrument, :began_playing => Time.now - 10.years)
#=> #<ActiveRecord::Associations::CollectionProxy [#<MusiciansInstrument id: 19, musician_id: nil, instrument_id: 15, began_playing: "2004-02-25 23:52:12">]>
musician.save!
#=> ActiveRecord::RecordInvalid: Validation failed: Instruments can't be blank
2) In seeds.rb:
m7 = Musician.create
i7 = Instrument.create(:instrument_name => 'Voice')
m7.musicians_instruments << MusiciansInstrument.create(:instrument => i7, :began_playing => Time.now - 10.years)
These console queries return:
Musician.includes(:instruments).where(instruments.instrument_name => "Voice") #=>NameError: undefined local variable or method `instruments' for main:Object
Musician.joins(:instruments).where(instruments.instrument_name => "Voice") #=>NameError: undefined local variable or method `instruments' for main:Object
The seeds.rb statements work, and do create the associated objects and populate the through table with a began_playing date, but the queries are wrong. Two ways to query the began_playing field on the has_many :through join table are:
MusiciansInstrument.joins(:musician).where.not(began_playing: nil)
#=> #<ActiveRecord::Relation [#<MusiciansInstrument id: 13, musician_id: 7, instrument_id: 7, began_playing: "1984-02-26 03:59:01">]>
MusiciansInstrument.joins(:instrument).where.not(began_playing: nil)
#=> #<ActiveRecord::Relation [#<MusiciansInstrument id: 13, musician_id: 7, instrument_id: 7, created_at: "2014-02-26 03:59:01", updated_at: "2014-02-26 03:59:01", began_playing: "1984-02-26 03:59:01">]>
Does anyone know how or if this could be queried from the Musician or Instrument objects? Something like this, which doesn't work:
Musician.joins(:musicians_instruments).where.not(:musicians_instruments.began_playing => nil)

ActiveRecord has_one relationship does not return in certain cases

Given three models that are each nested in each other. If I create the top-level object and build_* the other child objects, I can retrieve all child objects through the relationships before and after save() on the original instance. However, if I try to retrieve the 2nd level nested object after find(:id) the original parent it fails. I can retrieve the 1st level nested object, however. This usually happens in a controller, but I'll illustrate it in console output below.
What am I overlooking?
Thanks in advance!
~jpv
>> l = Lead.new :buyer_name => 'Kim Possible', :email => 'kim#possible.com', :phone => '7131231234'
>> l.build_buyer
>> l.buyer.name = 'kim buyer'
>> l.buyer
>> l.buyer.build_contact_detail
>> l.buyer.contact_detail.email = "kim-contact-detail#possible.com"
>> l.save #returns true
>> l.buyer #THIS WORKS
=> #<Buyer id: 1, name: "kim buyer", lead_id: 1>
>> l.buyer.contact_detail #THIS WORKS
=> #<ContactDetail id: 1, company_id: nil, buyer_id: 1, email: nil, address_line_1: nil, address_line_2: nil, city: nil, state: nil, postal_code: nil>
>> l2 = Lead.find(1)
=> #<Lead id: 1, company_id: nil, buyer_id: nil, public_lead_id: nil, buyer_name: "Kim Possible", company_name: nil, email: "kim#possible.com", phone: "7131231234">
>> l2.buyer #THIS WORKS AS EXPECTED
=> #<Buyer id: 1, name: "kim buyer", lead_id: 1>
>> l2.buyer.contact_detail #THIS BREAKS
=> nil
All the boilerplate stuff below:
class Lead
has_one :buyer
#...
end
class Buyer
has_one :contact_detail
belongs_to :lead
#...
end
class ContactDetail
belongs_to :buyer
#...
end
The appropriate foreign keys are in each of the "belongs_to" classes.
class CreateBuyers < ActiveRecord::Migration
def self.up
create_table :buyers do |t|
t.string :name
t.integer :lead_id
...
class CreateContactDetails < ActiveRecord::Migration
def self.up
create_table :contact_details do |t|
t.integer :buyer_id
I think you might be getting tripped up because you're expecting the object to eager load nested child objects. In general, you have to explicitly specify it for that to happen in the find. Try adding :include => [:borrower, {:borrower => :contact_detail}] to the options for the find and see if that works.
Does this work if you do...
l.buyer.name = 'kim buyer'
l.save
l.buyer.build_contact_detail
...
If so, this could be a rails bug with the ContactDetail object not really knowing who its daddy is at creation time.

Resources