2013-01-26 07:52:58 -08:00
|
|
|
# encoding=utf-8
|
|
|
|
# ^ to put the regex in utf-8 mode
|
|
|
|
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
class Item
|
|
|
|
module Search
|
|
|
|
class Query
|
|
|
|
FIELD_CLASSES = {
|
|
|
|
:is_nc => Fields::Flag,
|
|
|
|
:is_pb => Fields::Flag,
|
|
|
|
:species_support_id => Fields::SetField,
|
|
|
|
:occupied_zone_id => Fields::SetField,
|
|
|
|
:restricted_zone_id => Fields::SetField,
|
2013-01-22 21:52:34 -08:00
|
|
|
:name => Fields::SetField,
|
|
|
|
:user_closet_hanger_ownership => Fields::SetField
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
def initialize(filters, user)
|
|
|
|
@filters = filters
|
2013-01-22 21:52:34 -08:00
|
|
|
@user = user
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def fields
|
|
|
|
initial_fields.tap do |fields|
|
|
|
|
@filters.each { |filter| fields[filter.key] << filter }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def to_flex_params
|
|
|
|
fields.values.map(&:to_flex_params).inject(&:merge)
|
|
|
|
end
|
|
|
|
|
|
|
|
def paginate(options={})
|
|
|
|
begin
|
|
|
|
flex_params = self.to_flex_params
|
|
|
|
rescue Item::Search::Contradiction
|
|
|
|
# If we have a contradictory query, no need to raise a stink about
|
|
|
|
# it, but no need to actually run a search, either.
|
|
|
|
return []
|
|
|
|
end
|
|
|
|
|
|
|
|
final_flex_params = {
|
|
|
|
:page => (options[:page] || 1),
|
2013-01-22 21:52:34 -08:00
|
|
|
:size => (options[:per_page] || 30),
|
|
|
|
:type => 'item'
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
}.merge(flex_params)
|
|
|
|
|
|
|
|
locales = I18n.fallbacks[I18n.locale] &
|
|
|
|
I18n.locales_with_neopets_language_code
|
|
|
|
final_flex_params[:locale] = locales.first
|
|
|
|
|
2013-01-21 11:17:36 -08:00
|
|
|
# Extend the names/negative_names queries with the corresponding
|
|
|
|
# localalized field names.
|
|
|
|
if final_flex_params[:_names] || final_flex_params[:_negative_names]
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
locale_entries = locales.map do |locale|
|
|
|
|
boost = (locale == I18n.locale) ? 4 : 1
|
2013-01-21 11:17:36 -08:00
|
|
|
"name.#{locale}^#{boost}"
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
end
|
|
|
|
|
2013-01-21 11:17:36 -08:00
|
|
|
# We *could* have set _name_locales once as a partial, but Flex won't
|
|
|
|
# let us call partials from inside other partials. Whatever. Assign
|
|
|
|
# it to each name entry instead. I also feel bad doing this
|
|
|
|
# afterwards, since it's kinda the field's job to return proper flex
|
|
|
|
# params, but that's a refactor for another day.
|
|
|
|
[:_names, :_negative_names].each do |key|
|
|
|
|
if final_flex_params[key]
|
|
|
|
final_flex_params[key].each do |name_query|
|
|
|
|
name_query[:fields] = locale_entries
|
|
|
|
end
|
|
|
|
end
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-01-22 21:52:34 -08:00
|
|
|
# Okay, yeah, looks like this really does deserve a refactor, like
|
|
|
|
# _names and _negative_names do. (Or Flex could just make all variables
|
|
|
|
# accessible from partials... hint, hint)
|
|
|
|
[:_user_closet_hanger_ownerships, :_negative_user_closet_hanger_ownerships].each do |key|
|
|
|
|
if final_flex_params[key]
|
|
|
|
Item::Search.error 'not_logged_in' unless @user
|
|
|
|
|
|
|
|
final_flex_params[key].each do |entry|
|
|
|
|
entry[:user_id] = @user.id
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
result = FlexSearch.item_search(final_flex_params)
|
2013-01-21 17:47:01 -08:00
|
|
|
result.scoped_loaded_collection(
|
|
|
|
:scopes => {'Item' => Item.includes(:translations)}
|
|
|
|
)
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Load the text query labels from I18n, so that when we see, say,
|
|
|
|
# the filter "species:acara", we know it means species_support_id.
|
|
|
|
TEXT_KEYS_BY_LABEL = {}
|
2013-01-26 07:52:58 -08:00
|
|
|
IS_KEYWORDS = {}
|
2013-01-22 21:52:34 -08:00
|
|
|
OWNERSHIP_KEYWORDS = {}
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
I18n.available_locales.each do |locale|
|
|
|
|
TEXT_KEYS_BY_LABEL[locale] = {}
|
2013-01-26 07:52:58 -08:00
|
|
|
IS_KEYWORDS[locale] = Set.new
|
2013-01-22 21:52:34 -08:00
|
|
|
OWNERSHIP_KEYWORDS[locale] = {}
|
2013-01-26 07:52:58 -08:00
|
|
|
|
|
|
|
I18n.fallbacks[locale].each do |fallback|
|
|
|
|
FIELD_CLASSES.keys.each do |key|
|
|
|
|
# A locale can specify multiple labels for a key by separating by
|
|
|
|
# commas: "occupies,zone,type"
|
|
|
|
labels = I18n.translate("items.search.labels.#{key}",
|
|
|
|
:locale => fallback).split(',')
|
|
|
|
|
|
|
|
labels.each do |label|
|
|
|
|
plain_label = label.parameterize # 'é' => 'e'
|
|
|
|
TEXT_KEYS_BY_LABEL[locale][plain_label] = key
|
|
|
|
end
|
|
|
|
|
|
|
|
is_keyword = I18n.translate('items.search.flag_keywords.is',
|
|
|
|
:locale => fallback)
|
|
|
|
IS_KEYWORDS[locale] << is_keyword.parameterize
|
|
|
|
|
|
|
|
{:owns => true, :wants => false}.each do |key, value|
|
|
|
|
translated_key = I18n.translate("items.search.labels.user_#{key}",
|
|
|
|
:locale => fallback)
|
|
|
|
OWNERSHIP_KEYWORDS[locale][translated_key] = value
|
|
|
|
end
|
2013-01-22 21:52:34 -08:00
|
|
|
end
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
TEXT_QUERY_RESOURCE_FINDERS = {
|
|
|
|
:species => lambda { |name|
|
|
|
|
species = Species.find_by_name(name)
|
|
|
|
unless species
|
|
|
|
Item::Search.error 'not_found.species', :species_name => name
|
|
|
|
end
|
|
|
|
species.id
|
|
|
|
},
|
2013-01-21 17:34:39 -08:00
|
|
|
:zone => lambda { |label|
|
|
|
|
zone_set = Zone.with_plain_label(label)
|
2013-01-25 09:15:54 -08:00
|
|
|
if zone_set.empty?
|
|
|
|
Item::Search.error 'not_found.zone', :zone_name => label
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
end
|
|
|
|
zone_set.map(&:id)
|
2013-01-22 21:52:34 -08:00
|
|
|
},
|
|
|
|
:ownership => lambda { |keyword|
|
|
|
|
OWNERSHIP_KEYWORDS[I18n.locale][keyword].tap do |value|
|
|
|
|
if value.nil?
|
|
|
|
Item::Search.error 'not_found.ownership', :keyword => keyword
|
|
|
|
end
|
|
|
|
end
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TEXT_QUERY_RESOURCE_TYPES_BY_KEY = {
|
|
|
|
:species_support_id => :species,
|
|
|
|
:occupied_zone_id => :zone,
|
2013-01-22 21:52:34 -08:00
|
|
|
:restricted_zone_id => :zone,
|
|
|
|
:user_closet_hanger_ownership => :ownership
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
}
|
|
|
|
|
2013-01-26 07:52:58 -08:00
|
|
|
TEXT_FILTER_EXPR = /([+-]?)(?:(\p{Word}+):)?(?:"([^"]+)"|(\S+))/
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
def self.from_text(text, user=nil)
|
|
|
|
filters = []
|
2013-01-22 21:52:34 -08:00
|
|
|
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
text.scan(TEXT_FILTER_EXPR) do |sign, label, quoted_value, unquoted_value|
|
|
|
|
raw_value = quoted_value || unquoted_value
|
|
|
|
is_positive = (sign != '-')
|
|
|
|
|
2013-01-26 07:52:58 -08:00
|
|
|
Rails.logger.debug(label.inspect)
|
|
|
|
Rails.logger.debug(TEXT_KEYS_BY_LABEL[I18n.locale].inspect)
|
|
|
|
Rails.logger.debug(IS_KEYWORDS[I18n.locale].inspect)
|
|
|
|
|
|
|
|
if label
|
|
|
|
plain_label = label.parameterize
|
|
|
|
|
|
|
|
if IS_KEYWORDS[I18n.locale].include?(plain_label)
|
|
|
|
# is-filters are weird. "-is:nc" is transposed to something more
|
|
|
|
# like "-nc:<nil>", then it's translated into a negative "is_nc"
|
|
|
|
# flag. Fun fact: "nc:foobar" and "-nc:foobar" also work. A bonus,
|
|
|
|
# I guess. There's probably a good way to refactor this to avoid
|
|
|
|
# the unintended bonus syntax, but this is a darn good cheap
|
|
|
|
# technique for the time being.
|
|
|
|
label = raw_value
|
|
|
|
plain_label = raw_value.parameterize
|
|
|
|
raw_value = nil
|
|
|
|
end
|
|
|
|
|
|
|
|
key = TEXT_KEYS_BY_LABEL[I18n.locale][plain_label]
|
|
|
|
else
|
|
|
|
key = :name
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
if key.nil?
|
|
|
|
message = I18n.translate('items.search.errors.not_found.label',
|
|
|
|
:label => label)
|
|
|
|
raise Item::Search::Error, message
|
|
|
|
end
|
|
|
|
|
2013-01-28 14:25:00 -08:00
|
|
|
#if key == :user_closet_hanger_ownership
|
|
|
|
# Item::Search.error 'user_filters_disabled'
|
|
|
|
#end
|
2013-01-26 23:45:18 -08:00
|
|
|
|
globalized search first draft
Confirmed features:
* Output (retrieval, sorting, etc.)
* Name (positive and negative, but new behavior)
* Flags (positive and negative)
Planned features:
* users:owns, user:wants
Known issues:
* Sets are broken
* Don't render properly
* Shouldn't actually be done as joined sets, anyway, since
we actually want (set1_zone1 OR set1_zone2) AND
(set2_zone1 OR set2_zone2), which will require breaking
it into multiple terms queries.
* Name has regressed: ignores phrases, doesn't require *all*
words. While we're breaking sets into multiple queries,
maybe we'll do something similar for name. In fact, we
really kinda have to if we're gonna keep sorting by name,
since "straw hat" returns all hats. Eww.
2013-01-18 21:23:37 -08:00
|
|
|
if TEXT_QUERY_RESOURCE_TYPES_BY_KEY.has_key?(key)
|
|
|
|
resource_type = TEXT_QUERY_RESOURCE_TYPES_BY_KEY[key]
|
|
|
|
finder = TEXT_QUERY_RESOURCE_FINDERS[resource_type]
|
|
|
|
value = finder.call(raw_value)
|
|
|
|
else
|
|
|
|
value = raw_value
|
|
|
|
end
|
|
|
|
|
|
|
|
filters << Filter.new(key, value, is_positive)
|
|
|
|
end
|
|
|
|
|
|
|
|
self.new(filters, user)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
# The fields start out empty, then have the filters inserted into 'em,
|
|
|
|
# so that the fields can validate and aggregate their requirements.
|
|
|
|
def initial_fields
|
|
|
|
{}.tap do |fields|
|
|
|
|
FIELD_CLASSES.map do |key, klass|
|
|
|
|
fields[key] = klass.new(key)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|