Rails 4 表单 - 文本字段值必须与关联的 table 中的值匹配(自动完成)

Rails 4 form - text field value must match a value in an associated table (autocomplete)

我有两个关联models/tables

app/models/city.rb
has_many :businesses
Fields: id, city_name, state_code

app/models/business.rb
belongs_to :city
Fields: id, biz_name, address, city_name, state_code, zip

在您输入公司地址的新公司表单中,通常会有一个 select 州 and/or 个国家/地区的下拉框。但是对于城市来说,有成千上万的城市,所以这是行不通的。有没有办法在允许保存之前检查城市是否列在 cities table 中?甚至可能将 city_id 放在该字段中而不是 city_name?

我认为 validates_associated 不适用于这种情况。我只是制作一个自定义 validate 方法。如果您的城市都以小写形式存储,这将起作用,否则使用 humanize 而不是 downcase

validate :city_must_exist_with_city_name

def city_must_exist_with_city_name
    if !City.find_by(name: city_name.downcase)
        errors.add(:item_name, "must be a valid city.")
    end
end

编辑:通过执行此操作,您可以在大写问题上更进一步:

if !City.where("lower(name) = ?", city_name.downcase).first

向您的 city_name 字段添加自动完成功能可以解决您的问题。

首先,由于 BusinessCity 对象之间的关联,您将需要 return 一个 business[city_id] 参数而不是 city_name 参数(假设您使用的是 form_for @business 助手)。

如果您正在使用 jQuery,您可以使用 devbridge jQuery-Autocomplete 插件: https://github.com/devbridge/jQuery-Autocomplete

创建一个简单的方法,returns JSON 格式的城市列表(将需要 jbuilder gem,它应该默认包含在你的 Gemfile 中),例如:

in app/controllers/businesses_controller.rb:

def ac_cities
  @query = params[:query]
  @results = City.where('city_name LIKE ?', "#{@query}%").order(city_name: :asc).limit(10)
  respond_to do |format|
    format.json
  end
end

in app/views/businesses/ac_cities.json.jbuilder:

json.query @query
json.suggestions @results do |result|
  json.value "<div data-id=\"#{result.id}\">#{result.city_name}</div>"
end

初始化 devbridge 实例并使用 CoffeeScript(或 JS)将其连接到您的 city_name 字段。 devbridge 插件还有一个 onSelect 功能,允许您填充隐藏的 business[city_id] 表单字段。 例如,给定上面的 JSON returned:

in app/assets/javascripts/businesses.js.coffee:

BusinessCitySelect = (suggestion) ->
  $(this).next('#business_city_id').val(suggestion.value.match(/data-id="(\d+)"/i)[1])
  $(this).val(suggestion.value.replace(new RegExp('<.*?\/?>', 'gi'), ''))

SearchACFormat = (suggestion, cv) ->
  pattern = '(<div.*?>.*?)('+cv+').*?'
  suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong><\/strong>')

$("#city_name").devbridgeAutocomplete({
  serviceUrl: "/businesses/ac_cities.json",
  formatResult: SearchACFormat,
  onSelect: BusinessCitySelect,
  noCache: true
})

SearchACFormat 方法利用 devbridge 的 formatResult 功能来清理我们的 JSON 以在 city_name 输入字段中正确显示,同时还提供突出显示。

根据回复建议和我自己的进一步调查,我得出了从关联的 table 获取表单中大量选项的最佳方式的结论。

看起来 自动完成 是最好的解决方案。这是一个常规文本字段,但当您输入第二个字母时,它会拉入它在下面列出的前 10 个匹配项。每个额外的字母都会改进搜索。由于我在 JavaScript 方面不是很熟练,而不是按照建议添加一个插件补充我自己的 JS,我选择使用 gem 抽象 JS 编码,特别是 rails-jquery-autocomplete.
以下是步骤:

设置

生成脚手架:

rails generate scaffold City name state
rails generate scaffold Business name address city:references state zip
rake db:migrate

安装 gem。它使用 jQuery-UI's autocomplete widget,因此也为此安装 rails gem。

# gemfile
gem 'jquery-ui-rails'
gem 'rails-jquery-autocomplete'

将这些添加到您的 javascript 和样式表资产中:

# app/assets/javascripts/application.js > put these after //= require jquery_ujs
//= require jquery-ui/autocomplete
//= require autocomplete-rails

# app/assets/stylesheets/application.css     
*= require jquery-ui/autocomplete

型号

首先创建协会:

# app/model/city.rb
has_many :businesses

# app/model/business.rb
belongs_to :city

如果要确保输入的城市在城市table中,则添加验证:

# app/models/business.rb
validates :city, presence: true

企业 table 有一个字段 city_id 而不是城市名称。我们可以使用 getter 和 setter 方法在业务模型文件中创建一个虚拟属性。自从我从一个 4 岁的 RailsCast 那里得到它以来,可能有一个更新的方法来做到这一点,但它有效。

#getter method
def city_name
  city.try(:name)
end
#setter method
def city_name=(name)
  self.city = City.find_by(name: name) if name.present?
end

路线

这里是 gem 魔法发挥作用的地方。添加一个路由到格式为 autocomplete_model_attribute:

的业务 REST 资源
# config/routes.rb
resources :businesses do
  get 'autocomplete_city_name' on: :collection
end

控制器

在控制器的顶部调用自动完成操作,将 class 名称和属性传递给它。默认情况下,自动完成将匹配从单词开头开始的文本。如果您想搜索单词中的任何位置,请将 'full' 选项设置为 true,否则将其保留。

# app/controllers/business_controller.rb
autocomplete :city, :name, full: true

由于我们在业务模型中创建了一个 city_name 虚拟属性,因此我们需要将其添加到允许的参数列表中

def business_params
  params.require(:business).permit(:name, :address, :city_id, :state, :zip, :city_name)
end

观看次数

在创建或编辑新业务实例的表单中,使用 autocomplete_field 助手将 city_id 字段更改为 city_name 虚拟属性。 'data-auto-focus' 选项将自动 select 列表中的第一个值。如果你不想那样,就把它关掉。

# app/views/businesses/_form.html.erb
<div class="field">
  <%= f.label :city_name %><br>
  <%= f.autocomplete_field :city_name, autocomplete_city_name_businesses_path, 'data-auto-focus' => true %>
</div>

完成!