如何从两个不同的外键访问模型?

时间:2018-08-14 13:54:04

标签: ruby-on-rails ruby postgresql ruby-on-rails-4 activerecord

我有两个相关的模型,旅行和乘车。行程可能有去程或回程。我的ActiveRecord模型如下:

class Route < ActiveRecord::Base
    has_one :trip
end

class Trip < ActiveRecord::Base
    belongs_to :going_route,     class_name: "Route"
    belongs_to :returning_route, class_name: "Route"
end

但是,当我要从路线访问旅行时,这给我带来了一个问题:

Route.first.trip

这会引发PostgreSQL错误:

  

PG :: UndefinedColumn:错误:列trips.route_id不存在

我如何告诉Route班学生,他的旅行是否在going_route_idreturning_route_id下?也许还有另一种解决方法?

P.S:我一直在寻找类似的问题,有很多问题,但没有一个完全像这个问题,可以解决我的问题。如果您有关于如何使差异变得更清晰的提示,尤其是标题。 Here是一个类似的问题


编辑:

我也尝试过使用lambda,如matthew's duplicate proposition

class FavoriteRoute < ActiveRecord::Base
    has_one :favorite_trip, -> (route) { where("going_route_id = :id OR returning_route_id = :id", id: route.id) }
end

这将引发相同的错误。而且如果我假设我应该使用find_by而不是where,因为我只需要一个结果,那么我会有另一个我不明白的错误:

  

NoMethodError:#<旅行:0x00007f827b9d13e0>的未定义方法'except'

4 个答案:

答案 0 :(得分:2)

您需要在belongs_to关联的反侧指定外键-意味着引用外键的has_one / has_many端:

class Trip < ActiveRecord::Base
    # specifying foreign_key here is not needed since AR
    # will deduce that its outbound_route_id
    belongs_to :outbound_route,
      class_name: "Route"
    belongs_to :return_route, 
      class_name: "Route"
end

class Route < ActiveRecord::Base
  has_one :trip_as_outbound,
    class_name: 'Trip',
    foreign_key: :outbound_route_id
  has_one :trip_as_returning,
    class_name: 'Trip',
    foreign_key: :return_route_id

  def trip
    trip_as_outbound || trip_as_returning
  end
end

解决此问题的一种方法是使用“单表继承”:

class Route < ApplicationRecord 
end

class Routes::Outbound < ::Route
  self.table_name = 'routes'
  has_one :trip, foreign_key: :outbound_route_id
end

class Routes::Return < ::Route
  self.table_name = 'routes'
  has_one :trip, foreign_key: :return_route_id
end

class Trip < ApplicationRecord
  belongs_to :outbound_route,
    class_name: '::Routes::Outbound'
  belongs_to :return_route,
    class_name: '::Routes::Return'
end

哪一个会给您正确的行程,但有些奇怪之处,例如Routes::Return.all会给您与Route.all相同的结果。

可以通过在type表中添加routes字符串列来解决此问题。为了提高性能,请在type和id上添加复合索引。

答案 1 :(得分:1)

由于goingreturning路线之间没有功能上的差异,因此请考虑在旅程和路线之间建立has_many关系。这将使路线可用于其他旅行,并为您提供所需的东西。

注意:这种方法存在缺陷,因为您使用的是多对多关系。这意味着一次旅行可能有多于一条的往返路线。您可以通过Trip模型中的代码进行管理,或者,如果您想为任一方向生成“多站”路线,这可能还不错。

您将生成一个名为trip_routes的模型。

trip_routes迁移可能看起来像这样:

create_table :trip_routes do |t|
   t.integer :trip_id
   t.integer :route_id
   t.string  :route_type
   t.boolean :favorite
end

# Consider this part based on how you think your indexes are best built, I'm 
# just making note that DB performance can be impacted particularly on these
# two fields.
add_index :trip_routes, :trip_id
add_index :trip_routes, :route_id

您的trip_route模型如下所示:

class TripRoute < ActiveRecord::Base
    belongs_to :trip
    belongs_to :route

    # This model knows whether it's the 'going' or 'returning' route, so do your 
    # route functionality here.
end

然后,您的trip模型将如下所示:

class Trip < ActiveRecord::Base
    has_many :trip_routes
    has_many :route, through: trip_routes

    # Helper to get the going route
    def going_trip_route
        self.trip_routes.find_by(route_type: "going")
    end

    # Helper to get the going route
    def returning_trip_route
        self.trip_routes.find_by(route_type: "returning")
    end

end

您的route模型如下所示:

class Route < ActiveRecord::Base
    has_many :trip_routes
    has_many :trips, through: trip_routes
end

答案 2 :(得分:0)

在看到所有可能的解决方案和我们的需求之后,我们选择了下一个解决方案:

class Route
    def trip
        @trip ||= Trip.find_by("going_route_id = :id OR returning_route_id = :id", id: id)
    end
end

我认为这不是最好的解决方法,而且感觉很棘手。但是,这是最快的实现,没有性能问题。此解决方案的另一个问题是没有Rails验证。

答案 3 :(得分:0)

尝试在路由表中添加密钥

add_column :routes, :going_key,     :integer
add_column :routes, :returning_key, :integer

然后在您的行程和路线模型中

class Route < ActiveRecord::Base
  belongs_to :going_route, foreign_key: :going_key, class_name: Trip
  belongs_to :returning_route, foreign_key: :returning_key, class_name: Trip
end

class Trip < ActiveRecord::Base
end

Route.first.going_route 
Route.first.returning_route