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
字段添加自动完成功能可以解决您的问题。
首先,由于 Business
和 City
对象之间的关联,您将需要 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>
完成!
我有两个关联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
字段添加自动完成功能可以解决您的问题。
首先,由于 Business
和 City
对象之间的关联,您将需要 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>
完成!