quandl-elasticsearch 2.1.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rubocop.yml +34 -0
  4. data/COMMANDS.md +29 -0
  5. data/Gemfile +10 -0
  6. data/Gemfile.lock +155 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +50 -0
  9. data/Rakefile +1 -0
  10. data/config/elasticsearch.yml +32 -0
  11. data/elasticsearch/elasticsearch.yml +386 -0
  12. data/elasticsearch/logging.yml +56 -0
  13. data/elasticsearch/stopwords/english.txt +38 -0
  14. data/elasticsearch/synonyms/synonyms_english.txt +318 -0
  15. data/fixtures/vcr_cassettes/search_spec_database_1.yml +38 -0
  16. data/fixtures/vcr_cassettes/search_spec_database_2.yml +38 -0
  17. data/fixtures/vcr_cassettes/search_spec_dataset_1.yml +48 -0
  18. data/fixtures/vcr_cassettes/search_spec_dataset_2.yml +41 -0
  19. data/fixtures/vcr_cassettes/setup.yml +139 -0
  20. data/lib/quandl/elasticsearch.rb +61 -0
  21. data/lib/quandl/elasticsearch/base.rb +20 -0
  22. data/lib/quandl/elasticsearch/database.rb +22 -0
  23. data/lib/quandl/elasticsearch/dataset.rb +51 -0
  24. data/lib/quandl/elasticsearch/indice.rb +96 -0
  25. data/lib/quandl/elasticsearch/query.rb +282 -0
  26. data/lib/quandl/elasticsearch/search.rb +150 -0
  27. data/lib/quandl/elasticsearch/tag.rb +21 -0
  28. data/lib/quandl/elasticsearch/template.rb +189 -0
  29. data/lib/quandl/elasticsearch/utility.rb +6 -0
  30. data/lib/quandl/elasticsearch/version.rb +6 -0
  31. data/quandl +77 -0
  32. data/quandl-elasticsearch.gemspec +34 -0
  33. data/solano.yml +20 -0
  34. data/spec/lib/quandl/elasticsearch/database_spec.rb +98 -0
  35. data/spec/lib/quandl/elasticsearch/dataset_spec.rb +124 -0
  36. data/spec/lib/quandl/elasticsearch/indice_spec.rb +10 -0
  37. data/spec/lib/quandl/elasticsearch/query_spec.rb +239 -0
  38. data/spec/lib/quandl/elasticsearch/search_spec.rb +83 -0
  39. data/spec/lib/quandl/elasticsearch/template_spec.rb +182 -0
  40. data/spec/lib/quandl/elasticsearch/utility_spec.rb +10 -0
  41. data/spec/lib/quandl/elasticsearch_spec.rb +99 -0
  42. data/spec/spec_helper.rb +27 -0
  43. data/templates/database_mapping.json +11 -0
  44. data/templates/dataset_mapping.json +9 -0
  45. data/templates/quandl_delimiter.json +0 -0
  46. data/templates/search_term_mapping.json +13 -0
  47. data/tests/Database-Ratings.csv +405 -0
  48. data/tests/Database-Tags.csv +341 -0
  49. data/tests/compare.csv +1431 -0
  50. data/tests/compare.rb +33 -0
  51. data/tests/console.rb +4 -0
  52. data/tests/generated_db_tags.csv +341 -0
  53. data/tests/search.rb +14 -0
  54. data/tests/search_db_mapping.txt +402 -0
  55. data/tests/status.rb +2 -0
  56. data/tests/test_search.csv +87 -0
  57. data/tests/test_search.rb +113 -0
  58. data/tests/testing-list.txt +183 -0
  59. data/tests/top500searches.csv +477 -0
  60. metadata +300 -0
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'quandl/elasticsearch/indice'
3
+
4
+ describe Quandl::Elasticsearch::Indice do
5
+ describe '.initialize' do
6
+ it 'can initialize' do
7
+ _indice = Quandl::Elasticsearch::Indice.new
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,239 @@
1
+ require 'spec_helper'
2
+
3
+ describe Quandl::Elasticsearch::Query do
4
+ let(:dataset_search_filter) do
5
+ [
6
+ { term: { db_hidden: false } },
7
+ { term: { db_exclusive: false } }
8
+ ]
9
+ end
10
+ describe '.initialize' do
11
+ it 'can initialize' do
12
+ query = Quandl::Elasticsearch::Query.new
13
+ expect(query.class).to be(Quandl::Elasticsearch::Query)
14
+ end
15
+ end
16
+
17
+ describe '.query_body' do
18
+ let(:expected) do
19
+ {
20
+ explain: false,
21
+ fields: %w(_parent code),
22
+ query: {
23
+ bool: {
24
+ must: {
25
+ bool: {
26
+ should: [
27
+ {
28
+ multi_match: {
29
+ fields: ['code', 'name^1.1'],
30
+ query: 'stocks', type: 'phrase', operator: 'and',
31
+ zero_terms_query: 'all', slop: 10
32
+ }
33
+ },
34
+ {
35
+ has_child: {
36
+ type: 'dataset', score_mode: 'max',
37
+ query: {
38
+ bool: {
39
+ should: [
40
+ { multi_match: { fields: ['name^1.1'], query: 'stocks', type: 'phrase', slop: 10 } },
41
+ { multi_match: { fields: ['code', 'name^1.1'], query: 'stocks', type: 'best_fields' } },
42
+ { prefix: { code: 'stocks' } }
43
+ ]
44
+ }
45
+ }
46
+ }
47
+ },
48
+ { prefix: { code: 'stocks' } }
49
+ ]
50
+ }
51
+ },
52
+ filter: { bool: { must: [] } }
53
+ }
54
+ },
55
+ from: 1,
56
+ size: 10
57
+ }
58
+ end
59
+ it 'returns the correct JSON string' do
60
+ expect(Quandl::Elasticsearch::Query.query_body('stocks', 1, 10)).to eq(expected)
61
+ end
62
+ end
63
+
64
+ describe '.multi_dataset_query_body' do
65
+ let(:expected_query) do
66
+ {
67
+ timeout: '10s',
68
+ fields: %w(_parent _id code),
69
+ query: {
70
+ bool: {
71
+ must: {
72
+ bool: {
73
+ should: [{ multi_match: { fields: ['code', 'name^1.1'], query: 'stocks', type: 'best_fields' } },
74
+ { prefix: { code: 'stocks' } }]
75
+ }
76
+ },
77
+ filter: {
78
+ bool: {
79
+ must: [{ term: { _parent: 1 } }, { term: { is_private: false } }]
80
+ }
81
+ }
82
+ }
83
+ },
84
+ sort: ['_score', { to_date: { order: 'desc' } }],
85
+ highlight: { fields: { name: {} } },
86
+ size: 10
87
+ }
88
+ end
89
+
90
+ it 'returns the correct JSON string' do
91
+ expect(Quandl::Elasticsearch::Query.multi_dataset_query_body(1, 'stocks', 10)).to eq(index: 'quandl_index', type: 'dataset', search: expected_query)
92
+ end
93
+ end
94
+
95
+ describe '.dataset_query_body' do
96
+ context 'searching using a query' do
97
+ let(:options) { { query: 'stocks' } }
98
+ let(:expected_query) do
99
+ {
100
+ fields: %w(_parent code),
101
+ query: {
102
+ bool: {
103
+ must: {
104
+ bool: {
105
+ should: [
106
+ { multi_match: { fields: ['name^1.1'], query: 'stocks', type: 'phrase', slop: 10 } },
107
+ { multi_match: { fields: ['code', 'name^1.1'], query: 'stocks', type: 'best_fields' } },
108
+ { prefix: { code: 'stocks' } }
109
+ ]
110
+ }
111
+ },
112
+ filter: {
113
+ bool: {
114
+ must: [{ term: { is_private: false } }] + dataset_search_filter
115
+ }
116
+ }
117
+ }
118
+ },
119
+ highlight: { fields: { name: {} } }, from: 1, size: 10
120
+ }
121
+ end
122
+
123
+ it 'returns the correct JSON string' do
124
+ expect(Quandl::Elasticsearch::Query.dataset_query_body(1, 10, options)).to eq(expected_query)
125
+ end
126
+ end
127
+
128
+ context 'searching all datasets' do
129
+ let(:options) { {} }
130
+ let(:expected_query) do
131
+ {
132
+ fields: %w(_parent code),
133
+ query: {
134
+ bool: {
135
+ must: { match_all: {} },
136
+ filter: {
137
+ bool: {
138
+ must: [{ term: { is_private: false } }] + dataset_search_filter
139
+ }
140
+ }
141
+ }
142
+ },
143
+ sort: [{ to_date: { order: 'desc' } }],
144
+ from: 1,
145
+ size: 10
146
+ }
147
+ end
148
+
149
+ it 'returns the correct JSON string' do
150
+ expect(Quandl::Elasticsearch::Query.dataset_query_body(1, 10, options)).to eq(expected_query)
151
+ end
152
+ end
153
+
154
+ context 'searching within a database' do
155
+ let(:options) { { query: 'stocks', database_id: 1 } }
156
+ let(:expected_query) do
157
+ {
158
+ fields: %w(_parent code),
159
+ query: {
160
+ bool: {
161
+ must: {
162
+ bool: {
163
+ should: [
164
+ { multi_match: { fields: ['name^1.1'], query: 'stocks', type: 'phrase', slop: 10 } },
165
+ { multi_match: { fields: ['code', 'name^1.1'], query: 'stocks', type: 'best_fields' } },
166
+ { prefix: { code: 'stocks' } }
167
+ ]
168
+ }
169
+ },
170
+ filter: {
171
+ bool: { must: [{ term: { is_private: false } }, { term: { _parent: 1 } }] }
172
+ }
173
+ }
174
+ },
175
+ highlight: { fields: { name: {} } },
176
+ from: 1,
177
+ size: 10
178
+ }
179
+ end
180
+
181
+ it 'returns the correct JSON string' do
182
+ expect(Quandl::Elasticsearch::Query.dataset_query_body(1, 10, options)).to eq(expected_query)
183
+ end
184
+ end
185
+ end
186
+
187
+ context 'filter option' do
188
+ let(:filter) { 'sample' }
189
+ let(:options) { { query: 'stocks', database_id: 1, filter: filter } }
190
+
191
+ context 'filter option is set to sample' do
192
+ it 'filters on sample=true datasets' do
193
+ query_body = Quandl::Elasticsearch::Query.dataset_query_body(1, 10, options)
194
+ expect(query_body[:query][:bool][:filter][:bool][:must]).to include(term: { sample: true })
195
+ end
196
+ end
197
+
198
+ context 'filter option is not set to sample' do
199
+ let(:filter) { 'all' }
200
+
201
+ it 'does not filter on sample=true datasets' do
202
+ query_body = Quandl::Elasticsearch::Query.dataset_query_body(1, 10, options)
203
+ expect(query_body[:query][:bool][:filter][:bool][:must]).to contain_exactly({ term: { is_private: false } }, term: { _parent: 1 })
204
+ end
205
+ end
206
+ end
207
+
208
+ describe '.add_timeout!' do
209
+ let(:body) { {} }
210
+
211
+ context 'timeout configured' do
212
+ context 'search key body' do
213
+ it 'adds timeout to body' do
214
+ Quandl::Elasticsearch::Query.add_timeout!(body)
215
+ expect(body).to eq(timeout: '10s')
216
+ end
217
+ end
218
+
219
+ context 'root body' do
220
+ let(:body) { { search: {} } }
221
+
222
+ it 'adds timeout to search body' do
223
+ Quandl::Elasticsearch::Query.add_timeout!(body)
224
+ expect(body).to eq(search: { timeout: '10s' })
225
+ end
226
+ end
227
+ end
228
+
229
+ context 'timeout is not configured' do
230
+ before(:each) do
231
+ Quandl::Elasticsearch.configuration.api_timeout = nil
232
+ end
233
+ it 'does not add timeout to body' do
234
+ Quandl::Elasticsearch::Query.add_timeout!(body)
235
+ expect(body).to eq({})
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+ require 'vcr'
3
+
4
+ describe Quandl::Elasticsearch::Search do
5
+ describe '.initialize' do
6
+ it 'can initialize' do
7
+ search = Quandl::Elasticsearch::Search.new
8
+ expect(search.class).to be(Quandl::Elasticsearch::Search)
9
+ end
10
+ end
11
+
12
+ describe '.page_size' do
13
+ it 'can return default page size' do
14
+ expect(subject.page_size).to be(10)
15
+ end
16
+
17
+ it 'can change a page size via init and return it' do
18
+ search = Quandl::Elasticsearch::Search.new(5)
19
+ expect(search.page_size).to be(5)
20
+ end
21
+ end
22
+
23
+ describe '.max_datasets' do
24
+ it 'can return max_datasets' do
25
+ expect(subject.max_datasets).to be(3)
26
+ end
27
+ end
28
+
29
+ describe '.database' do
30
+ it 'can process raw data from ES into appropriate format' do
31
+ VCR.use_cassette('search_spec_database_1') do
32
+ response = subject.database('stocks', '', 'free', 0, false)
33
+ expect(response).to eq([{ '_id' => '33', 'code' => 'NSE' }, { '_id' => '460', 'code' => 'LUXSE' }, { '_id' => '442', 'code' => 'ZAGREBSE' }, { '_id' => '462', 'code' => 'BUCHARESTSE' }, { '_id' => '5628', 'code' => 'EURONEXT' }, { '_id' => '450', 'code' => 'PHILSE' }, { '_id' => '663', 'code' => 'SI' }, { '_id' => '1383', 'code' => 'SIX' }, { '_id' => '464', 'code' => 'NIKKEI' }, { '_id' => '8401', 'code' => 'CRYPTOCHART' }, { 'total' => 130, 'took' => 752 }])
34
+ end
35
+ VCR.use_cassette('search_spec_database_2') do
36
+ response = subject.database('stocks', '', 'premium', 0, false)
37
+ expect(response).to eq([{ '_id' => '12910', 'code' => 'EOD' }, { '_id' => '13091', 'code' => 'ZFA' }, { '_id' => '13122', 'code' => 'ZFB' }, { '_id' => '13065', 'code' => 'ZSEE' }, { '_id' => '12266', 'code' => 'ZEE' }, { '_id' => '13000', 'code' => 'ZET' }, { '_id' => '12985', 'code' => 'ZAR' }, { '_id' => '12908', 'code' => 'SF1' }, { '_id' => '12973', 'code' => 'ZDIV' }, { '_id' => '12991', 'code' => 'ZSS' }, { 'total' => 18, 'took' => 769 }])
38
+ end
39
+ end
40
+
41
+ it 'adds timeout to query body' do
42
+ VCR.use_cassette('search_spec_database_1') do
43
+ expect(Quandl::Elasticsearch::Query).to receive(:add_timeout!).and_call_original
44
+ subject.database('stocks', '', 'premium', 0, false)
45
+ end
46
+ end
47
+
48
+ context 'timeout' do
49
+ it 'raises timeout error' do
50
+ expect_any_instance_of(::Elasticsearch::Transport::Client).to receive(:search).and_return('timed_out' => true)
51
+ expect { subject.database('stocks', '', 'premium', 0, false) }.to raise_error(Quandl::Elasticsearch::Search::TimeoutError)
52
+ end
53
+ end
54
+ end
55
+
56
+ describe '.dataset' do
57
+ it 'can process raw data returned from ES into appropriate format' do
58
+ VCR.use_cassette('search_spec_dataset_1') do
59
+ response = subject.dataset('stocks', 'daily', 0, 10, database_id: 104_71)
60
+ expect(response).to eq([{ '_id' => '13147174', 'highlight' => { 'name' => ['OMX Iceland All-<em>Share</em> GI (OMXIGI)'] } }, { '_id' => '13147176', 'highlight' => { 'name' => ['OMX Iceland All-<em>Share</em> PI (OMXIPI)'] } }, { '_id' => '13147219', 'highlight' => { 'name' => ['OMX Stockholm All-<em>Share</em> Cap_GI (OMXSCAPGI)'] } }, { '_id' => '13147220', 'highlight' => { 'name' => ['OMX Stockholm All-<em>Share</em> Cap_PI (OMXSCAPPI)'] } }, { '_id' => '13147404', 'highlight' => { 'name' => ['OMXI-FO All <em>Share</em> PI (OMXIFOPI)'] } }, { '_id' => '13147405', 'highlight' => { 'name' => ['OMXI-FO All <em>Share</em> GI (OMXIFOGI)'] } }, { '_id' => '13127717', 'highlight' => { 'name' => ['NASDAQ Select Canadian Preferred <em>Share</em> Index TR (NQCAPFDT)'] } }, { '_id' => '13127718', 'highlight' => { 'name' => ['NASDAQ Select Canadian Preferred <em>Share</em> Index (NQCAPFD)'] } }, { '_id' => '13961773', 'highlight' => { 'name' => ['NASDAQ Bullet<em>Shares</em> Corporate Debt 0-3 Ladder (BSCP03)'] } }, { '_id' => '13961774', 'highlight' => { 'name' => ['NASDAQ Bullet<em>Shares</em> High Yield Debt 0-3 Ladder TR (BSHY03T)'] } }, { 'total' => 30 }])
61
+ end
62
+
63
+ VCR.use_cassette('search_spec_dataset_2') do
64
+ response = subject.dataset('mexico', nil, 0, 3, database_id: 104_71)
65
+ expect(response).to eq([{ '_id' => '13124809', 'highlight' => { 'name' => ['NASDAQ <em>Mexico</em> Basic Matls AUD Index (NQMX1000AUD)'] } }, { '_id' => '13124810', 'highlight' => { 'name' => ['NASDAQ <em>Mexico</em> AUD Index (NQMXAUD)'] } }, { '_id' => '13124813', 'highlight' => { 'name' => ['NASDAQ <em>Mexico</em> AUD NTR Index (NQMXAUDN)'] } }, { 'total' => 315 }])
66
+ end
67
+ end
68
+
69
+ it 'adds timout to query body' do
70
+ VCR.use_cassette('search_spec_dataset_1') do
71
+ expect(Quandl::Elasticsearch::Query).to receive(:add_timeout!).and_call_original
72
+ subject.dataset('stocks', 'daily', 0, 10, database_id: 104_71)
73
+ end
74
+ end
75
+
76
+ context 'timeout' do
77
+ it 'raises timeout error' do
78
+ expect_any_instance_of(::Elasticsearch::Transport::Client).to receive(:search).and_return('timed_out' => true)
79
+ expect { subject.dataset('stocks', 'daily', 0, 10, database_id: 104_71) }.to raise_error(Quandl::Elasticsearch::Search::TimeoutError)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+ require 'quandl/elasticsearch/indice'
3
+ require 'active_support/all'
4
+
5
+ describe Quandl::Elasticsearch::Template do
6
+ describe '.initialize' do
7
+ it 'can initialize' do
8
+ template = Quandl::Elasticsearch::Template.new
9
+ expect(template.class).to be(Quandl::Elasticsearch::Template)
10
+ end
11
+ end
12
+
13
+ describe '.main_index' do
14
+ it 'can return the correct string' do
15
+ expect(Quandl::Elasticsearch::Template.main_index.deep_stringify_keys).to match(
16
+ {
17
+ settings: {
18
+ index: {
19
+ number_of_shards: 1, number_of_replicas: 0
20
+ },
21
+ similarity: {
22
+ quandl_bm25: {
23
+ type: 'BM25', k1: 0, b: 0.75
24
+ }
25
+ },
26
+ analysis: {
27
+ analyzer: {
28
+ keyword_lowercase: {
29
+ type: 'english', tokenizer: 'keyword', filter: 'lowercase', stopwords: '_none_'
30
+ },
31
+ quandl_index: {
32
+ type: 'custom', tokenizer: 'whitespace', filter: %w(quandl_delimiter lowercase quandl_synonym_filter quandl_stop_words quandl_stemmer trim), char_filter: ['quandl_char_filter']
33
+ },
34
+ quandl_search: {
35
+ type: 'custom', tokenizer: 'whitespace', filter: %w(quandl_delimiter lowercase quandl_stop_words quandl_stemmer trim), char_filter: ['quandl_char_filter']
36
+ }
37
+ },
38
+ filter: {
39
+ quandl_synonym_filter: {
40
+ type: 'synonym',
41
+ synonyms: be_an(Array)
42
+ },
43
+ quandl_stemmer: {
44
+ type: 'stemmer',
45
+ name: 'english'
46
+ },
47
+ quandl_stop_words: {
48
+ type: 'stop',
49
+ ignore_case: true,
50
+ stopwords: be_an(Array)
51
+ },
52
+ quandl_delimiter: {
53
+ type: 'word_delimiter',
54
+ split_on_case_change: true,
55
+ catenate_numbers: true, preserve_original: true, generate_word_parts: true, split_on_numbers: true, stem_english_prossessive: true, type_table: ['& => ALPHA', '_ => ALPHA', '$ => DIGIT', '% => DIGIT'], protected_word: ['u.s.', 'u.s.a.']
56
+ }
57
+ },
58
+ char_filter: {
59
+ quandl_char_filter: {
60
+ type: 'mapping', mappings: ['\\u0060=>\\u0020', '\\u002F=>\\u0020', '\\u003A=>\\u0020', '\\u002D=>\\u0020', '\\u0028=>\\u0020', '\\u0029=>\\u0020', '\\u002C=>\\u0020', '\\u003B=>\\u0020']
61
+ }
62
+ }
63
+ }
64
+ },
65
+ mappings: {
66
+ database: {
67
+ properties: {
68
+ code: {
69
+ type: 'string', store: true, norms: {
70
+ enabled: false
71
+ },
72
+ analyzer: 'keyword_lowercase'
73
+ },
74
+ name: {
75
+ type: 'string', store: true, norms: {
76
+ enabled: true
77
+ },
78
+ analyzer: 'quandl_index', search_analyzer: 'quandl_search'
79
+ },
80
+ description: {
81
+ type: 'string', store: true, norms: {
82
+ enabled: true
83
+ },
84
+ analyzer: 'quandl_index'
85
+ },
86
+ documentation: {
87
+ type: 'string', store: true, norms: {
88
+ enabled: true
89
+ },
90
+ analyzer: 'quandl_index'
91
+ },
92
+ premium: {
93
+ type: 'boolean', store: true, norms: {
94
+ enabled: false
95
+ }
96
+ },
97
+ hidden: {
98
+ type: 'boolean', store: true, norms: {
99
+ enabled: false
100
+ }
101
+ },
102
+ exclusive: {
103
+ type: 'boolean', store: true, norms: {
104
+ enabled: false
105
+ }
106
+ },
107
+ type: {
108
+ type: 'string', store: true, norms: {
109
+ enabled: false
110
+ },
111
+ index: 'not_analyzed'
112
+ },
113
+ rating: {
114
+ type: 'integer', store: true, norms: {
115
+ enabled: false
116
+ }, index: 'not_analyzed'
117
+ },
118
+ tags: {
119
+ type: 'string', store: true, norms: {
120
+ enabled: false
121
+ },
122
+ index: 'not_analyzed'
123
+ }
124
+ }
125
+ },
126
+ dataset: {
127
+ _parent: {
128
+ type: 'database'
129
+ },
130
+ properties: {
131
+ code: {
132
+ type: 'string', store: true, norms: {
133
+ enabled: false
134
+ },
135
+ analyzer: 'keyword_lowercase'
136
+ },
137
+ name: {
138
+ type: 'string', store: true, norms: {
139
+ enabled: true
140
+ },
141
+ analyzer: 'quandl_index', search_analyzer: 'quandl_search'
142
+ },
143
+ is_private: {
144
+ type: 'boolean', norms: {
145
+ enabled: false
146
+ },
147
+ store: false
148
+ },
149
+ type: {
150
+ type: 'string', store: true, norms: {
151
+ enabled: false
152
+ },
153
+ index: 'not_analyzed'
154
+ },
155
+ frequency: {
156
+ type: 'string', store: false, norms: {
157
+ enabled: false
158
+ },
159
+ index: 'not_analyzed'
160
+ },
161
+ to_date: {
162
+ type: 'date', format: 'date', store: false, norms: {
163
+ enabled: false
164
+ }
165
+ },
166
+ db_hidden: {
167
+ type: 'boolean', norms: { enabled: false }, store: false
168
+ },
169
+ db_exclusive: {
170
+ type: 'boolean', norms: { enabled: false }, store: false
171
+ },
172
+ sample: {
173
+ type: 'boolean', norms: { enabled: false }, store: false
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }.deep_stringify_keys
179
+ )
180
+ end
181
+ end
182
+ end