Ruby模型关系 - 三向关系

时间:2017-09-16 21:46:43

标签: ruby-on-rails ruby ruby-on-rails-3 activerecord

我有一个有3个特定型号的应用程序;用户,国家和城市。每种方法的理想关系如下:

用户

  • has_many:countries
  • has_many:cities,through :: country

国家

  • has_many:用户
  • has_many:cities

城市

  • has_many:用户
  • belongs_to:country

这些关系中的大多数都是相当直接的,然而,用户与城市的关系给了我一些问题。我希望能够为用户分配城市,只要用户已被分配到与城市相关联的国家/地区。现在我甚至无法为用户分配一个城市,更不用说为限制制定正确的逻辑了。

到目前为止,我能够阅读和搜索的内容已经导致了以下内容。

User.rb

class User < ApplicationRecord

  before_save { self.email = email.downcase}
  #attr_accessible :username, :email
  validates_confirmation_of :password
  has_secure_password

  validates :username, presence: true, length: { maximum: 25 }, uniqueness: {case_sensitive: false}
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }
  validates :password, presence: true, confirmation: true, length: { minimum: 6 }
  validates :password_confirmation, presence: true

  has_many :trips
  has_many :countries, through: :trips
  has_many :cities, through: :trips

end

Country.rb

class Country < ApplicationRecord

  has_many :trips
  has_many :cities
  has_many :users, through: :trips

end

City.rb

class City < ApplicationRecord

  has_many :trips
  belongs_to :country

end

Trip.rb

class Trip < ApplicationRecord

  belongs_to :country
  belongs_to :user
  belongs_to :city

end

1 个答案:

答案 0 :(得分:0)

  

希望能够为用户分配城市,只要用户具有   被分配到与该城市相关的国家

你不能也不想。

创建一个国家/地区。创建一个城市并将其分配给一个国家/地区。创建旅行或访问,分配该访问或到城市旅行然后将用户添加到该旅行或访问。瞧!你有你需要的关系!我想。

一个城市属于一个国家 旅行属于一个城市 访问属于一个城市 用户有很多次访问 用户有很多次旅行 因为旅行和访问都属于一个城市,所以你可以通过连接两边的关系来拥有,因此访问和旅行表需要城市和用户ID。 而且你拥有所需的关系和所需的逻辑。

请勿尝试做出违反要求的事情,例如直接将城市加入用户,因为无法将城市分配给用户。事实上,为了让您更加头疼,您甚至可能希望在许多国家的许多城市进行多次访问。在这种情况下,您希望将访问分配给不是城市的旅行。

这是一个逻辑问题,而不是编码问题。

更新回应评论

class User < ApplicationRecord
  has_many :trips
  has_many :cities, through: :trips
end

class Trip < ApplicationRecord
  belongs_to :user
  belongs_to :city
end

class City < ApplicationRecord
  has_many :trips
  has_many :users, through: :trips
end

然后在控制台

Loading development environment (Rails 5.1.4)
2.4.0 :001 > u = User.create(name: "Fred")
   (0.0ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Fred"], ["created_at", "2017-09-17 14:24:30.720005"], ["updated_at", "2017-09-17 14:24:30.720005"]]
   (16.3ms)  commit transaction
 => #<User id: 1, name: "Fred", created_at: "2017-09-17 14:24:30", updated_at: "2017-09-17 14:24:30"> 
2.4.0 :004 > c = City.create(name: "Leeds")
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "cities" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "Leeds"], ["created_at", "2017-09-17 14:26:19.293166"], ["updated_at", "2017-09-17 14:26:19.293166"]]
   (33.9ms)  commit transaction
 => #<City id: 1, name: "Leeds", created_at: "2017-09-17 14:26:19", updated_at: "2017-09-17 14:26:19"> 
2.4.0 :005 > t = Trip.create(name: "T1", user: u, city: c) 
   (0.2ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "trips" ("user_id", "city_id", "name", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["user_id", 1], ["city_id", 1], ["name", "T1"], ["created_at", "2017-09-17 14:28:44.656390"], ["updated_at", "2017-09-17 14:28:44.656390"]]
   (26.0ms)  commit transaction
 => #<Trip id: 1, user_id: 1, city_id: 1, name: "T1", created_at: "2017-09-17 14:28:44", updated_at: "2017-09-17 14:28:44"> 
2.4.0 :006 > t.user.name#
 => "Fred" 
2.4.0 :007 > t.city.name
 => "Leeds" 
2.4.0 :008 > t.name
 => "T1" 

这就是正常情况如何通过工作,但你想在旅行模型上有一个很多的城市,所以我会更新它将如何工作

好的,要实现下一个旅程有许多城市和城市有多次旅行的阶段,请注意关系两侧的has_many,你需要一个新的交叉参考表,可能叫做trip_city_xref,其复合主键是city_id和trip_id

这是在Rails术语中我讨厌的has_and_belongs_to_many关系。我的观点是,has_and_belongs_to_many是Rails唯一出错的地方。这是一个捷径。其他人会不同意,但这就是我用一种没有那种捷径的语言来设置它的方式。我认为它更清晰,更不模糊。所以我会设置这样的东西

class Trip < ApplicationRecord
  belongs_to :user
  belongs_to: :city_trip_xref and  
  has_many :cities, through: :city_trip_xref
end

class City < ApplicationRecord
  has_many :trips, through: :city_trip_xref
  has_many :users, through: :trips
end

class CityTripXref < ApplicationRecord
  belongs_to :trip
  belongs_to :city
end

这只是我的意见所以请随时阅读has_and_belongs_to_many here 那么你现在对你的用户做了什么?我再次更新

更新User.cities 所以你的用户模型看起来像这样

class User < ApplicationRecord
  has_many :trips
  has_many :city_trip_xrefs, through: :trips
  def cities
    trips.map(&:city_trip_xrefs).flatten.map(&:city)
  end
end

要获得用户在旅途中访问的所有城市,您可以展平并映射关系 你= User.first u.trips.map(安培;:city_trip_xrefs).flatten.map(安培;:城市)

因此,您可以在您的用户类上创建一个方法,使用上面的用户模型中显示的城市。至于将一个城市分配给用户,你无论如何都不想直接这样做。你会设置一个旅行,为这次旅行添加用户和城市,然后你拥有在现实世界的例子中你可能需要的所有功能,除非你的要求有所不同

使用控制台中的上述创建记录以及city_trip_xref表中的记录将所有内容组合在一起,以加入多个到城市的行程以及现有城市的行程

首先在控制台中创建外部参照

 ctx = CityTripXref.new
 => #<CityTripXref id: nil, city_id: nil, trip_id: nil, created_at: nil, updated_at: nil> 
2.4.0 :008 > ctx.city = City.first
 => #<City id: 1, name: "Leeds", created_at: "2017-09-17 14:26:19", updated_at: "2017-09-17 14:26:19"> 
2.4.0 :009 > ctx.trip = Trip.first
 => #<Trip id: 1, user_id: 1, city_id: 1, name: "T1", created_at: "2017-09-17 14:28:44", updated_at: "2017-09-17 14:28:44"> 
2.4.0 :010 > ctx.save

接下来获取第一个用户第一次旅行的第一个城市的名称

2.4.0 :028 > u = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
 => #<User id: 1, name: "Fred", created_at: "2017-09-17 14:24:30", updated_at: "2017-09-17 14:24:30"> 
2.4.0 :029 > u.cities.first.name
  Trip Load (0.1ms)  SELECT "trips".* FROM "trips" WHERE "trips"."user_id" = ?  [["user_id", 1]]
  CityTripXref Load (0.1ms)  SELECT "city_trip_xrefs".* FROM "city_trip_xrefs" WHERE "city_trip_xrefs"."trip_id" = ?  [["trip_id", 1]]
  City Load (0.1ms)  SELECT  "cities".* FROM "cities" WHERE "cities"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
 => "Leeds" 
2.4.0 :030 >

希望这是有道理的。