diff --git a/lib/stretchy/attributes/type/array.rb b/lib/stretchy/attributes/type/array.rb index 9dd39c8..70e3549 100644 --- a/lib/stretchy/attributes/type/array.rb +++ b/lib/stretchy/attributes/type/array.rb @@ -1,7 +1,20 @@ module Stretchy::Attributes::Type class Array < Stretchy::Attributes::Type::Base # :nodoc: + OPTIONS = [:data_type, :fields] def type :array end + + def type_for_database + data_type || :text + end + + def mappings(name) + options = {type: type_for_database} + self.class::OPTIONS.each { |option| options[option] = send(option) unless send(option).nil? } + options.delete(:fields) if fields == false + options[:fields] = {keyword: {type: :keyword, ignore_above: 256}} if type_for_database == :text && fields.nil? + { name => options }.as_json + end end end \ No newline at end of file diff --git a/lib/stretchy/attributes/type/base.rb b/lib/stretchy/attributes/type/base.rb index bebcfc6..188287a 100644 --- a/lib/stretchy/attributes/type/base.rb +++ b/lib/stretchy/attributes/type/base.rb @@ -20,11 +20,15 @@ def initialize(**args) end def mappings(name) - options = {type: type} + options = {type: type_for_database} self.class::OPTIONS.each { |option| options[option] = send(option) unless send(option).nil? } { name => options }.as_json end + def type_for_database + type + end + private def define_option_methods! diff --git a/lib/stretchy/attributes/type/date_time.rb b/lib/stretchy/attributes/type/date_time.rb index 4c843e1..1b1800a 100644 --- a/lib/stretchy/attributes/type/date_time.rb +++ b/lib/stretchy/attributes/type/date_time.rb @@ -27,5 +27,9 @@ class DateTime < Stretchy::Attributes::Type::Base def type :datetime end + + def type_for_database + :date + end end end \ No newline at end of file diff --git a/lib/stretchy/attributes/type/hash.rb b/lib/stretchy/attributes/type/hash.rb index b772c63..7701c8b 100644 --- a/lib/stretchy/attributes/type/hash.rb +++ b/lib/stretchy/attributes/type/hash.rb @@ -21,6 +21,16 @@ def type :hash end + def type_for_database + :object + end + + def mappings(name) + options = {} + OPTIONS.each { |option| options[option] = send(option) unless send(option).nil? } + { name => options }.as_json + end + private def cast_value(value) diff --git a/lib/stretchy/attributes/type/keyword.rb b/lib/stretchy/attributes/type/keyword.rb index b1fa0a9..ec3ec34 100644 --- a/lib/stretchy/attributes/type/keyword.rb +++ b/lib/stretchy/attributes/type/keyword.rb @@ -2,6 +2,7 @@ module Stretchy::Attributes::Type # Public: Defines a keyword attribute for the model. # # opts - The Hash options used to refine the attribute (default: {}): + # # :doc_values - The Boolean indicating if the field should be stored on disk in a column-stride fashion. Defaults to true. # :eager_global_ordinals - The Boolean indicating if global ordinals should be loaded eagerly on refresh. Defaults to false. # :fields - The Hash of multi-fields for the same string value to be indexed in multiple ways. @@ -20,7 +21,6 @@ module Stretchy::Attributes::Type # :time_series_dimension - The Boolean indicating if the field is a time series dimension. Defaults to false. # # Examples - # # class MyModel # include StretchyModel # attribute :tag, :keyword, ignore_above: 256, time_series_dimension: true diff --git a/lib/stretchy/attributes/type/string.rb b/lib/stretchy/attributes/type/string.rb index f35a3e0..f2c71f4 100644 --- a/lib/stretchy/attributes/type/string.rb +++ b/lib/stretchy/attributes/type/string.rb @@ -1,5 +1,5 @@ module Stretchy::Attributes::Type - class String < Stretchy::Attributes::Type::Base # :nodoc: + class String < Stretchy::Attributes::Type::Text # :nodoc: def type :string end diff --git a/lib/stretchy/attributes/type/text.rb b/lib/stretchy/attributes/type/text.rb index bde23c5..04e8f57 100644 --- a/lib/stretchy/attributes/type/text.rb +++ b/lib/stretchy/attributes/type/text.rb @@ -6,7 +6,7 @@ module Stretchy::Attributes::Type # :eager_global_ordinals - The Boolean indicating if global ordinals should be loaded eagerly on refresh. Defaults to false. # :fielddata - The Boolean indicating if the field can use in-memory fielddata for sorting, aggregations, or scripting. Defaults to false. # :fielddata_frequency_filter - The Hash of expert settings which allow to decide which values to load in memory when fielddata is enabled. - # :fields - The Hash of multi-fields allow the same string value to be indexed in multiple ways for different purposes. + # :fields - The Hash of multi-fields allow the same string value to be indexed in multiple ways for different purposes. By default, a 'keyword' field is added. Set to false to disable. # :index - The Boolean indicating if the field should be searchable. Defaults to true. # :index_options - The String indicating what information should be stored in the index, for search and highlighting purposes. Defaults to 'positions'. # :index_prefixes - The Hash indicating if term prefixes of between 2 and 5 characters are indexed into a separate field. @@ -20,6 +20,7 @@ module Stretchy::Attributes::Type # :term_vector - The String indicating if term vectors should be stored for the field. Defaults to 'no'. # :meta - The Hash of metadata about the field. # + # # Examples # # class MyModel @@ -34,5 +35,17 @@ class Text < Stretchy::Attributes::Type::Base def type :text end + + def type_for_database + :text + end + + def mappings(name) + options = {type: type_for_database} + OPTIONS.each { |option| options[option] = send(option) unless send(option).nil? } + options.delete(:fields) if fields == false + options[:fields] = {keyword: {type: :keyword, ignore_above: 256}} if fields.nil? + { name => options }.as_json + end end end \ No newline at end of file diff --git a/lib/stretchy/delegation/gateway_delegation.rb b/lib/stretchy/delegation/gateway_delegation.rb index 6bae3a9..eaa52be 100644 --- a/lib/stretchy/delegation/gateway_delegation.rb +++ b/lib/stretchy/delegation/gateway_delegation.rb @@ -39,8 +39,8 @@ def reload_gateway_configuration! def gateway(&block) reload_gateway_configuration! if @gateway && @gateway.client != Stretchy.configuration.client - @gateway ||= Stretchy::Repository.create(client: Stretchy.configuration.client, index_name: index_name, klass: base_class) - block.arity < 1 ? @gateway.instance_eval(&block) : block.call(@gateway) if block_given? + @gateway ||= Stretchy::Repository.create(client: Stretchy.configuration.client, index_name: index_name, klass: base_class, mapping: base_class.attribute_mappings.merge(dynamic: true)) + # block.arity < 1 ? @gateway.instance_eval(&block) : block.call(@gateway) if block_given? @gateway end diff --git a/spec/stretchy/associations/belongs_to_spec.rb b/spec/stretchy/associations/belongs_to_spec.rb index 5eaf700..ed7ef3c 100644 --- a/spec/stretchy/associations/belongs_to_spec.rb +++ b/spec/stretchy/associations/belongs_to_spec.rb @@ -19,7 +19,7 @@ class Book < Stretchy::Record attribute :title, :keyword attribute :description, :string - attribute :published_at, :date + attribute :published_at, :datetime attribute :publisher_id, :keyword belongs_to :publisher diff --git a/spec/stretchy/attributes_spec.rb b/spec/stretchy/attributes_spec.rb index f6c598a..290dca6 100644 --- a/spec/stretchy/attributes_spec.rb +++ b/spec/stretchy/attributes_spec.rb @@ -20,30 +20,16 @@ context 'in model' do let(:model) do - class AttributeModel < Stretchy::Record + Object.send(:remove_const, :AttributeModel) if Object.const_defined?(:AttributeModel) + stub_const("AttributeModel", Class.new(Stretchy::Record) do attribute :name, :text attribute :age, :integer attribute :tags, :array - attribute :data, :hash, properties: {weights: {type: :array}, biases: {type: :integer}} + attribute :data, :hash, properties: {weights: {type: :integer}, biases: {type: :integer}} attribute :flagged, :boolean, default: false - end - AttributeModel - end - - it 'array type is registered' do - expect(model.attribute_types['tags'].type).to eq(:array) - end - - it 'hash type is registered' do - expect(model.attribute_types['data'].type).to eq(:hash) - end - - it 'keyword type is registered' do - expect(model.attribute_types['name'].type).to eq(:text) - end - - it 'text type is registered' do - expect(model.attribute_types['age'].type).to eq(:integer) + attribute :body, :text + attribute :only_text, :text, fields: false + end) end context 'hash' do @@ -55,19 +41,96 @@ class AttributeModel < Stretchy::Record context 'mappings' do - it 'returns mappings' do - expect(model.attribute_mappings).to eq({ - properties: { - name: {type: :text}, - age: {type: :integer}, - tags: {type: :array}, - data: {type: :hash, properties: {weights: {type: :array}, biases: {type: :integer}}}, - flagged: {type: :boolean}, - id: { type: :keyword }, - created_at: { type: :datetime }, - updated_at: { type: :datetime } + + context 'auto keyword fields for text fields' do + it 'adds keyword to text fields' do + expect(model.attribute_mappings['properties']['body']).to eq({type: :text, fields: {keyword: {type: :keyword, ignore_above: 256}}}.as_json) + end + + it 'does not add keyword to text fields when fields is false' do + expect(model.attribute_mappings['properties']['only_text']).to eq({type: :text}.as_json) + end + + it 'does not add keyword to text fields when fields is present' do + model.attribute :location, :text, fields: {raw: {type: :keyword}} + expect(model.attribute_mappings['properties']['location']).to eq({type: :text, fields: {raw: {type: :keyword}}}.as_json) + end + + let(:mapping) do + { + "attribute_models": { + "mappings": { + "dynamic": true, + "properties": { + "age": { + "type": "integer" + }, + "body": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "created_at": { + "type": "date" + }, + "data": { + "properties": { + "biases": { + "type": "integer" + }, + "weights": { + "type": "integer" + } + } + }, + "flagged": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "only_text": { + "type": "text" + }, + "tags": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "updated_at": { + "type": "date" + } + } + } } - }.as_json) + }.with_indifferent_access + end + + it 'creates index with attribute mappings' do + model.delete_index! if model.index_exists? + model.create_index! + # ap model.mappings.as_json + # ap mapping[:attribute_models][:mappings][:properties].as_json + expect(model.mappings.with_indifferent_access[:properties].as_json).to eq(mapping[:attribute_models][:mappings][:properties]) + end + end