elastics 0.5.0 → 0.6.1

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.
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: