elastics 0.5.0 → 0.6.1

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: 4414829781480b6aaf6c1fa14bf0450a0e164906
4
- data.tar.gz: c63d19ed109fae4244b4c9427814c4ffa1f10de6
3
+ metadata.gz: 4aec32b4215bd40d80c3d367f2166a0b91a278b9
4
+ data.tar.gz: ccae5ce2bf5904bd2605f152f4b02e1556449035
5
5
  SHA512:
6
- metadata.gz: 43f6b27708081b51906e6768edb32cd54243974582c0beae93a2030f3b605d62e8f5c4a245c9b5dc8fa6a7de0e0590756404bfb748961ad66e513dc99bcb7bda
7
- data.tar.gz: c02fc34b15851945a8b5da43e93326062c903383dca4a7a8e21134c8d199f92a1918bebcb2f71e32f9c08046c3e8520e97765c5045b3d52682788be9dd97c321
6
+ metadata.gz: 4327171fbed3358386552e0a516b55e10c13aa47efea873e80fdc122a409862a73564a744ec49e2f7889c5be6e71e4c1d8a937aba1a48e2b915d0fe5f07cedba
7
+ data.tar.gz: cf810f168e30abb9167ff719c653f9e604764eb845734a0c16fa15146db3e4c558d6206258d83d394c2fe999297c7a029c0d2515a5de0c5a7e3338141510634c
data/README.md CHANGED
@@ -101,8 +101,15 @@ end
101
101
  User.elastics # Elastics::Client instance
102
102
  User.elastics_params # hash with index & type values for the model
103
103
  User.request_elastics(params) # performs request merging params with elastics_params
104
- User.search_elastics(data)
104
+
105
+ search = User.search_elastics(data)
105
106
  # Returns Elastics::ActiveRecord::SearchResult object with some useful methods
107
+ search.relation # User.where(id: found_ids)
108
+ search.collection # Array of users sorted as in elastics response
109
+
110
+ # Set scope applied to relation:
111
+ search = Project.search_elastics(data, scope: ->(s) { s.includes(:user) })
112
+ search.collection.first.user # eagger-loaded
106
113
 
107
114
  # Indexing on create/update can be skipped with skip_elastics
108
115
  User.skip_elastics { users.each { |x| x.update_attributes(smth: 'not indexed') } }
@@ -136,6 +143,8 @@ production:
136
143
  index_prefix: app_
137
144
  ```
138
145
 
146
+ YAML is passed througn ERB before parsing.
147
+
139
148
  #### Create mappings & import data
140
149
  ```
141
150
  $ rake elastics:migrate elastics:reindex
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_runtime_dependency 'httpclient', '~> 2.4.0'
21
+ spec.add_runtime_dependency 'httpclient', '~> 2.4'
22
22
 
23
23
  spec.add_development_dependency 'bundler', '~> 1.5'
24
24
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -8,9 +8,11 @@ module Elastics
8
8
  autoload :Instrumentation, 'elastics/instrumentation'
9
9
  autoload :Model, 'elastics/model'
10
10
  autoload :QueryHelper, 'elastics/query_helper'
11
+ autoload :SearchQuery, 'elastics/search_query'
11
12
  autoload :Result, 'elastics/result'
12
13
  autoload :Tasks, 'elastics/tasks'
13
14
  autoload :VersionManager, 'elastics/version_manager'
15
+ autoload :RSpec, 'elastics/rspec'
14
16
  end
15
17
 
16
18
  require 'elastics/railtie' if defined?(Rails)
@@ -38,7 +38,10 @@ module Elastics
38
38
  private
39
39
  def install_elastics_hooks(hooks)
40
40
  if hooks.include?(:update)
41
- after_commit :index_elastics, on: [:create, :update], unless: :skip_elastics?
41
+ after_commit :index_elastics,
42
+ on: [:create, :update],
43
+ unless: :skip_elastics?,
44
+ if: -> { previous_changes.any? }
42
45
  end
43
46
  if hooks.include?(:destroy)
44
47
  after_commit :delete_elastics, on: [:destroy], unless: :skip_elastics?
@@ -21,8 +21,10 @@ module Elastics
21
21
 
22
22
  # Finds items by ids and returns array in the order in which ids were given.
23
23
  # Every missing record is replaced with `nil` in the result.
24
- def find_all_ordered(ids)
25
- items_by_id = where(id: ids).index_by(&:id)
24
+ # If `conditions_present` is `true` it doesn't add where clause.
25
+ def find_all_ordered(ids, conditions_present = false)
26
+ relation = conditions_present ? where(id: ids) : self
27
+ items_by_id = relation.index_by(&:id)
26
28
  ids.map { |i| items_by_id[i] }
27
29
  end
28
30
 
@@ -1,8 +1,12 @@
1
1
  module Elastics
2
2
  module ActiveRecord
3
3
  class SearchResult < Result::Search
4
+ # It expects `:model` option with a model-class.
5
+ # Optionally pass `scope` option with a lambda which takes and modifies
6
+ # relation.
4
7
  def initialize(response, options = {})
5
8
  @model = options[:model]
9
+ @scope = options[:scope]
6
10
  super response, options
7
11
  end
8
12
 
@@ -12,11 +16,14 @@ module Elastics
12
16
  end
13
17
 
14
18
  def collection
15
- @collection ||= @model.find_all_ordered ids_to_find
19
+ @collection ||= relation.find_all_ordered(ids_to_find, true)
16
20
  end
17
21
 
18
22
  def relation
19
- @model.where(id: ids_to_find)
23
+ @relation ||= begin
24
+ result = @model.where(id: ids_to_find)
25
+ @scope ? @scope.call(result) : result
26
+ end
20
27
  end
21
28
  end
22
29
  end
@@ -26,11 +26,11 @@ module Elastics
26
26
  end
27
27
 
28
28
  def debug!(dev = STDOUT)
29
- @client.debug_dev = dev
29
+ client.debug_dev = dev
30
30
  end
31
31
 
32
32
  def debug_off!
33
- @client.debug_dev = nil
33
+ client.debug_dev = nil
34
34
  end
35
35
 
36
36
  def set_index(index, type = nil)
@@ -13,11 +13,12 @@ module Elastics
13
13
  end
14
14
 
15
15
  def reset_elastics_index_name
16
- @elastics_index_name = if superclass.respond_to?(:elastics_index_name)
17
- superclass.elastics_index_name
18
- else
19
- compute_elastics_index_name
20
- end
16
+ @elastics_index_name =
17
+ if respond_to?(:superclass) && superclass.respond_to?(:elastics_index_name)
18
+ superclass.elastics_index_name
19
+ else
20
+ compute_elastics_index_name
21
+ end
21
22
  end
22
23
 
23
24
  def compute_elastics_index_name
@@ -24,7 +24,7 @@ module Elastics
24
24
  # for specified field.
25
25
  def terms_query(field, val, options = {})
26
26
  if val.is_a?(Array)
27
- {terms: {field => val}.merge(options)}
27
+ {terms: {field => val}.merge!(options)}
28
28
  else
29
29
  {term: {field => val}}
30
30
  end
@@ -0,0 +1,38 @@
1
+ module Elastics
2
+ module RSpec
3
+ module_function
4
+
5
+ # Adds around filter to perform elastics specific helper actions.
6
+ #
7
+ # - Enables autorefresh for each example,
8
+ # - Executes `clear_elastics` before each example,
9
+ # - Performs migration (once, for first occured example).
10
+ #
11
+ # Filter is applied only to tagged examples (`:elastics` by default).
12
+ #
13
+ # RSpec.configure do |config|
14
+ # Elastics::RSpec.configure(config)
15
+ # end
16
+ def configure(config, tag = :elastics)
17
+ migrated = false
18
+ error = nil
19
+ config.around tag => true do |ex|
20
+ if migrated
21
+ raise error if error
22
+ Model.list.each(&:clear_elastics)
23
+ else
24
+ begin
25
+ Tasks.drop_indices
26
+ Tasks.migrate
27
+ rescue Error => e
28
+ error = e
29
+ raise e
30
+ ensure
31
+ migrated = true
32
+ end
33
+ end
34
+ AutoRefresh.enable! { ex.run }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,99 @@
1
+ module Elastics
2
+ # Helpers to build search query.
3
+ #
4
+ # class Query
5
+ # include Elastics::SearchQuery
6
+ #
7
+ # # implement any of:
8
+ # # query, phrase_query, query_filters, post_filter, aggregations
9
+ #
10
+ # def phrase_query
11
+ # {bool: {should: [
12
+ # {multi_match: {
13
+ # # ...
14
+ # }}
15
+ # ]}}
16
+ # # or just
17
+ # {math: {message: params[:query_string]}}
18
+ # end
19
+ #
20
+ # def query_filters
21
+ # [
22
+ # {term: {published: true}},
23
+ # terms_array_query(:tag, params[:tags], execution: :and),
24
+ # some_complex_filter,
25
+ # ]
26
+ # end
27
+ #
28
+ # def aggregations
29
+ # {
30
+ # tag: {terms: {
31
+ # field: :tag,
32
+ # size: 10,
33
+ # shard_size: 10,
34
+ # }},
35
+ # }
36
+ # end
37
+ # end
38
+ #
39
+ # result = Model.search_elastics Query.new(params).as_json
40
+ module SearchQuery
41
+ include Elastics::QueryHelper
42
+
43
+ attr_reader :params
44
+
45
+ def initialize(params)
46
+ @params = params
47
+ end
48
+
49
+ def as_json
50
+ page = params[:page] || 1
51
+ per_page = params[:per_page] || 10
52
+ result = {
53
+ from: (page - 1) * per_page,
54
+ size: per_page,
55
+ fields: [],
56
+ query: query,
57
+ sort: sort,
58
+ }
59
+ post_filter = self.post_filter
60
+ result[:post_filter] = post_filter if post_filter
61
+ aggregations = self.aggregations
62
+ result[:aggregations] = aggregations if aggregations
63
+ result
64
+ end
65
+
66
+ # Builds query from phrase_query & query_filters.
67
+ def query
68
+ normalize_query(phrase_query, query_filters.compact)
69
+ end
70
+
71
+ def phrase_query
72
+ end
73
+
74
+ def query_filters
75
+ []
76
+ end
77
+
78
+ def post_filter
79
+ end
80
+
81
+ def aggregations
82
+ end
83
+
84
+ # Takes `params[:sort]` and returns it compatible with elastics.
85
+ # Wraps scalars into array, hashes are converted into arrays,
86
+ # array are passed as is.
87
+ #
88
+ # {name: :asc, _score: :desc} => [{name: :asc}, {_score: :desc}]
89
+ # :created_at => [:created_at]
90
+ def sort
91
+ val = params[:sort]
92
+ case val
93
+ when Hash then val.map { |x| Hash[[x]] }
94
+ when Array then val
95
+ else val ? [val] : []
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,3 +1,5 @@
1
+ require 'yaml'
2
+ require 'erb'
1
3
  require 'active_support'
2
4
  require 'active_support/core_ext'
3
5
 
@@ -23,7 +25,21 @@ module Elastics
23
25
  extend self
24
26
 
25
27
  def log(*args)
26
- puts(*args)
28
+ puts(*args) if verbose
29
+ Rails.logger.info { "Elastics: #{args.join ' '}" } if defined?(Rails)
30
+ end
31
+
32
+ attr_accessor :verbose
33
+
34
+ def suppress_messages
35
+ verbose_was, self.verbose = verbose, false
36
+ yield
37
+ ensure
38
+ self.verbose = verbose_was
39
+ end
40
+
41
+ def load_yaml(file)
42
+ YAML.load(ERB.new(File.read(file)).result)
27
43
  end
28
44
 
29
45
  private
@@ -11,12 +11,54 @@ module Elastics
11
11
  @indices_paths ||= base_paths.map { |x| File.join x, 'indices' }
12
12
  end
13
13
 
14
+ # Merges settings from single files and dirs.
14
15
  def indices_settings
15
- @indices_settings ||= indices_paths.map { |path| Dir["#{path}/*.yml"] }.
16
+ @indices_settings ||= indices_from_files.merge!(indices_from_dirs)
17
+ end
18
+
19
+ # Reads indices settings from single yml file.
20
+ # Setting can be given specific for env or one for all envs.
21
+ #
22
+ # tweet:
23
+ # development:
24
+ # settings:
25
+ # number_of_shards: 5
26
+ # production:
27
+ # settings:
28
+ # number_of_shards: 10
29
+ #
30
+ # user:
31
+ # settings:
32
+ # number_of_shards: 5
33
+ def indices_from_files
34
+ indices_paths.each_with_object({}) do |path, hash|
35
+ file = "#{path}.yml"
36
+ next unless File.exists?(file)
37
+ load_yaml(file).each do |name, data|
38
+ hash[name] = data[Rails.env] || data
39
+ end
40
+ end
41
+ end
42
+
43
+ # Reads indices settings from separate files. Index name is taken from
44
+ # file name, setting can be given specific for env or one for all envs.
45
+ #
46
+ # development:
47
+ # settings:
48
+ # number_of_shards: 5
49
+ # production:
50
+ # settings:
51
+ # number_of_shards: 10
52
+ #
53
+ # # or
54
+ # settings:
55
+ # number_of_shards: 1
56
+ def indices_from_dirs
57
+ indices_paths.map { |path| Dir["#{path}/*.yml"] }.
16
58
  flatten.sort.
17
59
  each_with_object({}) do |file, hash|
18
60
  name = File.basename file, '.yml'
19
- data = YAML.load_file(file)
61
+ data = load_yaml(file)
20
62
  hash[name] = data[Rails.env] || data
21
63
  end
22
64
  end
@@ -76,10 +118,14 @@ module Elastics
76
118
  new_versions = {}
77
119
  post_aliases options do |index|
78
120
  new_versions[index] = version_manager.next_version index
79
- [
80
- alias_action(:remove, index, :current),
81
- alias_action(:add, index, :next),
82
- ]
121
+ if client.index_exists?(versioned_index_name(index, :current))
122
+ [
123
+ alias_action(:remove, index, :current),
124
+ alias_action(:add, index, :next),
125
+ ]
126
+ else
127
+ alias_action(:add, index, :next)
128
+ end
83
129
  end
84
130
  drop_indices(options.merge version: :current) if options.fetch(:drop, true)
85
131
  new_versions.each do |index, version|
@@ -21,12 +21,43 @@ module Elastics
21
21
  end
22
22
  end
23
23
 
24
+ # Merges mappings from single files and dirs.
24
25
  def mappings
25
- @mappings ||= mappings_paths.map { |path| Dir["#{path}/*.yml"] }.
26
+ @mappings ||= mappings_from_files.merge!(mappings_from_dirs)
27
+ end
28
+
29
+ # Reads mappings from single yml file.
30
+ #
31
+ # user:
32
+ # dynamic: false
33
+ # properties:
34
+ # name: string
35
+ # tweet:
36
+ # properties:
37
+ # content: string
38
+ # user_id: integer
39
+ def mappings_from_files
40
+ mappings_paths.each_with_object({}) do |path, hash|
41
+ file = "#{path}.yml"
42
+ next unless File.exists?(file)
43
+ load_yaml(file).each do |name, data|
44
+ hash[name] = fix_mapping(name, data)
45
+ end
46
+ end
47
+ end
48
+
49
+ # Reads mappings from separate files. Type name is taken from file name.
50
+ #
51
+ # # user.yml
52
+ # dynamic: false
53
+ # properties:
54
+ # name: string
55
+ def mappings_from_dirs
56
+ mappings_paths.map { |path| Dir["#{path}/*.yml"] }.
26
57
  flatten.sort.
27
58
  each_with_object({}) do |file, hash|
28
59
  name = File.basename file, '.yml'
29
- hash[name] = fix_mapping(name, YAML.load_file(file))
60
+ hash[name] = fix_mapping(name, load_yaml(file))
30
61
  end
31
62
  end
32
63
 
@@ -1,5 +1,5 @@
1
1
  module Elastics
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.1'.freeze
3
3
 
4
4
  def self.gem_version
5
5
  Gem::Version.new VERSION
@@ -10,6 +10,7 @@ namespace 'elastics' do
10
10
  types: ENV['types'] && ENV['types'].split(',').map(&:strip),
11
11
  indices: args.extras.empty? ? nil : args.extras
12
12
  }
13
+ Elastics::Tasks.verbose = ENV.fetch('VERBOSE', 'true') == 'true'
13
14
  end
14
15
 
15
16
  desc 'Drop administrative index'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Melentiev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-14 00:00:00.000000000 Z
11
+ date: 2017-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 2.4.0
19
+ version: '2.4'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 2.4.0
26
+ version: '2.4'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -131,6 +131,8 @@ files:
131
131
  - lib/elastics/railtie.rb
132
132
  - lib/elastics/result.rb
133
133
  - lib/elastics/result/search.rb
134
+ - lib/elastics/rspec.rb
135
+ - lib/elastics/search_query.rb
134
136
  - lib/elastics/tasks.rb
135
137
  - lib/elastics/tasks/config.rb
136
138
  - lib/elastics/tasks/indices.rb
@@ -166,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
166
168
  version: '0'
167
169
  requirements: []
168
170
  rubyforge_project:
169
- rubygems_version: 2.4.2
171
+ rubygems_version: 2.6.8
170
172
  signing_key:
171
173
  specification_version: 4
172
174
  summary: ElasticSearch client with ActiveRecord integration
@@ -178,4 +180,3 @@ test_files:
178
180
  - spec/lib/elastics/instrumentation_spec.rb
179
181
  - spec/lib/elastics/model/schema_spec.rb
180
182
  - spec/spec_helper.rb
181
- has_rdoc: