artirix_data_models 0.5.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f6a90c9b7956cc31c38374094fd416d8d4e9dd09
4
- data.tar.gz: 3cf3edcbf15f24b14b7beb57943b9f93bb12dbaa
3
+ metadata.gz: 4c953eedf5e26123d9a2136591abf12b0a6be6ff
4
+ data.tar.gz: a3966ad6e6704d526176554e4b9a5d332dde1264
5
5
  SHA512:
6
- metadata.gz: 568c1f293797134592df3bd5bca0ba3daacf0ede0aff7770004b5a3aae4821f9ae8eb2df8a668607bff2bfcb4f31bc6ea1a8c334b213084c19763ddebe578714
7
- data.tar.gz: 5e27b33a022ccb060b174fb4466fb62bea449cc8c6ff8a5b3b405482b20f0f7c14eba73eeeddd4f62b31d447b0f2999d0afa8cf2485f095eda3ff3d425dea8bc
6
+ metadata.gz: 2c79dbd9b140a1e584bad0d110d38a6a6ae384761d41ea296d78a58ebc81c44cea3691cbc5ed5ef00cdd9e5fb92d565549946ffeaf356748b57fac362f155677
7
+ data.tar.gz: afc8d927f2082134cb81a9f118f73b799882a019fccc20760886c2d34a2fc0cebfae479794523e735eccddf9b5110edecd1d6b04bd2c61fac00dcc0ebe53913c
data/.gitignore CHANGED
@@ -12,4 +12,5 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
- .idea
15
+ .idea
16
+ /manual_sandbox.rb
data/README.md CHANGED
@@ -224,7 +224,30 @@ end
224
224
 
225
225
  ## Changes
226
226
 
227
- ### v.0.5.0
227
+ ### 0.6.2
228
+
229
+ *Fixed Breaking Change*: removal of `Aggregation.from_json` static method. Now back but delegating to default factory method is `aggregation_factory.aggregation_from_json` in the Aggregation Factory *instance*.
230
+
231
+ - EsCollection's aggregations can now be build based on raw ElasticSearch responses, including nested aggregations. It ignores any aggregation that does not have "buckets", so that nested aggs for `global` or `filtered` are skipped and only the ones with real data are used. (TODO: write docs. In the mean time, have a look at the specs).
232
+ - added `aggregation` method to `Aggregation::Value` class, and also the aggs to the `data_hash` if they are present.
233
+
234
+ ### 0.5.0
228
235
 
229
236
  - opening gem as is to the public.
230
- - still a lot of TODOs in the documentation
237
+ - still a lot of TODOs in the documentation
238
+
239
+
240
+ ## Yanked versions
241
+
242
+
243
+ ### ~0.6.1~
244
+
245
+ Yanked because of breaking change introduction: removal of `Aggregation.from_json` method
246
+
247
+ - added `aggregation` method to `Aggregation::Value` class, and also the aggs to the `data_hash` if they are present.
248
+
249
+ ### ~v0.6.0~
250
+
251
+ Yanked because of breaking change introduction: removal of `Aggregation.from_json` method
252
+
253
+ - EsCollection's aggregations can now be build based on raw ElasticSearch responses, including nested aggregations. It ignores any aggregation that does not have "buckets", so that nested aggs for `global` or `filtered` are skipped and only the ones with real data are used. (TODO: write docs. In the mean time, have a look at the specs).
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency 'faraday'
25
25
  spec.add_dependency 'keyword_init', '~> 1.3'
26
26
  spec.add_dependency 'naught'
27
+ spec.add_dependency 'hashie'
27
28
 
28
29
  spec.add_development_dependency 'kaminari', '~> 0.16'
29
30
  spec.add_development_dependency 'will_paginate', '~> 3.0'
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'artirix_data_models'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ require 'pry'
11
+ Pry.start
12
+
13
+ # require 'irb'
14
+ # IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -6,11 +6,7 @@ module ArtirixDataModels
6
6
  delegate :each, :empty?, to: :buckets
7
7
 
8
8
  def self.from_json(definition, value_class = Value)
9
- buckets = definition[:buckets].map do |bucket|
10
- value_class.new definition[:name].to_sym, bucket[:name], bucket[:count]
11
- end
12
-
13
- new definition[:name].to_sym, buckets
9
+ DAORegistry.aggregations_factory.aggregation_from_json(definition, value_class: value_class, aggregation_class: self)
14
10
  end
15
11
 
16
12
  def pretty_name
@@ -32,7 +28,14 @@ module ArtirixDataModels
32
28
  }
33
29
  end
34
30
 
35
- class Value < Struct.new(:aggregation_name, :name, :count)
31
+ class Value < Struct.new(:aggregation_name, :name, :count, :aggregations)
32
+
33
+ def aggregations
34
+ Array(super)
35
+ end
36
+
37
+ alias_method :nested_aggregations, :aggregations
38
+ alias_method :nested_aggregations=, :aggregations=
36
39
 
37
40
  def pretty_name
38
41
  tranlsation_key = "aggregations.#{aggregation_name.to_s.gsub('.', '_')}.buckets.#{name.to_s.gsub('.', '_')}"
@@ -47,7 +50,20 @@ module ArtirixDataModels
47
50
  count == 0
48
51
  end
49
52
 
53
+ def aggregation(name)
54
+ n = name.to_sym
55
+ aggregations.detect { |x| x.name == n }
56
+ end
57
+
50
58
  def data_hash
59
+ basic_data_hash.tap do |h|
60
+ if aggregations.present?
61
+ h[:aggregations] = aggregations.map(&:data_hash)
62
+ end
63
+ end
64
+ end
65
+
66
+ def basic_data_hash
51
67
  {
52
68
  name: name,
53
69
  count: count
@@ -0,0 +1,47 @@
1
+ module ArtirixDataModels
2
+
3
+ class AggregationBuilder
4
+ attr_reader :aggregations_factory, :definition, :value_class, :aggregation_class
5
+
6
+ def initialize(aggregations_factory:, definition:, aggregation_class: Aggregation, value_class: Aggregation::Value)
7
+ @aggregations_factory = aggregations_factory
8
+ @definition = definition
9
+ @aggregation_class = aggregation_class
10
+ @value_class = value_class
11
+ end
12
+
13
+ def build
14
+ aggregation_class.new agg_name, buckets
15
+ end
16
+
17
+ alias_method :call, :build
18
+
19
+ private
20
+ def buckets
21
+ definition[:buckets].map do |bucket|
22
+ build_bucket(bucket)
23
+ end
24
+ end
25
+
26
+ def build_bucket(bucket)
27
+ name = bucket[:name]
28
+ count = bucket[:count]
29
+ nested_aggs = nested_aggs_from(bucket)
30
+
31
+ value_class.new agg_name, name, count, nested_aggs
32
+ end
33
+
34
+ def nested_aggs_from(bucket)
35
+ raw_nested_aggs = bucket.fetch(:aggregations) { [] }
36
+
37
+ raw_nested_aggs.map do |nested_agg|
38
+ aggregations_factory.build_from_json nested_agg, value_class
39
+ end
40
+ end
41
+
42
+ def agg_name
43
+ definition[:name].to_sym
44
+ end
45
+
46
+ end
47
+ end
@@ -1,28 +1,18 @@
1
1
  module ArtirixDataModels
2
2
  class AggregationsFactory
3
- DEFAULT_FACTORY = ->(aggregation) { Aggregation.from_json aggregation }
4
3
  DEFAULT_COLLECTION_CLASS_NAME = ''.freeze
5
4
 
6
- # singleton instance
7
5
  def initialize
8
6
  @_loaders = Hash.new { |h, k| h[k] = {} }
9
7
  setup_config
10
8
  end
11
9
 
10
+ # SETUP AND CONFIG MANAGEMENT
11
+
12
12
  def setup_config
13
13
  # To be Extended
14
14
  end
15
15
 
16
- def build_from_json(aggregation, collection_class = nil)
17
- get_loader(aggregation[:name], collection_class).call aggregation
18
- end
19
-
20
- def get_loader(aggregation_name, collection_class)
21
- @_loaders[collection_class.to_s][aggregation_name.to_s] ||
22
- @_loaders[DEFAULT_COLLECTION_CLASS_NAME][aggregation_name.to_s] ||
23
- DEFAULT_FACTORY
24
- end
25
-
26
16
  def set_loader(aggregation_name, collection_class = nil, loader = nil, &block)
27
17
  if block
28
18
  @_loaders[collection_class.to_s][aggregation_name.to_s] = block
@@ -33,13 +23,43 @@ module ArtirixDataModels
33
23
  end
34
24
  end
35
25
 
36
- # static methods
37
- def self.set_loader(aggregation_name, collection_class, loader = nil, &block)
38
- instance.set_loader aggregation_name, collection_class, loader, &block
26
+ def default_loader
27
+ proc { |aggregation| Aggregation.from_json aggregation }
39
28
  end
40
29
 
41
- def self.build_from_json(aggregation_name, collection_class)
42
- instance.build_from_json aggregation_name, collection_class
30
+ def get_loader(aggregation_name, collection_class)
31
+ @_loaders[collection_class.to_s][aggregation_name.to_s] ||
32
+ @_loaders[DEFAULT_COLLECTION_CLASS_NAME][aggregation_name.to_s] ||
33
+ default_loader
43
34
  end
35
+
36
+ # AGGREGATION BUILDING
37
+
38
+ def build_from_json(aggregation, collection_class = nil)
39
+ get_loader(aggregation[:name], collection_class).call aggregation
40
+ end
41
+
42
+ def build_all_from_raw_data(raw, collection_class = nil)
43
+ normalised = normalise_aggregations_data(raw)
44
+ normalised.map { |definition| build_from_json definition, collection_class }
45
+ end
46
+
47
+ def aggregation_from_json(definition, value_class: Aggregation::Value, aggregation_class: Aggregation)
48
+ builder_params = {
49
+ aggregations_factory: self,
50
+ definition: definition,
51
+ aggregation_class: aggregation_class,
52
+ value_class: value_class,
53
+ }
54
+
55
+ AggregationBuilder.new(builder_params).build
56
+ end
57
+
58
+ private
59
+
60
+ def normalise_aggregations_data(raw_aggs)
61
+ RawAggregationDataNormaliser.new(self, raw_aggs).normalise
62
+ end
63
+
44
64
  end
45
65
  end
@@ -27,6 +27,10 @@ class ArtirixDataModels::DAORegistry
27
27
  set_loader(:model_fields) { ArtirixDataModels::ModelFieldsDAO.new gateway: get(:gateway) }
28
28
  end
29
29
 
30
+ def aggregations_factory
31
+ get :aggregations_factory
32
+ end
33
+
30
34
  def method_missing(method, *args, &block)
31
35
  if exist?(method)
32
36
  get(method)
@@ -106,7 +106,7 @@ module ArtirixDataModels
106
106
  end
107
107
 
108
108
  def aggregations
109
- @aggregations ||= response[:aggregations].to_a.map { |aggregation| aggregations_factory.build_from_json aggregation, model_class }
109
+ @aggregations ||= build_aggregations
110
110
  end
111
111
 
112
112
  def aggregation(name)
@@ -152,7 +152,15 @@ module ArtirixDataModels
152
152
  ].join(CACHE_KEY_SECTION_SEPARATOR)
153
153
  end
154
154
 
155
+ def raw_aggregations_data
156
+ response[:aggregations]
157
+ end
158
+
155
159
  private
160
+ def build_aggregations
161
+ aggregations_factory.build_all_from_raw_data(raw_aggregations_data)
162
+ end
163
+
156
164
  def load_results
157
165
  hits[:hits].map do |document|
158
166
  deserialize_document(document)
@@ -0,0 +1,68 @@
1
+ module ArtirixDataModels
2
+ class RawAggregationDataNormaliser
3
+
4
+ FIND_BUCKETS = ->(_k, v, _o) { v.respond_to?(:key?) && v.key?(:buckets) }
5
+
6
+ attr_reader :raw_aggs, :aggregations_factory
7
+
8
+ def initialize(aggregations_factory, raw_aggs)
9
+ @aggregations_factory = aggregations_factory
10
+ @raw_aggs = raw_aggs
11
+ end
12
+
13
+ def normalise
14
+ return [] unless raw_aggs.present?
15
+ return raw_aggs if Array === raw_aggs
16
+
17
+ normalise_hash
18
+ end
19
+
20
+ alias_method :call, :normalise
21
+
22
+ private
23
+
24
+ def normalise_hash
25
+ with_buckets_list = deep_locate raw_aggs, FIND_BUCKETS
26
+
27
+ with_buckets_list.reduce([]) do |list, with_buckets|
28
+ with_buckets.each do |name, value|
29
+ add_normalised_element_to_list(list, name, value)
30
+ end
31
+
32
+ list
33
+ end
34
+ end
35
+
36
+ def add_normalised_element_to_list(list, k, v)
37
+ return unless Hash === v && v.key?(:buckets)
38
+
39
+ buckets = v[:buckets].map do |raw_bucket|
40
+ normalise_bucket(raw_bucket)
41
+ end
42
+
43
+ list << { name: k, buckets: buckets }
44
+ end
45
+
46
+ def normalise_bucket(raw_bucket)
47
+ basic_bucket(raw_bucket).tap do |bucket|
48
+ nested_aggs = nested_aggs_from(raw_bucket)
49
+ bucket[:aggregations] = nested_aggs if nested_aggs.present?
50
+ end
51
+ end
52
+
53
+ def basic_bucket(raw_bucket)
54
+ name = raw_bucket[:key] || raw_bucket[:name]
55
+ count = raw_bucket[:doc_count] || raw_bucket[:count]
56
+ { name: name, count: count }
57
+ end
58
+
59
+ # aux
60
+ def deep_locate(object, callable)
61
+ Hashie::Extensions::DeepLocate.deep_locate(callable, object).deep_dup
62
+ end
63
+
64
+ def nested_aggs_from(raw_bucket)
65
+ RawAggregationDataNormaliser.new(aggregations_factory, raw_bucket).normalise
66
+ end
67
+ end
68
+ end
@@ -1,3 +1,4 @@
1
+ # :nocov:
1
2
  def fake_mode_for(model_name)
2
3
  before(:all) do
3
4
  SimpleConfig.for(:site) do
@@ -22,3 +23,4 @@ def fake_mode_for(model_name)
22
23
  end
23
24
  end
24
25
 
26
+ # :nocov:
@@ -1,3 +1,4 @@
1
+ # :nocov:
1
2
  def given_gateway_config(connection_url = nil)
2
3
  connection_url ||= 'http://example.com/other'
3
4
  before(:all) do
@@ -21,3 +22,5 @@ def mock_gateway_get_not_found_response(path:, body: nil, gateway: nil)
21
22
 
22
23
  allow(gateway).to receive(:perform_get).with(path, body).and_raise ArtirixDataModels::DataGateway::NotFound
23
24
  end
25
+
26
+ # :nocov:
@@ -1,3 +1,5 @@
1
+ # :nocov:
2
+
1
3
  # based on https://github.com/geoffharcourt/active_model_lint-rspec/blob/master/lib/active_model_lint-rspec/an_active_model.rb
2
4
  shared_examples_for 'a ReadOnly ActiveModel like ArtirixDataModels' do
3
5
  require 'active_model/lint'
@@ -18,3 +20,4 @@ shared_examples_for 'a ReadOnly ActiveModel like ArtirixDataModels' do
18
20
  end
19
21
  end
20
22
 
23
+ # :nocov:
@@ -1,4 +1,4 @@
1
-
1
+ # :nocov:
2
2
  shared_examples_for 'an ArtirixDataModel DAO' do
3
3
  # example of needed definitions
4
4
  #
@@ -95,3 +95,4 @@ shared_examples_for 'an ArtirixDataModel DAO' do
95
95
  Then { result == partial_mode_fields_list }
96
96
  end
97
97
  end
98
+ # :nocov:
@@ -1,4 +1,4 @@
1
-
1
+ # :nocov:
2
2
  shared_examples_for 'an ArtirixDataModel Model' do
3
3
 
4
4
  # Example of Config:
@@ -222,3 +222,4 @@ shared_examples_for 'an ArtirixDataModel Model' do
222
222
  end
223
223
  end
224
224
 
225
+ # :nocov:
@@ -1,3 +1,4 @@
1
+ # :nocov:
1
2
  shared_examples_for 'has attributes' do
2
3
  describe 'responds to the given attribute getters and setters' do
3
4
  When(:subject) { described_class.new }
@@ -29,4 +30,6 @@ shared_examples_for 'has attributes' do
29
30
  attributes.reject { |at| subject.respond_to? "#{at}?" }.empty?
30
31
  end
31
32
  end
32
- end
33
+ end
34
+
35
+ # :nocov:
@@ -1,3 +1,3 @@
1
1
  module ArtirixDataModels
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.2'
3
3
  end
@@ -1,4 +1,4 @@
1
- require "artirix_data_models/version"
1
+ require 'artirix_data_models/version'
2
2
 
3
3
  # dependencies
4
4
  require 'active_support/all'
@@ -7,6 +7,7 @@ require 'oj'
7
7
  require 'faraday'
8
8
  require 'keyword_init'
9
9
  require 'naught'
10
+ require 'hashie'
10
11
 
11
12
  # note DO NOT require kaminari or will_paginate, it'll be done when invoking `ArtirixDataModels::EsCollection.work_with_will_paginate`
12
13
 
@@ -14,6 +15,8 @@ require 'naught'
14
15
  # loading features
15
16
  require 'artirix_data_models/es_collection'
16
17
  require 'artirix_data_models/aggregations_factory'
18
+ require 'artirix_data_models/raw_aggregation_data_normaliser'
19
+ require 'artirix_data_models/aggregation_builder'
17
20
  require 'artirix_data_models/aggregation'
18
21
  require 'artirix_data_models/model'
19
22
  require 'artirix_data_models/gateways/data_gateway'
@@ -4,7 +4,7 @@ RSpec.describe ArtirixDataModels::EsCollection, type: :model do
4
4
 
5
5
  describe "with an ES response" do
6
6
 
7
- Given(:model_class){
7
+ Given(:model_class) {
8
8
  Class.new do
9
9
  attr_reader :data
10
10
 
@@ -13,9 +13,9 @@ RSpec.describe ArtirixDataModels::EsCollection, type: :model do
13
13
  end
14
14
  end
15
15
  }
16
- Given(:es_response){ ArtirixDataModels::EsCollection::EMPTY_RESPONSE }
16
+ Given(:es_response) { ArtirixDataModels::EsCollection::EMPTY_RESPONSE }
17
17
 
18
- When(:es_collection){ ArtirixDataModels::EsCollection.new(model_class, response: es_response) }
18
+ When(:es_collection) { ArtirixDataModels::EsCollection.new(model_class, response: es_response) }
19
19
 
20
20
  Then { es_collection.total == 0 }
21
21
  Then { es_collection.results == [] }
@@ -23,7 +23,7 @@ RSpec.describe ArtirixDataModels::EsCollection, type: :model do
23
23
 
24
24
  describe "coerced from an array" do
25
25
 
26
- Given(:model_class){
26
+ Given(:model_class) {
27
27
  Class.new do
28
28
  attr_reader :number
29
29
 
@@ -33,13 +33,142 @@ RSpec.describe ArtirixDataModels::EsCollection, type: :model do
33
33
  end
34
34
  }
35
35
 
36
- Given(:results_array){ [model_class.new(1), model_class.new(2), model_class.new(3)] }
36
+ Given(:results_array) { [model_class.new(1), model_class.new(2), model_class.new(3)] }
37
37
 
38
- When(:es_collection){ ArtirixDataModels::EsCollection.from_array(results_array) }
38
+ When(:es_collection) { ArtirixDataModels::EsCollection.from_array(results_array) }
39
39
 
40
40
  Then { es_collection.total == 3 }
41
41
  Then { es_collection.results == results_array }
42
42
  Then { es_collection.aggregations == [] }
43
43
  end
44
44
 
45
+ describe 'aggregations' do
46
+
47
+ Given(:model_class) do
48
+ Class.new do
49
+ attr_reader :data
50
+
51
+ def initialize(data)
52
+ @data = { given: data }
53
+ end
54
+ end
55
+ end
56
+
57
+ Given(:es_response) { Oj.load(File.read(fixture_file), symbol_keys: true) }
58
+
59
+ context 'with Data Layer conversion into array' do
60
+ Given(:fixture_file) { fixture_pathname('articles_search_dl.json') }
61
+
62
+ When(:es_collection) { ArtirixDataModels::EsCollection.new(model_class, response: es_response) }
63
+
64
+ Then { es_collection.total == 11477 }
65
+ Then { es_collection.results == [] }
66
+
67
+ Then { es_collection.aggregations.size == 2 }
68
+
69
+ Then { es_collection.aggregations.first.name == :primary_category_slug }
70
+ Then { es_collection.aggregations.first.buckets.size == 10 }
71
+ Then { es_collection.aggregations.first.buckets.first.name == 'yacht-market-intelligence' }
72
+ Then { es_collection.aggregations.first.buckets.first.count == 7794 }
73
+ Then { es_collection.aggregations.first.buckets.last.name == 'brokerage' }
74
+ Then { es_collection.aggregations.first.buckets.last.count == 7 }
75
+
76
+ Then { es_collection.aggregations.last.name == :location_slug }
77
+ Then { es_collection.aggregations.last.buckets.size == 10 }
78
+ Then { es_collection.aggregations.last.buckets.first.name == 'americas' }
79
+ Then { es_collection.aggregations.last.buckets.first.count == 3 }
80
+ Then { es_collection.aggregations.last.buckets.last.name == 'italy' }
81
+ Then { es_collection.aggregations.last.buckets.last.count == 1 }
82
+ end
83
+
84
+ context 'with Raw ES with single aggregation' do
85
+ Given(:fixture_file) { fixture_pathname('articles_search_raw_es.json') }
86
+
87
+ When(:es_collection) { ArtirixDataModels::EsCollection.new(model_class, response: es_response) }
88
+
89
+ Then { es_collection.total == 14974 }
90
+ Then { es_collection.results == [] }
91
+
92
+ Then { es_collection.aggregations.size == 1 }
93
+
94
+ Then { es_collection.aggregations.first.name == :disease_slug }
95
+ Then { es_collection.aggregations.first.buckets.size == 6 }
96
+ Then { es_collection.aggregations.first.buckets.first.name == 'sickle-cell-anemia' }
97
+ Then { es_collection.aggregations.first.buckets.first.count == 5754 }
98
+ Then { es_collection.aggregations.first.buckets.last.name == 'fabry-disease' }
99
+ Then { es_collection.aggregations.first.buckets.last.count == 742 }
100
+ end
101
+
102
+ context 'with Raw ES with single nested aggregation' do
103
+ Given(:fixture_file) { fixture_pathname('articles_search_nested_single_raw_es.json') }
104
+
105
+ When(:es_collection) { ArtirixDataModels::EsCollection.new(model_class, response: es_response) }
106
+
107
+ Then { es_collection.total == 11492 }
108
+ Then { es_collection.results == [] }
109
+
110
+ Then { es_collection.aggregations.size == 2 }
111
+
112
+ Then { es_collection.aggregations.first.name == :category_slug }
113
+ Then { es_collection.aggregations.first.buckets.size == 24 }
114
+ Then { es_collection.aggregations.first.buckets.first.name == 'brokerage-sales-news' }
115
+ Then { es_collection.aggregations.first.buckets.first.count == 7792 }
116
+ Then { es_collection.aggregations.first.buckets.last.name == 'why-charter-a-superyacht' }
117
+ Then { es_collection.aggregations.first.buckets.last.count == 1 }
118
+
119
+ Then { es_collection.aggregations.last.name == :location_slug }
120
+ Then { es_collection.aggregations.last.buckets.size == 19 }
121
+ Then { es_collection.aggregations.last.buckets.first.name == 'americas' }
122
+ Then { es_collection.aggregations.last.buckets.first.count == 4 }
123
+ Then { es_collection.aggregations.last.buckets.last.name == 'tahiti' }
124
+ Then { es_collection.aggregations.last.buckets.last.count == 1 }
125
+ end
126
+
127
+ context 'with Raw ES with nested aggregations' do
128
+ Given(:fixture_file) { fixture_pathname('articles_search_nested_raw_es.json') }
129
+
130
+ Given(:model_class) {
131
+ Class.new do
132
+ attr_reader :data
133
+
134
+ def initialize(data)
135
+ @data = { given: data }
136
+ end
137
+ end
138
+ }
139
+ Given(:es_response) { Oj.load(File.read(fixture_file), symbol_keys: true) }
140
+
141
+ When(:es_collection) { ArtirixDataModels::EsCollection.new(model_class, response: es_response) }
142
+
143
+ Then { es_collection.total == 4512 }
144
+ Then { es_collection.results == [] }
145
+
146
+ Then { es_collection.aggregations.size == 1 }
147
+
148
+ Then { es_collection.aggregations.first.name == :level1_taxonomy }
149
+ Then { es_collection.aggregations.first.buckets.size == 4 }
150
+
151
+ Then { es_collection.aggregations.first.buckets.first.name == 'Treatment' }
152
+ Then { es_collection.aggregations.first.buckets.first.count == 2404 }
153
+ Then { es_collection.aggregations.first.buckets.first.aggregations.size == 1 }
154
+ Then { es_collection.aggregations.first.buckets.first.aggregations.first.name == :level2_taxonomy }
155
+ Then { es_collection.aggregations.first.buckets.first.aggregations.first.buckets.size == 7 }
156
+ Then { es_collection.aggregations.first.buckets.first.aggregations.first.buckets.first.name == 'Drug Treatments' }
157
+ Then { es_collection.aggregations.first.buckets.first.aggregations.first.buckets.first.count == 977 }
158
+ Then { es_collection.aggregations.first.buckets.first.aggregations.first.buckets.last.name == 'Complementary and Alternative Therapies' }
159
+ Then { es_collection.aggregations.first.buckets.first.aggregations.first.buckets.last.count == 14 }
160
+
161
+
162
+ Then { es_collection.aggregations.first.buckets.last.name == 'Living' }
163
+ Then { es_collection.aggregations.first.buckets.last.count == 365 }
164
+ Then { es_collection.aggregations.first.buckets.last.aggregations.size == 1 }
165
+ Then { es_collection.aggregations.first.buckets.last.aggregations.first.name == :level2_taxonomy }
166
+ Then { es_collection.aggregations.first.buckets.last.aggregations.first.buckets.size == 8 }
167
+ Then { es_collection.aggregations.first.buckets.last.aggregations.first.buckets.first.name == 'Emotional Impact' }
168
+ Then { es_collection.aggregations.first.buckets.last.aggregations.first.buckets.first.count == 104 }
169
+ Then { es_collection.aggregations.first.buckets.last.aggregations.first.buckets.last.name == 'Cognitive Impact' }
170
+ Then { es_collection.aggregations.first.buckets.last.aggregations.first.buckets.last.count == 3 }
171
+ end
172
+ end
173
+
45
174
  end
@@ -0,0 +1,99 @@
1
+ {
2
+ "hits": {
3
+ "total": 11477,
4
+ "max_score": 0.0,
5
+ "hits": []
6
+ },
7
+ "aggregations": [
8
+ {
9
+ "name": "primary_category_slug",
10
+ "buckets": [
11
+ {
12
+ "name": "yacht-market-intelligence",
13
+ "count": 7794
14
+ },
15
+ {
16
+ "name": "brokerage-sales-news",
17
+ "count": 7792
18
+ },
19
+ {
20
+ "name": "yachts",
21
+ "count": 2238
22
+ },
23
+ {
24
+ "name": "news",
25
+ "count": 2236
26
+ },
27
+ {
28
+ "name": "charter",
29
+ "count": 1322
30
+ },
31
+ {
32
+ "name": "luxury-yacht-charter-news",
33
+ "count": 1292
34
+ },
35
+ {
36
+ "name": "luxury-yacht-events",
37
+ "count": 110
38
+ },
39
+ {
40
+ "name": "luxury-yacht-events-news",
41
+ "count": 110
42
+ },
43
+ {
44
+ "name": "luxury-yacht-charter-advice",
45
+ "count": 15
46
+ },
47
+ {
48
+ "name": "brokerage",
49
+ "count": 7
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ "name": "location_slug",
55
+ "buckets": [
56
+ {
57
+ "name": "americas",
58
+ "count": 3
59
+ },
60
+ {
61
+ "name": "central-america",
62
+ "count": 3
63
+ },
64
+ {
65
+ "name": "europe",
66
+ "count": 3
67
+ },
68
+ {
69
+ "name": "caribbean",
70
+ "count": 2
71
+ },
72
+ {
73
+ "name": "costa-rica",
74
+ "count": 2
75
+ },
76
+ {
77
+ "name": "mediterranean",
78
+ "count": 2
79
+ },
80
+ {
81
+ "name": "anegada",
82
+ "count": 1
83
+ },
84
+ {
85
+ "name": "france",
86
+ "count": 1
87
+ },
88
+ {
89
+ "name": "french-polynesia",
90
+ "count": 1
91
+ },
92
+ {
93
+ "name": "italy",
94
+ "count": 1
95
+ }
96
+ ]
97
+ }
98
+ ]
99
+ }
@@ -0,0 +1,149 @@
1
+ {
2
+ "took": 16,
3
+ "timed_out": false,
4
+ "_shards": {
5
+ "total": 5,
6
+ "successful": 5,
7
+ "failed": 0
8
+ },
9
+ "hits": {
10
+ "total": 4512,
11
+ "max_score": 0,
12
+ "hits": []
13
+ },
14
+ "aggregations": {
15
+ "taxonomy": {
16
+ "doc_count": 4674,
17
+ "level1_taxonomy": {
18
+ "doc_count_error_upper_bound": 0,
19
+ "sum_other_doc_count": 0,
20
+ "buckets": [
21
+ {
22
+ "key": "Treatment",
23
+ "doc_count": 2404,
24
+ "level2_taxonomy": {
25
+ "doc_count_error_upper_bound": 0,
26
+ "sum_other_doc_count": 0,
27
+ "buckets": [
28
+ {
29
+ "key": "Drug Treatments",
30
+ "doc_count": 977
31
+ },
32
+ {
33
+ "key": "Drug Development",
34
+ "doc_count": 731
35
+ },
36
+ {
37
+ "key": "Other Treatments",
38
+ "doc_count": 358
39
+ },
40
+ {
41
+ "key": "Surgical Treatments",
42
+ "doc_count": 264
43
+ },
44
+ {
45
+ "key": "Assistive Devices",
46
+ "doc_count": 43
47
+ },
48
+ {
49
+ "key": "Palliative Care",
50
+ "doc_count": 17
51
+ },
52
+ {
53
+ "key": "Complementary and Alternative Therapies",
54
+ "doc_count": 14
55
+ }
56
+ ]
57
+ }
58
+ },
59
+ {
60
+ "key": "Understand",
61
+ "doc_count": 1356,
62
+ "level2_taxonomy": {
63
+ "doc_count_error_upper_bound": 0,
64
+ "sum_other_doc_count": 0,
65
+ "buckets": [
66
+ {
67
+ "key": "Symptoms",
68
+ "doc_count": 1066
69
+ },
70
+ {
71
+ "key": "Outlook",
72
+ "doc_count": 143
73
+ },
74
+ {
75
+ "key": "Causes",
76
+ "doc_count": 123
77
+ },
78
+ {
79
+ "key": "Ethics",
80
+ "doc_count": 24
81
+ }
82
+ ]
83
+ }
84
+ },
85
+ {
86
+ "key": "Diagnosis",
87
+ "doc_count": 549,
88
+ "level2_taxonomy": {
89
+ "doc_count_error_upper_bound": 0,
90
+ "sum_other_doc_count": 0,
91
+ "buckets": [
92
+ {
93
+ "key": "Diagnostic Techniques",
94
+ "doc_count": 525
95
+ },
96
+ {
97
+ "key": "Diagnostic Challenges",
98
+ "doc_count": 24
99
+ }
100
+ ]
101
+ }
102
+ },
103
+ {
104
+ "key": "Living",
105
+ "doc_count": 365,
106
+ "level2_taxonomy": {
107
+ "doc_count_error_upper_bound": 0,
108
+ "sum_other_doc_count": 0,
109
+ "buckets": [
110
+ {
111
+ "key": "Emotional Impact",
112
+ "doc_count": 104
113
+ },
114
+ {
115
+ "key": "Genetic Counseling",
116
+ "doc_count": 63
117
+ },
118
+ {
119
+ "key": "Diet",
120
+ "doc_count": 59
121
+ },
122
+ {
123
+ "key": "Fertility/Sex",
124
+ "doc_count": 56
125
+ },
126
+ {
127
+ "key": "Exercise",
128
+ "doc_count": 48
129
+ },
130
+ {
131
+ "key": "Physical Impact",
132
+ "doc_count": 26
133
+ },
134
+ {
135
+ "key": "Sleep",
136
+ "doc_count": 6
137
+ },
138
+ {
139
+ "key": "Cognitive Impact",
140
+ "doc_count": 3
141
+ }
142
+ ]
143
+ }
144
+ }
145
+ ]
146
+ }
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,212 @@
1
+ {
2
+ "took": 2,
3
+ "timed_out": false,
4
+ "_shards": {
5
+ "total": 5,
6
+ "successful": 5,
7
+ "failed": 0
8
+ },
9
+ "hits": {
10
+ "total": 11492,
11
+ "max_score": 0,
12
+ "hits": []
13
+ },
14
+ "aggregations": {
15
+ "global_primary_category_slug": {
16
+ "doc_count": 11492,
17
+ "global_filtered_primary_category_slug": {
18
+ "doc_count": 11492,
19
+ "category_slug": {
20
+ "doc_count_error_upper_bound": 0,
21
+ "sum_other_doc_count": 0,
22
+ "buckets": [
23
+ {
24
+ "key": "brokerage-sales-news",
25
+ "doc_count": 7792
26
+ },
27
+ {
28
+ "key": "news",
29
+ "doc_count": 2236
30
+ },
31
+ {
32
+ "key": "luxury-yacht-charter-news",
33
+ "doc_count": 1292
34
+ },
35
+ {
36
+ "key": "luxury-yacht-events-news",
37
+ "doc_count": 110
38
+ },
39
+ {
40
+ "key": "charter",
41
+ "doc_count": 14
42
+ },
43
+ {
44
+ "key": "brokerage",
45
+ "doc_count": 7
46
+ },
47
+ {
48
+ "key": "luxury-yacht-charter-advice",
49
+ "doc_count": 5
50
+ },
51
+ {
52
+ "key": "cruising-advice",
53
+ "doc_count": 4
54
+ },
55
+ {
56
+ "key": "luxury-yacht-life",
57
+ "doc_count": 4
58
+ },
59
+ {
60
+ "key": "safety-advice",
61
+ "doc_count": 4
62
+ },
63
+ {
64
+ "key": "design01-news",
65
+ "doc_count": 3
66
+ },
67
+ {
68
+ "key": "plan-and-book-advice",
69
+ "doc_count": 3
70
+ },
71
+ {
72
+ "key": "charter-news",
73
+ "doc_count": 2
74
+ },
75
+ {
76
+ "key": "destinations",
77
+ "doc_count": 2
78
+ },
79
+ {
80
+ "key": "superyacht-sales-data",
81
+ "doc_count": 2
82
+ },
83
+ {
84
+ "key": "testcat123456789-edit",
85
+ "doc_count": 2
86
+ },
87
+ {
88
+ "key": "yachts",
89
+ "doc_count": 2
90
+ },
91
+ {
92
+ "key": "yachts-luxury-yacht-broker-directory",
93
+ "doc_count": 2
94
+ },
95
+ {
96
+ "key": "annual-reports",
97
+ "doc_count": 1
98
+ },
99
+ {
100
+ "key": "boat-shows",
101
+ "doc_count": 1
102
+ },
103
+ {
104
+ "key": "company-news",
105
+ "doc_count": 1
106
+ },
107
+ {
108
+ "key": "new-to-the-fleet",
109
+ "doc_count": 1
110
+ },
111
+ {
112
+ "key": "superyacht1234",
113
+ "doc_count": 1
114
+ },
115
+ {
116
+ "key": "why-charter-a-superyacht",
117
+ "doc_count": 1
118
+ }
119
+ ]
120
+ }
121
+ }
122
+ },
123
+ "global_location_slug": {
124
+ "doc_count": 11492,
125
+ "global_filtered_location_slug": {
126
+ "doc_count": 11492,
127
+ "location_slug": {
128
+ "doc_count_error_upper_bound": 0,
129
+ "sum_other_doc_count": 0,
130
+ "buckets": [
131
+ {
132
+ "key": "americas",
133
+ "doc_count": 4
134
+ },
135
+ {
136
+ "key": "central-america",
137
+ "doc_count": 4
138
+ },
139
+ {
140
+ "key": "europe",
141
+ "doc_count": 3
142
+ },
143
+ {
144
+ "key": "caribbean",
145
+ "doc_count": 2
146
+ },
147
+ {
148
+ "key": "costa-rica",
149
+ "doc_count": 2
150
+ },
151
+ {
152
+ "key": "mediterranean",
153
+ "doc_count": 2
154
+ },
155
+ {
156
+ "key": "anegada",
157
+ "doc_count": 1
158
+ },
159
+ {
160
+ "key": "france",
161
+ "doc_count": 1
162
+ },
163
+ {
164
+ "key": "french-polynesia",
165
+ "doc_count": 1
166
+ },
167
+ {
168
+ "key": "italy",
169
+ "doc_count": 1
170
+ },
171
+ {
172
+ "key": "leeward-islands",
173
+ "doc_count": 1
174
+ },
175
+ {
176
+ "key": "north-america",
177
+ "doc_count": 1
178
+ },
179
+ {
180
+ "key": "pacific",
181
+ "doc_count": 1
182
+ },
183
+ {
184
+ "key": "panama",
185
+ "doc_count": 1
186
+ },
187
+ {
188
+ "key": "rest-of-the-world",
189
+ "doc_count": 1
190
+ },
191
+ {
192
+ "key": "san-blas-islands",
193
+ "doc_count": 1
194
+ },
195
+ {
196
+ "key": "spain",
197
+ "doc_count": 1
198
+ },
199
+ {
200
+ "key": "st-barths",
201
+ "doc_count": 1
202
+ },
203
+ {
204
+ "key": "tahiti",
205
+ "doc_count": 1
206
+ }
207
+ ]
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "took": 5,
3
+ "timed_out": false,
4
+ "_shards": {
5
+ "total": 5,
6
+ "successful": 5,
7
+ "failed": 0
8
+ },
9
+ "hits": {
10
+ "total": 14974,
11
+ "max_score": 0,
12
+ "hits": []
13
+ },
14
+ "aggregations": {
15
+ "disease_slug": {
16
+ "doc_count_error_upper_bound": 0,
17
+ "sum_other_doc_count": 0,
18
+ "buckets": [
19
+ {
20
+ "key": "sickle-cell-anemia",
21
+ "doc_count": 5754
22
+ },
23
+ {
24
+ "key": "cystic-fibrosis",
25
+ "doc_count": 4512
26
+ },
27
+ {
28
+ "key": "behcet-syndrome",
29
+ "doc_count": 1613
30
+ },
31
+ {
32
+ "key": "myasthenia-gravis",
33
+ "doc_count": 1402
34
+ },
35
+ {
36
+ "key": "cushing-syndrome",
37
+ "doc_count": 951
38
+ },
39
+ {
40
+ "key": "fabry-disease",
41
+ "doc_count": 742
42
+ }
43
+ ]
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,5 @@
1
+ require 'pathname'
2
+
3
+ def fixture_pathname(filename)
4
+ Pathname(__FILE__).join("../../fixtures/#{filename}")
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: artirix_data_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eduardo Turiño
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-07 00:00:00.000000000 Z
11
+ date: 2015-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: hashie
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: kaminari
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -195,7 +209,9 @@ dependencies:
195
209
  description: used in Boat International UI and Admin apps
196
210
  email:
197
211
  - eturino@artirix.com
198
- executables: []
212
+ executables:
213
+ - console
214
+ - setup
199
215
  extensions: []
200
216
  extra_rdoc_files: []
201
217
  files:
@@ -207,8 +223,11 @@ files:
207
223
  - README.md
208
224
  - Rakefile
209
225
  - artirix_data_models.gemspec
226
+ - bin/console
227
+ - bin/setup
210
228
  - lib/artirix_data_models.rb
211
229
  - lib/artirix_data_models/aggregation.rb
230
+ - lib/artirix_data_models/aggregation_builder.rb
212
231
  - lib/artirix_data_models/aggregations_factory.rb
213
232
  - lib/artirix_data_models/cache_service.rb
214
233
  - lib/artirix_data_models/cached_action_adaptor.rb
@@ -224,6 +243,7 @@ files:
224
243
  - lib/artirix_data_models/gateway_response_adaptors/model_adaptor.rb
225
244
  - lib/artirix_data_models/gateways/data_gateway.rb
226
245
  - lib/artirix_data_models/model.rb
246
+ - lib/artirix_data_models/raw_aggregation_data_normaliser.rb
227
247
  - lib/artirix_data_models/spec_support.rb
228
248
  - lib/artirix_data_models/spec_support/fake_mode.rb
229
249
  - lib/artirix_data_models/spec_support/gateway_mock.rb
@@ -238,11 +258,16 @@ files:
238
258
  - spec/artirix_data_models/gateways/data_gateway_spec.rb
239
259
  - spec/artirix_data_models/gateways/gateway_response_adaptors/model_adaptor_spec.rb
240
260
  - spec/artirix_data_models/model_fields_dao_spec.rb
261
+ - spec/fixtures/articles_search_dl.json
262
+ - spec/fixtures/articles_search_nested_raw_es.json
263
+ - spec/fixtures/articles_search_nested_single_raw_es.json
264
+ - spec/fixtures/articles_search_raw_es.json
241
265
  - spec/spec_helper.rb
242
266
  - spec/support/.keep
243
267
  - spec/support/a_finder_enabled_ui_model_dao.rb
244
268
  - spec/support/a_search_enabled_ui_model_dao.rb
245
269
  - spec/support/artirix_data_models.rb
270
+ - spec/support/fixtures.rb
246
271
  homepage: ''
247
272
  licenses:
248
273
  - MIT
@@ -273,9 +298,14 @@ test_files:
273
298
  - spec/artirix_data_models/gateways/data_gateway_spec.rb
274
299
  - spec/artirix_data_models/gateways/gateway_response_adaptors/model_adaptor_spec.rb
275
300
  - spec/artirix_data_models/model_fields_dao_spec.rb
301
+ - spec/fixtures/articles_search_dl.json
302
+ - spec/fixtures/articles_search_nested_raw_es.json
303
+ - spec/fixtures/articles_search_nested_single_raw_es.json
304
+ - spec/fixtures/articles_search_raw_es.json
276
305
  - spec/spec_helper.rb
277
306
  - spec/support/.keep
278
307
  - spec/support/a_finder_enabled_ui_model_dao.rb
279
308
  - spec/support/a_search_enabled_ui_model_dao.rb
280
309
  - spec/support/artirix_data_models.rb
310
+ - spec/support/fixtures.rb
281
311
  has_rdoc: