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