【问题标题】:How to access model from two distinct foreign keys?如何从两个不同的外键访问模型?
【发布时间】:2019-01-21 09:56:56
【问题描述】:

我有两个相关的模型,Trip 和 Ride。一个行程可能有一个去程或回程。我的 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>

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


【解决方案1】:

尝试在路由表中添加键

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

然后在您的 Trip 和 Route 模型中

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 

【讨论】:

  • 我认为这行不通...为此,我将使用一个 trip_id 键,并且两个表相互引用这一事实会出现问题。此外,正如问题中所说,我需要在性能问题上保持going_route_idreturning_route_id 从我的trips 表访问
【解决方案2】:

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

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

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

【讨论】:

  • 是否存在一条路线可用于多次旅行的场景?似乎很有可能,因为您没有来自 Route -> Trip 管理数据的关联。在这种情况下,这将是有问题的,因为它只会关联到第一个行程,而您永远不会看到与其他行程的关联。这可能是您愿意承担的风险。
  • 路线只能一趟。但是,由于性能问题,我们需要能够从行程中快速访问路线(这就是选择going_route_idreturning_route_id 的原因)
【解决方案3】:

由于 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

【讨论】:

    【解决方案4】:

    您需要在 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 表来解决此问题。为了提高性能,请在类型和 id 上添加复合索引。

    【讨论】:

    • 对不起,如果我的定义不够清楚,我真的很难得到一个简洁的问题,但是您提供的模型远不能代表我们的数据:实际上,路线是,一些折线和时间信息,而行程是两条路线(和一些其他信息)的串联。对于您的第一个答案,如果找不到其他方法,我将使用它,但这将强制创建一个方法 trip,该方法将返回两者中不是 nil 的任何一个。
    • 我发现命名和模型相当混乱 - 所以虽然它可能无法解决您的确切领域问题,但一般原则仍然适用。 has_one / has_many 关联需要使用belongs_to 关联表指向模型上的单个外键。确实没有“动态”的方式来解决这个问题,因为关联是在类级别声明的 - 而不是每个实例。
    • has_one :favorite_trip, -&gt; (route) { where("going_route_id = :id OR returning_route_id = :id", id: route.id) } 将不起作用,因为pickle 是在JOIN 子句中引用了外键列。添加 where 子句并不能改变这一点。
    • 我添加了一个与 STI / polymorphism 接壤的替代解决方案。
    • 我想 STI 是要走的路。确保添加 type 列。如果您有现有记录,请确保更新它们并将类型列设置为 "Routes::Outbound"/"Routes::Return"
    猜你喜欢
    • 1970-01-01
    • 2017-10-13
    • 2015-12-04
    • 2018-11-11
    • 2019-09-28
    • 2013-07-10
    • 2016-12-07
    • 2010-10-07
    相关资源
    最近更新 更多