Dealing with associations in rails for has_many through…but two join tables deep

风格不统一 提交于 2019-12-24 11:33:52

问题


Edit 4. Something in my explanation is just not going right... attached is a (very poorly done graphic) that shows the tables and how the schema is, and then my main question is given what I have that a provider is joined via a group location id via the provider_location table, which the group location holds the group and ultimately the group name. This is what I want to know, building a table like this how do I get to the group name? all the way from a provider. It is like I need a has_many through through. (BIG NOTE: Provider Address and Group Address in the image is really Provider Location and Group Location)

EDIT x 3: Thanks @mrshoco. It got me closer, now its like something else with my structure is not quite right.... I get this error when running provider_test_location.rb

vagrant@precise32:/vagrant/ipanmv2$ rake test test/models/provider_location_tes
t.rb
Run options: --seed 18117

# Running:

E

Finished in 0.190900s, 5.2383 runs/s, 5.2383 assertions/s.

  1) Error:
ProviderLocationTest#test_fetching_a_group_name_for_a_provider:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: provider_
locations.group_id: SELECT groups.group_name FROM "groups" INNER JOIN "provider_
locations" ON "provider_locations"."group_id" = "groups"."id" INNER JOIN "provid
ers" ON "providers"."id" = "provider_locations"."provider_id" WHERE "providers".
"first_name" = 'Shane'
    test/models/provider_location_test.rb:37:in `puts'
    test/models/provider_location_test.rb:37:in `puts'
    test/models/provider_location_test.rb:37:in `block in <class:ProviderLocatio
nTest>'

1 runs, 1 assertions, 0 failures, 1 errors, 0 skips

Here is the complete unit test

require 'test_helper'

class ProviderLocationTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end

   test "adding a provider to a group location" do
    group = Group.new
    group.group_name = 'My Awesome'
    group.save
    adr = Address.new
    adr.city = 'Las Cruces'
    adr.state = 'New Mexico'
    adr.zip = '88012'
    adr.address_line_one = '382 Dark side of moon'
    adr.address_line_two = ''
    adr.save    

    gl  = GroupLocation.new
    gl.group = group
    gl.address=adr      
    gl.save


    prv = Provider.new
    prv.first_name = 'Shane'
    prv.last_name = 'Thomas'
    prv.save

    pl = ProviderLocation.new      
    pl.provider = prv
    pl.group_location = gl   ###############ISSUE

    assert pl.save, 'Provider location did not save'

    puts Group.joins(:providers).where(providers: { first_name: 'Shane' }).select('groups.group_name')




  end
end

The models are following:

class Provider < ActiveRecord::Base
    belongs_to :designation
    belongs_to :specialty
    has_many :provider_locations
    has_many :invoices
    has_many :groups, through: :provider_locations
end

class ProviderLocation < ActiveRecord::Base
  belongs_to :provider
  belongs_to :group_location
end

class Group < ActiveRecord::Base

    #validations
    validates :group_name, presence: true


    #associations
    has_many :providers, through: :provider_locations
    has_many :invoices
    has_one  :billing
    has_many :addresses, through: :group_locations


    has_many :group_locations
    belongs_to  :billing_address, class_name: 'Address', foreign_key: 'billing_address_id'
    belongs_to  :mailing_address, class_name: 'Address', foreign_key: 'mailing_address_id'
    has_and_belongs_to_many :insurances
    has_many :provider_locations



end


class GroupLocation < ActiveRecord::Base
  belongs_to :address
  belongs_to :group

end

class Address < ActiveRecord::Base
    has_many :group_locations
    has_many :billings
end

回答1:


I've created a project which illustrates solution of your problem and it works. You can find it here https://github.com/dimakura/stackoverflow-projects/tree/master/32260679-dealing-with-associations. Below is description of the same concepts, so browsing the repository is completely optional.

Actually there are two ways to solve your problem both of them are usable in appropriate context.

But let's first create tables and models.

Create tables

I simplified your tables, omitting irrelevant details. Below are scripts for creating them.

groups table:

create_table :groups do |t|
  t.string :group_name

  t.timestamps null: false
end

addresses table:

create_table :addresses do |t|
  t.string :city
  t.string :state
  t.string :zip
  t.string :address_line_one
  t.string :address_line_two

  t.timestamps null: false
end

group_locations table:

create_table :group_locations do |t|
  t.references :group, index: true, foreign_key: true
  t.references :address, index: true, foreign_key: true

  t.timestamps null: false
end

providers table:

create_table :providers do |t|
  t.string :first_name
  t.string :last_name

  t.timestamps null: false
end

provider_locations table:

create_table :provider_locations do |t|
  t.references :provider, index: true, foreign_key: true
  t.references :group_location, index: true, foreign_key: true

  t.timestamps null: false
end

Models

Below are relations between Address, Group and GroupLocation models:

class Address < ActiveRecord::Base
  has_many :group_locations
end

class Group < ActiveRecord::Base
  has_many :group_locations
end

class GroupLocation < ActiveRecord::Base
  belongs_to :group
  belongs_to :address
  has_many :provider_locations
end

ProviderLocation is also simple:

class ProviderLocation < ActiveRecord::Base
  belongs_to :provider
  belongs_to :group_location
end

Relatively complicated one is Provider:

class Provider < ActiveRecord::Base
  has_many :provider_locations
  has_many :group_locations, through: :provider_locations
  has_many :groups, through: :group_locations
end

You can see now, that from a given Provider you can easily navigate down to Groups.

Let's now test our setup.

Testing

We have definition of your problem in this request:

... how to get all the groups a provider might belong to through the provider locations through group locations...

As I said in the begining there are two ways to solve your problem, depending on what's given.

Let's first assume we have a provider selected and want all groups related to this single provider.

class ProviderLocationTest < ActiveSupport::TestCase
  def setup
    group = Group.create(group_name: 'My Awesome')
    adr = Address.create(city: 'Las Cruces', state: 'New Mexico', zip: '88012', address_line_one: '382 Dark Side of the Moon', address_line_two: '')
    gl =GroupLocation.create(group: group, address: adr)
    @provider = Provider.create(first_name: 'Shane', last_name: 'Thomas')
    pl = ProviderLocation.create(provider: @provider, group_location: gl)
  end

  test 'getting groups for a single provider' do
    assert_equal ['My Awesome'], @provider.groups.map{ |group| group.group_name }
  end
end

It's a great way when working with a single provider.

But there is another scenario, when we want to select all groups belonging to more than one provider. Even worse, we might be given request: "Give us all groups for providers whose age not exceeds 50".

In this case we can exploit direct joining.

Below we illustrate, how to use joining when we want to get groups for all providers whose first name is Shane.

test 'getting groups based on arbitrary query' do
  group_names = Group.joins(group_locations: [ provider_locations: :provider]).where("providers.first_name=?", 'Shane').map{ |group| group.group_name }
  assert_equal ['My Awesome'], group_names
end



回答2:


# Group name given a provider name
Group.joins(:providers).where('providers.first_name' => 'Shane').select('groups.name')

# address/location of the provider given the group name
Provider.joins(:groups).where("groups.name" => "your_group_name").first.provider_locations.first



回答3:


Previous answer is correct

Group.joins(:providers).where(providers: { first_name: 'Shane' }).select('groups.name')

But you have an error 'no such table groups_providers' because in Provider model there is has_and_belongs_to_many :groups so rails are searching for providers_groups or groups_providers model. Instead of that you should write has_many :groups, through: :provider_locations and belongs_to :group in ProviderLocation model




回答4:


Let me try to understand, the way i see it you have 5 tables, three which represent models:

  • Provider
  • Group
  • Address

And another two that are join-tables:

  • Provider-location
  • Group-location

If I understand correctly, You have this because a group has many addresses and so does a provider. Also a provider has a relationship with group directly because it is not affected by the addresses, what i think you are missing is a the join table for groups_providers, this of course would need a migration to create said table. in which case you would have something of sort:

class Provider < ActiveRecord::Base
  has_many_and_belongs_to_many :groups
end

If however. the relationship between provider and group is dependent on the address only, the relationship between provider and groups passes through address:

class Provider < ActiveRecord::Base
  has_many :addresses
  has_many :groups, though: addresses
end

# This is not tested, just illustrating the idea.

It is also possible that the the relationship, between groups, providers and addresses is dependent on the ids for two of the three embedded?, in this case I'd like to know more for the reasoning behind this design.



来源:https://stackoverflow.com/questions/32260679/dealing-with-associations-in-rails-for-has-many-through-but-two-join-tables-de

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!