Rails ActiveRecord join three tables and retrieve nested objects - ruby-on-rails

I have three model classes: Games, Teams, and Players:
class Game < ActiveRecord::Base
has_many :teams
end
class Team < ActiveRecord::Base
belongs_to :game
has_many :players
end
class Player < ActiveRecord::Base
belongs_to :team
end
class CreateGames < ActiveRecord::Migration
def change
create_table :games do |t|
t.string :play_date
t.string :date
t.timestamps null: false
end
end
end
class CreateTeams < ActiveRecord::Migration
def change
create_table :teams do |t|
t.integer :team_number
t.references :game, index: true
t.timestamps null: false
end
add_foreign_key :teams, :games
end
end
class CreatePlayers < ActiveRecord::Migration
def change
create_table :players do |t|
t.string :name
t.references :team, index: true
t.timestamps null: false
end
add_foreign_key :players, :teams
end
end
I am trying to write an Active Record query to return an object that contains nested objects for 1 game, M number of teams, N number of players. So, for example, I am expecting my returned data to look something like this:
{ "id" => "1", "play_date"=>"2016-01-28",
"teams" =>
[{"id" => "1", "game_id" => "1", "team_number" => "1",
"players" =>
[{"id" => "1", "team_id" => "1", "name" => "Jim"},
{"id" => "2", "team_id" => "1", "name" => "Bob"},
{"id" => "3", "team_id" => "1", "name" => "Sally"}
]
},
{"id" => "2", "game_id" => "1", "team_number" => "2",
"players" =>
[{"id" => "4", "team_id" => "2", "name" => "Ed"},
{"id" => "5", "team_id" => "2", "name" => "Molly"},
{"id" => "6", "team_id" => "2", "name" => "Tim"}
]
}
]
}
The closest I've been able to get this to working is the following:
#data = Player.select("players.*, teams.*").joins(team: :game).where(games: {id: params[:id]})
which returns something like:
[
#<Player id: 1, name: "Jim", team_id: 1, created_at: "2016-01-02 19:27:52", updated_at: "2016-01-02 19:27:52">,
#<Player id: 2, name: "Bob", team_id: 1, created_at: "2016-01-02 19:27:52", updated_at: "2016-01-02 19:27:52">,
#<Player id: 3, name: "Sally", team_id: 1, created_at: "2016-01-02 19:27:52", updated_at: "2016-01-02 19:27:52">,
#<Player id: 4, name: "Ed", team_id: 2, created_at: "2016-01-02 19:27:52", updated_at: "2016-01-02 19:27:52">,
#<Player id: 5, name: "Molly", team_id: 2, created_at: "2016-01-02 19:27:52", updated_at: "2016-01-02 19:27:52">,
#<Player id: 6, name: "Tim", team_id: 2, created_at: "2016-01-02 19:27:52", updated_at: "2016-01-02 19:27:52">
]
I've tried multiple variations of the above code to try to get a set of nested objects like in my expected output example, but I always get results that return only a single level of objects.
Can anyone help return results that contain nested objects? Thanks.

I guess I needed to take a break, I was able to figure this out now.
I think my issue was with relying on the .inspect command to check and see if my multiple levels of objects were being loaded. Turns out if I just simplified my query to:
#data = Game.joins(teams: :players).find(params[:id])
and then actually tried looping through the data in my view:
<% #data.teams.each do |team| %>
<%= team.players[0].name %>
<% end %>
This allowed me to access all of the data I needed. I'm guessing due to lazy loading (but maybe not) inspect doesn't show all of the data.

Related

Rails rolify gem /assosiation issue

I use rolify gem with devise for AdminUser
my Roles table
class RolifyCreateRoles < ActiveRecord::Migration
def change
create_table(:roles) do |t|
t.string :name
t.references :resource, :polymorphic => true
t.timestamps
end
create_table(:admin_users_roles, :id => false) do |t|
t.references :admin_user
t.references :role
end
add_index(:roles, :name)
add_index(:roles, [ :name, :resource_type, :resource_id ])
add_index(:admin_users_roles, [ :admin_user_id, :role_id ])
end
end
model 'Role'
class Role < ActiveRecord::Base
has_and_belongs_to_many :admin_users, :join_table => :admin_users_roles
belongs_to :resource,
:polymorphic => true
validates :resource_type,
:inclusion => { :in => Rolify.resource_types },
:allow_nil => true
scopify
end
my issue is when i want to get users witch belong to role it gives empty array instead of my adminuser object
u = AdminUser.first
u.add_role(:admin)
u.roles => #<Role id: 1, name: "admin", admin_user_id: 1, resource_id: nil, resource_type: nil, created_at: "2016-06-16 15:03:33", updated_at: "2016-06-17 09:04:30">
and when i do
Role.first=> #<Role id: 1, name: "admin", admin_user_id: 1, resource_id: nil, resource_type: nil, created_at: "2016-06-16 15:03:33", updated_at: "2016-06-17 09:29:32">
Role.first.admin_users => []
To check if a user has a global role:
user = User.find(1)
user.add_role :admin # sets a global role
user.has_role? :admin
=> true
Or to check at instance level In your case
u = AdminUser.first
u.add_role(:admin)
> Role.first=> #<Role id: 1, name: "admin", admin_user_id: 1,
> resource_id: nil, resource_type: nil, created_at: "2016-06-16
> 15:03:33", updated_at: "2016-06-17 09:29:32">
Instead of doing this => Role.first.admin_users => []
Try
u = AdminUser.first
u.roles
Rolify has a pretty clear documentation
Rolify
oh i fixed it by changing relation in my admin_user model
from has_many to has_and_belongs_to_many :roles

Why can't I chain these assocations?

Given I have this...
create_table "competitors", :force => true do |t|
t.integer "review_id"
end
create_table "questions", :force => true do |t|
t.integer "product_id"
end
create_table "reviews", :force => true do |t|
t.integer "product_id"
end
class Competitor < ActiveRecord::Base
belongs_to :review
def product
self.review.product
end
def questions
self.review.product.questions
end
end
class Review < ActiveRecord::Base
belongs_to :product
def questions
self.product.questions
end
end
class Product < ActiveRecord::Base
has_many :questions
has_many :reviews
end
class Question < ActiveRecord::Base
belongs_to :product
end
...then how is this even possible?
> #competitor.questions
=> []
> #competitor
=> #<Competitor id: 14, review_id: 7, name: "Colonial FirstState", url: "http://firststate.com.au", created_at: "2013-06-07 06:05:37", updated_at: "2013-06-07 06:05:37", logo_file_name: nil, logo_content_type: nil, logo_file_size: nil, logo_updated_at: nil, logo_processing: false, logo: nil>
> #review = #competitor.review
=> #<Review id: 7, product_id: 9, client_name: "BHP", url: "http://bhp.com.au", created_at: "2013-06-07 06:05:37", updated_at: "2013-06-07 06:05:37">
> #product = #competitor.review.product
=> #<Product id: 9, name: "Retail Site Search", created_at: "2013-06-07 05:54:31", updated_at: "2013-06-07 05:54:31", description: nil>
> #competitor.product
=> #<Product id: 9, name: "Retail Site Search", created_at: "2013-06-07 06:05:37", updated_at: "2013-06-07 06:05:37", description: nil>
> #product.questions
=> []
> Product.find(9).questions
Product Load (1.0ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT 1 [["id", 9]]
Question Load (0.3ms) SELECT "questions".* FROM "questions" WHERE "questions"."product_id" = 9
=> [#<Question id: 13... etc etc ....>]
In other words, when I find the product via it's ID, I can see the product's questions, but when I find the product at the end of chain, I can't see them.
You'll be much better off if you don't define your own questions methods, and instead use has_many :questions, through: :product. See http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association for more details.
The answer was the delegate method http://api.rubyonrails.org/classes/Module.html#method-i-delegate

Selecting Rails Models via Virtual Attributes

I have the two rails models Section & SectionRevision. A Section is mostly just a container that holds all the Revisions relating to itself. So most of the attributes for the Section are basically stored within the SectionRevision model so there's a history of Revisions that can be reverted back to at any time.
Sometimes I need to access the attributes for the latest revision from the Sections Model so I've created some Virtual Attributes to account for this.
Each model has the attributes as defined in these migrations:
Section:
class CreateSections < ActiveRecord::Migration
def change
create_table :sections do |t|
t.integer "page_id", :null => false
t.timestamps
t.datetime "deleted_at"
end
add_index("sections", "page_id")
add_index("sections", "current_revision_id")
end
end
SectionRevision:
class CreateSectionRevisions < ActiveRecord::Migration
def change
create_table :section_revisions do |t|
t.integer "section_id", :null => false
t.integer "parent_section_id"
t.integer "position"
t.string "title", :default => "", :null => false
t.text "body", :null => false
t.timestamps
end
add_index("section_revisions", "section_id")
add_index("section_revisions", "parent_section_id")
end
end
And the models:
SectionRevision:
class SectionRevision < ActiveRecord::Base
belongs_to :section, :class_name => 'Section', :foreign_key => 'section_id'
belongs_to :parent_section, :class_name => 'Section', :foreign_key => 'parent_section_id'
def parsed_json
return JSON.parse(self.body)
end
end
Section:
class Section < ActiveRecord::Base
belongs_to :page
has_many :revisions, :class_name => 'SectionRevision', :foreign_key => 'section_id'
has_many :references
def current_revision
self.revisions.order('created_at DESC').first
end
def position
self.current_revision.position
end
def parent_section
self.current_revision.parent_section
end
def children
Sections.where(:parent_section => self.id)
end
end
As you can see Section has a couple of virtual attributes like, parent_section,current_revision & position.
The problem being now I would like to create a virtual attribute, children that selects all sections where the virtual attribute parent_section.id is equal to self.id. Is this possible at all? I know the above code won't work as its doing a query for a column that doesn't exist - and I'm not sure how to access the Model instances from within the model 'Sections' doesn't appear to work.
Can a perform a selection based on virtual attributes?
I've updated the model based on ProGNOMmers answer and get the following:
class Section < ActiveRecord::Base
has_many :revisions, :class_name => 'SectionRevision',
:foreign_key => 'section_id'
#Need to somehow modify :child_revisions to only be selected if it is the section_id's current_revision?
has_many :child_revisions, :class_name => 'SectionRevision',
:foreign_key => 'parent_section_id'
has_many :children, :through => :child_revisions,
:source => :section
end
Circumstance 1: This works perfectly fine.
1.9.3p392 :040 > section
=> #<Section id: 3, page_id: 10, created_at: "2013-04-02 01:31:42", updated_at: "2013-04-02 01:31:42", deleted_at: nil>
1.9.3p392 :041 > sub_section
=> #<Section id: 4, page_id: 10, created_at: "2013-04-04 10:19:33", updated_at: "2013-04-04 10:19:33", deleted_at: nil>
1.9.3p392 :042 > revision1
=> #<SectionRevision id: 5, section_id: 4, title: "test", body: "[{\"type\":\"testbody\"}]", created_at: "2013-04-04 10:21:46", updated_at: "2013-04-04 21:55:10", position: 3, parent_section_id: nil>
1.9.3p392 :043 > revision2
=> #<SectionRevision id: 6, section_id: 4, title: "test", body: "[{\"type\":\"testbody\"}]", created_at: "2013-04-04 12:29:19", updated_at: "2013-04-04 21:55:15", position: 3, parent_section_id: 3>
1.9.3p392 :044 > sub_section.current_revision
SectionRevision Load (0.6ms) SELECT `section_revisions`.* FROM `section_revisions` WHERE `section_revisions`.`section_id` = 4 ORDER BY created_at DESC LIMIT 1
=> #<SectionRevision id: 6, section_id: 4, title: "test", body: "[{\"type\":\"testbody\"}]", created_at: "2013-04-04 12:29:19", updated_at: "2013-04-04 21:55:15", position: 3, parent_section_id: 3>
1.9.3p392 :045 > section.children
=> [#<Section id: 4, page_id: 10, created_at: "2013-04-04 10:19:33", updated_at: "2013-04-04 10:19:33", deleted_at: nil>]
Circumstance 2:
1.9.3p392 :021 > section
=> #<Section id: 3, page_id: 10, created_at: "2013-04-02 01:31:42", updated_at: "2013-04-02 01:31:42", deleted_at: nil>
1.9.3p392 :022 > sub_section
=> #<Section id: 4, page_id: 10, created_at: "2013-04-04 10:19:33", updated_at: "2013-04-04 10:19:33", deleted_at: nil>
1.9.3p392 :023 > revision1
=> #<SectionRevision id: 5, section_id: 4, title: "test", body: "[{\"type\":\"testbody\"}]", created_at: "2013-04-04 10:21:46", updated_at: "2013-04-04 10:24:22", position: 3, parent_section_id: 3>
1.9.3p392 :024 > revision2
=> #<SectionRevision id: 6, section_id: 4, title: "test", body: "[{\"type\":\"testbody\"}]", created_at: "2013-04-04 12:29:19", updated_at: "2013-04-04 12:29:19", position: 3, parent_section_id: nil>
1.9.3p392 :025 > sub_section.current_revision
SectionRevision Load (0.7ms) SELECT `section_revisions`.* FROM `section_revisions` WHERE `section_revisions`.`section_id` = 4 ORDER BY created_at DESC LIMIT 1
=> #<SectionRevision id: 6, section_id: 4, title: "test", body: "[{\"type\":\"testbody\"}]", created_at: "2013-04-04 12:29:19", updated_at: "2013-04-04 12:29:19", position: 3, parent_section_id: nil>
1.9.3p392 :026 > section.children
Section Load (0.6ms) SELECT `sections`.* FROM `sections` INNER JOIN `section_revisions` ON `sections`.`id` = `section_revisions`.`section_id` WHERE `section_revisions`.`parent_section_id` = 3
=> [#<Section id: 4, page_id: 10, created_at: "2013-04-04 10:19:33", updated_at: "2013-04-04 10:19:33", deleted_at: nil>]
In circumstance 2 I would like section.children to return => [] as sub_section.current_revision.parent_section_id = nil and not section.id.
In other words section.children should return all Sections where .current_revision.parent_section_id = section.id but I can't query that as .current_revision is a virtual attribute.
Is it possible to maybe turn Section.current_revision in to some sort of association? Or maybe the only way is to add a current_revision column to the sections table?
I think custom relationships are well suited for this cases:
class Section < ActiveRecord::Base
has_many :revisions, :class_name => 'SectionRevision',
:foreign_key => 'section_id'
has_many :child_revisions, :class_name => 'SectionRevision',
:foreign_key => 'parent_section_id'
has_many :children, :through => :child_revisions,
:source => :section
end
Section.find(42).children
#=> SELECT ... WHERE ... AND section_revisions.parent_section = 42
I didn't tried the code, there could be mistakes, but the idea should be correct.
I deleted the part about :conditions, since is not useful after the last edits
Looks like you should improve your model as ProGNOMmers stated; you could use some of following gems:
ancestry ( http://railscasts.com/episodes/262-trees-with-ancestry )
awesome_nested_set ( https://github.com/collectiveidea/awesome_nested_set )
But answering your question literally, you can try adding 'children' method to your SectionRevision model and delegate Section#children to current_revision.
class SectionRevision
def children
SectionRevision.where(:parent_section => self.id) # parent_section IS a column of SectionRevision
end
end
class Section
def children
current_revision.children
end
end
BTW you could use #delegate for delegating:
class Section
delegate :children, :position, :parent_section, to: :current_revision
def current_revision
Section.where(:parent_section => self.id)
end
end
http://apidock.com/rails/Module/delegate

Why aren't the methods generated by has_and_belongs_to_many bidirectional?

I have...
app/models/report.rb:
has_and_belongs_to :standards
app/models/standard.rb:
has_and_belongs_to :reports
db/schema.rb:
create_table "reports_standards", :id => false, :force => true do |t|
t.integer "report_id"
t.integer "standard_id"
end
When I log into rails console, all seems OK initially...
> #report = Report.create :name => "foo"
=> #<Report id: 2, name: "foo", created_at: "2013-02-21 03:10:06", updated_at: "2013-02-21 03:10:06">
> #standard = #report.standards.build :name => "bar"
=> #<Standard id: nil, name: "bar", created_at: nil, updated_at: nil>
> #report.standards
=> [#<Standard id: nil, name: "bar", created_at: nil, updated_at: nil>]
...but then it turns strange...
> #standard.reports
=> []
Isn't it meant to be:
> #standard.reports
=> [#<Report id: 2, name: "foo", created_at: "2013-02-21 03:10:06", updated_at: "2013-02-21 03:10:06">]
Why isn't it? How do I fix it?
you are running #report.standards.build :name => "bar" which only builds the record and not create it in the database. if you change build to create, you should be able to see the associations.

Is there a more efficient way to create an ActiveRecord relationship on a class that references itself through another table?

I apologize if the question title is confusing. I have the following situation: I have a Person model which stores people in the standard way in the people table.
I need to add a has_many relationship for emergency_contacts on the Person model. I tried doing this in the following way:
Migrations:
create_table :people do |t|
t.string :first
t.string :middle
t.string :last
t.timestamps
end
create_table :emergency_contacts, :id => false do |t|
t.integer :person_id
t.integer :emergency_contact_id
t.timestamps
end
Models:
class Person < ActiveRecord::Base
has_many :emergency_contacts
validates :first, :presence => true
validates :last, :presence => true
end
class EmergencyContact < ActiveRecord::Base
belongs_to :person
has_one :person, :foreign_key => 'emergency_contact_id'
end
This allows me to do:
ruby-1.9.2-p180 :001 > p = Person.new(first: "John", last: "Doe")
=> #<Person id: nil, first: "John", middle: nil, last: "Doe", created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :002 > ec = EmergencyContact.new
=> #<EmergencyContact person_id: nil, emergency_contact_id: nil, created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :003 > ec.emergency_contact = Person.new(first: "Peter", last: "Griffin")
=> #<Person id: nil, first: "Peter", middle: nil, last: "Griffin", created_at: nil, updated_at: nil>
ruby-1.9.2-p180 :004 > p.emergency_contacts << ec
=> [#<EmergencyContact person_id: nil, emergency_contact_id: nil, created_at: nil, updated_at: nil>]
ruby-1.9.2-p180 :005 > p.save!
=> true
However, I don't feel the EmergencyContact model should have to be there, since I am really just referencing a Person model anyway.
Is there a way to remove this "middle-man" model, so that I can just do something like:
p = Person.new(first: "John", last: "Doe")
p.emergency_contacts << Person.new(first: "Peter", last: "Griffin")
I'd really use self referential associations for this kind of purpose.
Se tutorial here: http://railscasts.com/episodes/163-self-referential-association

Resources