我遇到一个问题,我的City
模型正在创建多个重复项,并以某种方式忽略了我的验证。
我的控制器正在实例化Family
记录并按如下方式分配城市:
f = Family.new
f.assign_city('Seattle', 'Washington') # put in literals as examples
然后,在我的Family模型中,我有assign_city
方法:
def assign_city(city_name, state_name)
raise(GeoException, 'Both city and state names must be present')
unless city_name.present? && state_name.present?
existing_city = City.query_by_name_and_state(city_name,
state_name).first
if existing_city
self.city = existing_city
else
self.city = City.create! name: city_name, state: state_name,
country: 'USA', description: ''
end
end
我的City
模型包含以下条目:
validates :name, presence: true, uniqueness: {scope: :state}
validates :state, presence: true, numericality: false
scope :query_by_name_and_state, (->(city, state) {
if city.present? && state.present?
where('LOWER(name) LIKE ? AND LOWER(state) LIKE ?', city.downcase,
state.downcase)
else
where '' # to prevent exception to be raised if city, state are nil
end
})
我很困惑有两个原因:
那就是说,我不知何故有17个具有相同名称和州的重复城市!
PS-如果它也有帮助,我做了一个City.where('Seattle', 'Washington').map(&:is_valid?)
并得到一个[false, false, false,...]
的数组
任何建议都将不胜感激。谢谢!
答案 0 :(得分:1)
您不需要执行此条件逻辑,您可以使用find_or_create_by
def assign_city(city_name, state_name)
raise(GeoException, 'Both city and state names must be present') unless city_name.present? && state_name.present?
self.city = City.find_or_create_by name: city_name, state: state_name, country: 'USA', description: ''
end
很难知道为什么你没有得到模型验证错误。我怀疑它是因为您在Family
的未加载实例上运行。但是如果你想要安全,你可以在条件逻辑中执行city.valid?
。但是,如果要确保数据完整性,最佳做法是包括数据库级别验证,因为有多种方法可以绕过/覆盖rails中的模型验证。您的迁移可能如下所示:
class ChangeCity < ActiveRecord::Migration
def change
add_index :cities, [:city_name, :state_name], unique: true
end
end
这样会引发不依赖于模型验证的数据库级错误。您还需要先删除欺骗,否则此迁移将失败。
更新:我测试了你的城市范围方法,发现它可能不太可靠,因为你可以看到这里,如果我们传递空字符串的城市和阶段看看会发生什么:
simple_soundcloud_app(main)> existing_city = City.query_by_name_and_state('', '').first
City Load (0.1ms) SELECT "cities".* FROM "cities" ORDER BY "cities"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<City:0x007f84790c2308
id: 1,
name: "New York",
state: "NY",
created_at: Sun, 01 Apr 2018 22:58:35 UTC +00:00,
updated_at: Sun, 01 Apr 2018 22:58:35 UTC +00:00>
这并没有回答为什么您的验证没有按预期启动的问题但是我们无法确定您使用的输入数据或首先如何创建重复项。我建议为所有方法编写单元测试并测试所有边缘情况。
更新2,让我们修复你的范围方法,因为它有很多错误,特别是如上所述的黑客攻击。你的范围需要参数,所以不要在没有反模式的情况下调用它。您可能应该使用像https://github.com/loureirorg/city-state之类的宝石。但是,如果您要管理数据,请将其标准化为包含所有数据。然后这将工作:
class City < ApplicationRecord
has_many :families
validates :name, presence: true, uniqueness: {scope: :state}
validates :state, presence: true, numericality: false
scope :query_by_name_and_state, -> (city, state) {
where(name: city.downcase, state: state.downcase)
}
before_save :normalize_data
def normalize_data
self.name.downcase!
self.state.downcase!
end
end
simple_soundcloud_app(main)> existing_city = City.query_by_name_and_state('Boston', 'MA').first
City Load (0.1ms) SELECT "cities".* FROM "cities" WHERE "cities"."name" = ? AND "cities"."state" = ? ORDER BY "cities"."id" ASC LIMIT ? [["name", "boston"], ["state", "ma"], ["LIMIT", 1]]
=> #<City:0x007fce6a34e5c8
id: 3,
name: "boston",
state: "ma",
created_at: Sun, 08 Apr 2018 01:29:43 UTC +00:00,
updated_at: Sun, 08 Apr 2018 01:29:43 UTC +00:00>
simple_soundcloud_app(main)> existing_city = City.query_by_name_and_state('', '').first
City Load (0.1ms) SELECT "cities".* FROM "cities" WHERE "cities"."name" = ? AND "cities"."state" = ? ORDER BY "cities"."id" ASC LIMIT ? [["name", ""], ["state", ""], ["LIMIT", 1]]
=> nil