gummi 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. data/.gitignore +1 -0
  2. data/gummi.gemspec +7 -6
  3. data/lib/gummi.rb +32 -26
  4. data/lib/gummi/api.rb +3 -1
  5. data/lib/gummi/db_layer/default_index.rb +15 -0
  6. data/lib/gummi/db_layer/document.rb +206 -0
  7. data/lib/gummi/db_layer/document/attributes.rb +40 -0
  8. data/lib/gummi/db_layer/document/object.rb +15 -0
  9. data/lib/gummi/db_layer/document/search/filtered.rb +42 -0
  10. data/lib/gummi/db_layer/document/search/raw.rb +12 -0
  11. data/lib/gummi/db_layer/document/search/result.rb +34 -0
  12. data/lib/gummi/db_layer/document/search/searching.rb +51 -0
  13. data/lib/gummi/db_layer/fields/boolean.rb +13 -0
  14. data/lib/gummi/db_layer/fields/integer.rb +16 -0
  15. data/lib/gummi/db_layer/fields/keyword.rb +15 -0
  16. data/lib/gummi/db_layer/fields/ngram_and_plain.rb +20 -0
  17. data/lib/gummi/db_layer/fields/path_hierarchy.rb +15 -0
  18. data/lib/gummi/db_layer/fields/positive_integer.rb +21 -0
  19. data/lib/gummi/db_layer/fields/sanitized_string.rb +30 -0
  20. data/lib/gummi/db_layer/fields/string.rb +17 -0
  21. data/lib/gummi/db_layer/fields/time.rb +17 -0
  22. data/lib/gummi/db_layer/index.rb +150 -0
  23. data/lib/gummi/entity_layer/entity.rb +22 -0
  24. data/lib/gummi/errors.rb +7 -0
  25. data/lib/gummi/repository_layer/repository.rb +39 -0
  26. data/lib/gummi/repository_layer/repository/result.rb +42 -0
  27. data/lib/gummi/version.rb +1 -1
  28. data/lib/repobahn/repository.rb +25 -33
  29. data/lib/repobahn/repository/active_record.rb +17 -0
  30. data/spec/fixtures/admin/auto.rb +6 -0
  31. data/spec/fixtures/admin/cars.rb +12 -0
  32. data/spec/fixtures/admin/countries.rb +9 -0
  33. data/spec/fixtures/admin/country.rb +6 -0
  34. data/spec/fixtures/admin/db/country.rb +7 -0
  35. data/spec/fixtures/admin/db/vehicle.rb +7 -0
  36. data/spec/fixtures/cities.rb +7 -0
  37. data/spec/fixtures/city.rb +6 -0
  38. data/spec/fixtures/db/animal.rb +9 -0
  39. data/spec/fixtures/db/boat.rb +9 -0
  40. data/spec/fixtures/db/car.rb +9 -0
  41. data/spec/fixtures/db/city.rb +8 -0
  42. data/spec/fixtures/db/enemy.rb +10 -0
  43. data/spec/fixtures/db/game.rb +7 -0
  44. data/spec/fixtures/db/person.rb +15 -0
  45. data/spec/fixtures/db/rating.rb +11 -0
  46. data/spec/fixtures/db/ship.rb +18 -0
  47. data/spec/{models → fixtures}/people.rb +6 -2
  48. data/spec/{models → fixtures}/person.rb +3 -2
  49. data/spec/lib/gummi/db_layer/document_spec.rb +124 -0
  50. data/spec/lib/gummi/{entity_spec.rb → entity_layer/entity_spec.rb} +3 -1
  51. data/spec/lib/gummi/repository_layer/repository_spec.rb +63 -0
  52. data/spec/lib/repobahn/repository_spec.rb +72 -0
  53. data/spec/spec_helper.rb +37 -9
  54. metadata +87 -37
  55. data/lib/gummi/default_index.rb +0 -13
  56. data/lib/gummi/document.rb +0 -139
  57. data/lib/gummi/document/attributes.rb +0 -28
  58. data/lib/gummi/document/object.rb +0 -12
  59. data/lib/gummi/document/search/filtered.rb +0 -39
  60. data/lib/gummi/document/search/raw.rb +0 -9
  61. data/lib/gummi/document/search/result.rb +0 -25
  62. data/lib/gummi/document/search/searching.rb +0 -45
  63. data/lib/gummi/entity.rb +0 -20
  64. data/lib/gummi/fields/boolean.rb +0 -10
  65. data/lib/gummi/fields/integer.rb +0 -14
  66. data/lib/gummi/fields/keyword.rb +0 -13
  67. data/lib/gummi/fields/ngram_and_plain.rb +0 -18
  68. data/lib/gummi/fields/path_hierarchy.rb +0 -13
  69. data/lib/gummi/fields/positive_integer.rb +0 -19
  70. data/lib/gummi/fields/sanitized_string.rb +0 -28
  71. data/lib/gummi/fields/string.rb +0 -15
  72. data/lib/gummi/fields/time.rb +0 -15
  73. data/lib/gummi/index.rb +0 -146
  74. data/lib/gummi/repository.rb +0 -38
  75. data/lib/gummi/repository/result.rb +0 -30
  76. data/spec/lib/gummi/document_spec.rb +0 -73
  77. data/spec/lib/gummi/repository/result_spec.rb +0 -18
  78. data/spec/lib/gummi/repository_spec.rb +0 -81
  79. data/spec/models/db/person.rb +0 -15
@@ -0,0 +1,34 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Document
4
+ module Search
5
+ class Result
6
+
7
+ attr_reader :took, :total, :hits
8
+
9
+ def initialize(response, converter, per_page, page)
10
+ @response = Hashie::Mash.new response
11
+ @took = @response.hits.took
12
+ @total = @response.hits.total
13
+ @hits = @response.hits.hits
14
+ @converter = converter
15
+ @per_page = per_page
16
+ @page = page
17
+ end
18
+
19
+ def documents
20
+ @documents ||= begin
21
+ documents = Array(converter.hits_to_documents(hits)) if hits
22
+ Leaflet::Collection.new documents, total: total, page: page, per_page: per_page
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :response, :converter, :per_page, :page, :hits
29
+
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,51 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Document
4
+ module Search
5
+ module Searching
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Virtus.model
10
+
11
+ attribute :document_class, Class
12
+ attribute :index, String, default: lambda { |search, attr| Gummi::DbLayer::DefaultIndex.name }
13
+ attribute :type, String
14
+ attribute :page, Gummi::DbLayer::Fields::PositiveInteger, default: 1
15
+ attribute :per_page, Gummi::DbLayer::Fields::PositiveInteger, default: 300
16
+ attribute :options, Hash, default: {}
17
+ end
18
+
19
+ def to_client_args
20
+ args = {
21
+ index: index,
22
+ from: from,
23
+ size: size,
24
+ }
25
+ args[:type] = type if type
26
+ args.merge options
27
+ end
28
+
29
+ def execute
30
+ Gummi::DbLayer::Document::Search::Result.new client.search(to_client_args), document_class, per_page, page
31
+ end
32
+
33
+ private
34
+
35
+ def size
36
+ per_page
37
+ end
38
+
39
+ def from
40
+ per_page * (page - 1)
41
+ end
42
+
43
+ def client
44
+ Gummi::API.client
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class Boolean < Virtus::Attribute::Boolean
5
+
6
+ def mapping
7
+ { type: 'boolean' }
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class Integer < Virtus::Attribute
5
+
6
+ def coerce(value)
7
+ value.to_i if value.present?
8
+ end
9
+
10
+ def mapping
11
+ { type: 'integer' }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class Keyword < Virtus::Attribute
5
+ def coerce(value)
6
+ value
7
+ end
8
+
9
+ def mapping
10
+ { type: 'string', index_analyzer: 'keyword_index_analyzer', search_analyzer: 'keyword_search_analyzer' }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class NgramAndPlain < Virtus::Attribute
5
+ def coerce(value)
6
+ value
7
+ end
8
+
9
+ def mapping
10
+ { type: 'multi_field',
11
+ fields: {
12
+ name => { type: 'string', index_analyzer: 'text_index_analyzer', search_analyzer: 'text_search_analyzer' },
13
+ :plain => { type: 'string', index_analyzer: 'string_index_analyzer', search_analyzer: 'text_search_analyzer' },
14
+ }
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class PathHierarchy < Virtus::Attribute
5
+ def coerce(value)
6
+ value
7
+ end
8
+
9
+ def mapping
10
+ {type: 'string', index_analyzer: 'path_hierarchy_analyzer' }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class PositiveInteger < Virtus::Attribute
5
+
6
+ def coerce(value)
7
+ coerced = value.to_i
8
+ if coerced > 0
9
+ coerced
10
+ else
11
+ default_value.value
12
+ end
13
+ end
14
+
15
+ def mapping
16
+ { type: 'integer' }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,30 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class SanitizedString < Virtus::Attribute
5
+
6
+ def coerce(value)
7
+ return nil if value.blank?
8
+ sanitize_string_for_query(value.to_s)
9
+ end
10
+
11
+ def mapping
12
+ { type: 'string' }
13
+ end
14
+
15
+ def sanitize_string_for_query(str)
16
+ # Escape special characters
17
+ escaped_characters = Regexp.escape('\/\\+-&|!(){}[]^~*?:')
18
+ str = str.gsub(/([#{escaped_characters}])/) do |match|
19
+ '\\'+match
20
+ end
21
+
22
+ # Escape odd quotes
23
+ quote_count = str.count '"'
24
+ str = str.gsub(/(.*)"(.*)/, '\1\"\3') if quote_count % 2 == 1
25
+ str
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class String < Virtus::Attribute
5
+
6
+
7
+ def coerce(value)
8
+ value
9
+ end
10
+
11
+ def mapping
12
+ { type: 'string' }
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Fields
4
+ class Time < Virtus::Attribute
5
+
6
+ def coerce(value)
7
+ return nil unless value.respond_to? :in_time_zone
8
+ value.in_time_zone 'UTC'
9
+ end
10
+
11
+ def mapping
12
+ {type: "date"}
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,150 @@
1
+ module Gummi
2
+ module DbLayer
3
+ module Index
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+
8
+ # Return true if created or false if already created.
9
+ #
10
+ def setup
11
+ created_settings = client.indices.create index: name, body: { settings: settings }
12
+ created_settings.present?
13
+ refresh
14
+ rescue ::Elasticsearch::Transport::Transport::Errors::BadRequest => exception
15
+ false
16
+ end
17
+
18
+ # Return true if successful or already teared down.
19
+ #
20
+ # Raises NotImplementedError in production.
21
+ #
22
+ def teardown
23
+ raise NotImplementedError if Gummi.env == 'production'
24
+ response = client.indices.delete index: name
25
+ response.present?
26
+ rescue ::Elasticsearch::Transport::Transport::Errors::NotFound
27
+ true
28
+ end
29
+
30
+ def name
31
+ raise "Implement me"
32
+ end
33
+
34
+ def refresh
35
+ client.indices.refresh
36
+ client.cluster.health wait_for_status: :yellow
37
+ end
38
+
39
+ def settings
40
+ default_settings
41
+ end
42
+
43
+ def default_settings
44
+ {
45
+ index: {
46
+ # Main Settings
47
+ number_of_shards: '3',
48
+ number_of_replicas: (Gummi.env == 'production' ? '2' : '0'),
49
+ refresh_interval: '1s',
50
+ store: { type: (Gummi.env == 'test' ? :memory : :niofs) },
51
+ mapper: { dynamic: false },
52
+
53
+ analysis: {
54
+
55
+ # Tokenizers are just some sort of "tool" or "module" that can be applied to analyzers.
56
+ tokenizer: {
57
+ # This one is a little bit more general and is able to chop any word into all of its components.
58
+ ngram_tokenizer: {
59
+ type: 'nGram',
60
+ min_gram: 1,
61
+ max_gram: 7,
62
+ token_chars: [ 'letter', 'digit' ],
63
+ }
64
+
65
+ },
66
+
67
+ # Now we are ready to use our tokenizers.
68
+ # Let's create the most important thing: Analyzers.
69
+ analyzer: {
70
+
71
+ path_hierarchy_analyzer: {
72
+ type: 'custom',
73
+ tokenizer: 'path_hierarchy',
74
+ },
75
+ # When adding long text to Elastic, we most likely are going to use this
76
+ # analyzer. This is commonly used for titles and descriptions.
77
+ text_index_analyzer: {
78
+ type: 'custom',
79
+ tokenizer: 'ngram_tokenizer', # Chopping every word up into tokens
80
+ filter: {
81
+ 0 => 'standard', # Some default transformations
82
+ 1 => 'lowercase', # Make everything lowercase
83
+ 2 => 'word_delimiter', # E.g. "O'Neil" -> "O Neil", "Victoria's" -> "Victoria"
84
+ 2 => 'asciifolding', # Transform everything into ASCII
85
+ },
86
+ },
87
+
88
+ # For smaller texts, such as the city "stockholm", we don't want any
89
+ # tokenizing. It's enough to explicitly save the word as it is.
90
+ # As a matter of fact, if we would tokenize the city, then the facets
91
+ # would report that we have Transports in "st" "sto" "stoc" etc.
92
+ string_index_analyzer: {
93
+ type: 'custom',
94
+ tokenizer: 'standard',
95
+ filter: {
96
+ # The filters, however, are identical to the other analyzer.
97
+ 0 => 'standard',
98
+ 1 => 'lowercase',
99
+ 2 => 'word_delimiter',
100
+ 3 => 'asciifolding',
101
+ },
102
+ },
103
+
104
+ # For finding Slugs
105
+ keyword_index_analyzer: {
106
+ type: 'custom',
107
+ tokenizer: 'keyword',
108
+ filter: {
109
+ 0 => 'lowercase',
110
+ 1 => 'asciifolding',
111
+ },
112
+ },
113
+
114
+ # This is an analyzer that we apply to the search query itself.
115
+ text_search_analyzer: {
116
+ type: 'custom',
117
+ tokenizer: 'standard',
118
+ filter: {
119
+ 0 => 'standard',
120
+ 1 => 'lowercase',
121
+ 2 => 'word_delimiter',
122
+ 3 => 'asciifolding',
123
+ },
124
+ },
125
+
126
+ # This is an analyzer that we apply to the search query itself.
127
+ keyword_search_analyzer: {
128
+ type: 'custom',
129
+ tokenizer: 'keyword',
130
+ filter: {
131
+ 0 => 'lowercase',
132
+ 1 => 'asciifolding',
133
+ },
134
+ },
135
+
136
+ }
137
+ }
138
+ }
139
+ }
140
+ end
141
+
142
+ def client
143
+ Gummi::API.client
144
+ end
145
+
146
+ end
147
+
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,22 @@
1
+ module Gummi
2
+ module EntityLayer
3
+ module Entity
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include Repobahn::Entity
8
+ end
9
+
10
+ attr_accessor :id
11
+ attr_accessor :version
12
+
13
+ def ==(other)
14
+ other &&
15
+ self.id == other.id &&
16
+ self.version == other.version &&
17
+ self.attributes == other.attributes
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,7 @@
1
+ module Gummi
2
+ module Errors
3
+
4
+ ImplicitMappingForbidden = Class.new(ArgumentError)
5
+
6
+ end
7
+ end