2017-09-16 121 views
1

我有一个应用程序,有3个特定的模型;用户,国家和城市。对于每一个理想的关​​系将是如下:Ruby模型关系 - 三种关系

用户

  • 的has_many:国家
  • 的has_many:城市,通过:国家

国家

  • has_m任:用户
  • 的has_many:城市

  • 的has_many:用户
  • belongs_to的:国家

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

到目前为止,我所能读取和搜索的内容已经导致下面的内容。

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+\-.][email protected][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 
+0

您可以只创建两个标准连接表:'countries_users'和'cities_users',并且在后者上写**模型验证**来声明城市已分配给用户? –

+0

备注:您大概也需要某种预先或删除后的逻辑,以防止用户被**从一个国家分配**,但仍然分配给城市。 (即,不允许这样的删除,或者删除其他关联作为交易的一部分。) –

+0

您无法以用户has_many城市的身份将城市分配给用户?用户需要属于城市以便能够为用户分配城市!您真正想要的是将访问分配给用户并指定一个城市进行访问。将你的问题更新到一个有意义的问题,也许我可以进一步提供帮助。废弃简化它导致你的问题,并让你认为你应该能够做的事情,打破你的要求 – jamesc

回答

0

希望能够到一个城市分配给用户,只要用户 被分配到相关城市

您可以在全国你也不应该。

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

一个城市belongs_to的国家 之旅belongs_to的城市 belongs_to的城市 的访问用户的has_many访问 用户的has_many车次 因为无论旅行和参观属于一个城市,你可以通过双方关系具有的has_many因此访问和旅行表需要城市和用户ID 而且您拥有所需的关系和所需的逻辑。

不要试图做一些违反您的要求的事情,如直接将用户加入城市,因为城市无法分配给用户。事实上,只是为了给你一个更大的头痛,你甚至可能想要在许多国家的许多城市进行许多次旅行。在这种情况下,您希望访问被分配到不是城市的旅行。

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

UPDATE在回应评论

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通过的作品,但你想怎么上了趟模型的has_many城市,所以我会更新如何这将很快奏效

好的,为了实现下一个旅程有很多城市和一个城市has_many旅行的阶段,请注意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 那么你与你的用户现在怎么办?同样,稍后我会更新

UPDATE 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 

要获得所有用户的一趟逛城市,你可以扁平化和映射关系像这样 U = 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 

下一个GET第一位用户首次出行的第一个城市的名称

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 > 

希望是有道理的。

+0

好吧,以避免添加混淆(主要是我的部分),我已经带走了访问模型。我已将上面的代码编辑为我认为您所建议的内容。它仍然不起作用,我不能分配'user1.cities << [city1]',但它看起来很接近。 –

+0

你不能。你需要经历旅行。我不认为你真的明白你想要做什么。我将尽快更新我的答案,并告诉你如何设置应该如何工作。但我仍然不认为这是你想要的。就我个人而言,在编写UML用例或至少写出规范后,我会回到这个页面 – jamesc