elasticsearch-model 0.1.4 → 0.1.5

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,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YzQ5M2Q0YTQyMWFiNTY5OGIwNzYyMTA2NTkzOGFmNDczZWQzZjQ4Mg==
4
+ MjRiMDMxMDViNGQxODdmNTQ3Nzc4N2IyOWYyMTdiNzhlMmIzZWZhYw==
5
5
  data.tar.gz: !binary |-
6
- N2QyZDk1ODYzYjJlY2E3MGY0ZGNlNmFlZDBkMGYzMTk0NzdlMTlmOQ==
6
+ NjE4NjI4NDBkNDdjNTU4MjU1YTJhZDU0MzRmZTk1NDdkMTgxNDc1Mw==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NGI1ZTM1ODZhYmY1NmVhZmYxYTRjNjRkNTMyYjYxOWY5MzZhY2JkYTlmOWY1
10
- ZTlhMWI1NTkwOWNlYWVjNWU3ZWQxZTM3NWQ5MDIyZmUxNzRlYjJjNzk0Mjk4
11
- MzU2OTU3NmQzNjRjYzNkZmQ1MmI3MTdmZjAxYjlmYWY3MWEzYWY=
9
+ NWJhZjZjNzRiYmYwOTVlZjc4ZTk0ZTRhMWRkNWI0MWMxZTAxODVmYzAwZTM5
10
+ NDc4MTkxMDZlOTRlMDc1MDkyMGUwZTNjZDU4MWE2Y2I5YmQ4MGI2NDA0NDVi
11
+ NDI0MGUxNjdjOTE2MTQ4MmM2OTcwM2ZjMDA5Nzg3YTcwY2YzOTg=
12
12
  data.tar.gz: !binary |-
13
- Y2ZjZGM0MGY4OWFhMzZkNmI0NTU4YTlhYzkzNGZmN2FhNGUxZDg2N2NkOGQ3
14
- ZjM2YjIzNTYxODk2MmJjYWViZmU2NGI5YWI0NjBhNGMyOThiYWVlOTRiYjI5
15
- ZjUzYWNlNDZhMWM4N2MwYjE3ZmMwMDBiNzVkOTNmNTEwMTJmOTc=
13
+ NGM2NGM2NTBjNTEzMzdhNjZmYzI5MzM4NDZiYjEwOTQ4Y2Y2NWQyYTZiYWZl
14
+ YWExZWQzZmI5NWRmOWRjNzExMDE3NGFjMTM2ODk4NDliOTNkOWI1M2UwZjRk
15
+ MjA1MWFiODE1ZWY5ZGExZDMxZWZmNzIxNzNhNDAyMDlkZjdjZWI=
data/CHANGELOG.md CHANGED
@@ -1,4 +1,12 @@
1
- ## 0.1.3
1
+ ## 0.1.5
2
+
3
+ * Improved documentation
4
+ * Fixes and improvements to the "will_paginate" integration
5
+ * Added a `:preprocess` option to the `import` method
6
+ * Changed, that attributes are fetched from `as_indexed_json` in the `update_document` method
7
+ * Added an option to the import method to return an array of error messages instead of just count
8
+ * Fixed many problems with dependency hell
9
+ * Fixed tests so they run on Ruby 2.2
2
10
 
3
11
  ## 0.1.2
4
12
 
data/README.md CHANGED
@@ -126,7 +126,7 @@ Article.__elasticsearch__.client = Elasticsearch::Client.new host: 'api.server.o
126
126
  Or configure the client for all models:
127
127
 
128
128
  ```ruby
129
- Elasticsearch::Model.client = Elasticsearch::Client.new log:true
129
+ Elasticsearch::Model.client = Elasticsearch::Client.new log: true
130
130
  ```
131
131
 
132
132
  You might want to do this during you application bootstrap process, e.g. in a Rails initializer.
@@ -146,6 +146,9 @@ Article.import
146
146
  # => 0
147
147
  ```
148
148
 
149
+ It's possible to import only records from a specific `scope`, transform the batch with the `transform`
150
+ and `preprocess` options, or re-create the index by deleting it and creating it with correct mapping with the `force` option -- look for examples in the method documentation.
151
+
149
152
  No errors were reported during importing, so... let's search the index!
150
153
 
151
154
 
@@ -507,7 +510,8 @@ Article.first.__elasticsearch__.as_indexed_json
507
510
  # => {"id"=>1, "title"=>"Quick brown fox"}
508
511
  ```
509
512
 
510
- If you want to customize the serialization, just implement the `as_indexed_json` method yourself:
513
+ If you want to customize the serialization, just implement the `as_indexed_json` method yourself,
514
+ for instance with the [`as_json`](http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json) method:
511
515
 
512
516
  ```ruby
513
517
  class Article
@@ -524,6 +528,9 @@ Article.first.as_indexed_json
524
528
 
525
529
  The re-defined method will be used in the indexing methods, such as `index_document`.
526
530
 
531
+ Please note that in Rails 3, you need to either set `include_root_in_json: false`, or prevent adding
532
+ the "root" in the JSON representation with other means.
533
+
527
534
  #### Relationships and Associations
528
535
 
529
536
  When you have a more complicated structure/schema, you need to customize the `as_indexed_json` method -
data/Rakefile CHANGED
@@ -22,12 +22,18 @@ namespace :test do
22
22
  # test.warning = true
23
23
  end
24
24
 
25
- Rake::TestTask.new(:integration) do |test|
25
+ Rake::TestTask.new(:run_integration) do |test|
26
26
  Rake::Task['test:ci_reporter'].invoke if ENV['CI']
27
27
  test.libs << 'lib' << 'test'
28
28
  test.test_files = FileList["test/integration/**/*_test.rb"]
29
29
  end
30
30
 
31
+ desc "Run integration tests against ActiveModel 3 and 4"
32
+ task :integration do
33
+ sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/3.0.gemfile', __FILE__)}' bundle exec rake test:run_integration" unless defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
34
+ sh "BUNDLE_GEMFILE='#{File.expand_path('../gemfiles/4.0.gemfile', __FILE__)}' bundle exec rake test:run_integration"
35
+ end
36
+
31
37
  Rake::TestTask.new(:all) do |test|
32
38
  Rake::Task['test:ci_reporter'].invoke if ENV['CI']
33
39
  test.libs << 'lib' << 'test'
@@ -34,26 +34,24 @@ Gem::Specification.new do |s|
34
34
 
35
35
  s.add_development_dependency "sqlite3"
36
36
  s.add_development_dependency "activemodel", "> 3.0"
37
- s.add_development_dependency "activerecord", "> 4.0"
38
37
 
39
38
  s.add_development_dependency "oj"
40
39
  s.add_development_dependency "kaminari"
41
40
  s.add_development_dependency "will_paginate"
42
- # NOTE: Do not add Mongoid here, keep only in 3/4 files
43
41
 
44
- s.add_development_dependency "minitest", "~> 4.0"
42
+ s.add_development_dependency "minitest", "~> 4"
43
+ s.add_development_dependency "test-unit" if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
45
44
  s.add_development_dependency "shoulda-context"
46
45
  s.add_development_dependency "mocha"
47
46
  s.add_development_dependency "turn"
48
47
  s.add_development_dependency "yard"
49
48
  s.add_development_dependency "ruby-prof"
50
49
  s.add_development_dependency "pry"
51
- s.add_development_dependency "ci_reporter"
50
+ s.add_development_dependency "ci_reporter", "~> 1.9"
52
51
 
53
52
  if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
54
53
  s.add_development_dependency "simplecov"
55
54
  s.add_development_dependency "cane"
56
55
  s.add_development_dependency "require-prof"
57
- s.add_development_dependency "coveralls"
58
56
  end
59
57
  end
data/gemfiles/4.0.gemfile CHANGED
@@ -9,4 +9,3 @@ gemspec path: '../'
9
9
 
10
10
  gem 'activemodel', '~> 4'
11
11
  gem 'activerecord', '~> 4'
12
- gem 'mongoid', '~> 4.0.0.beta1'
@@ -78,17 +78,19 @@ module Elasticsearch
78
78
 
79
79
  module Importing
80
80
 
81
- # Fetch batches of records from the database
81
+ # Fetch batches of records from the database (used by the import method)
82
+ #
82
83
  #
83
84
  # @see http://api.rubyonrails.org/classes/ActiveRecord/Batches.html ActiveRecord::Batches.find_in_batches
84
85
  #
85
86
  def __find_in_batches(options={}, &block)
86
87
  named_scope = options.delete(:scope)
88
+ preprocess = options.delete(:preprocess)
87
89
 
88
90
  scope = named_scope ? self.__send__(named_scope) : self
89
91
 
90
92
  scope.find_in_batches(options) do |batch|
91
- yield batch
93
+ yield (preprocess ? self.__send__(preprocess, batch) : batch)
92
94
  end
93
95
  end
94
96
 
@@ -76,16 +76,36 @@ module Elasticsearch
76
76
  #
77
77
  # Article.import transform: transform
78
78
  #
79
+ # @example Update the batch before yielding it
80
+ #
81
+ # class Article
82
+ # # ...
83
+ # def enrich(batch)
84
+ # batch.each do |item|
85
+ # item.metadata = MyAPI.get_metadata(item.id)
86
+ # end
87
+ # batch
88
+ # end
89
+ # end
90
+ #
91
+ # Article.import preprocess: enrich
92
+ #
93
+ # @example Return an array of error elements instead of the number of errors, eg.
94
+ # to try importing these records again
95
+ #
96
+ # Article.import return: 'errors'
97
+ #
79
98
  def import(options={}, &block)
80
- errors = 0
99
+ errors = []
81
100
  refresh = options.delete(:refresh) || false
82
101
  target_index = options.delete(:index) || index_name
83
102
  target_type = options.delete(:type) || document_type
84
103
  transform = options.delete(:transform) || __transform
104
+ return_value = options.delete(:return) || 'count'
85
105
 
86
106
  unless transform.respond_to?(:call)
87
107
  raise ArgumentError,
88
- "Pass an object responding to `call` as the :transport option, #{transform.class} given"
108
+ "Pass an object responding to `call` as the :transform option, #{transform.class} given"
89
109
  end
90
110
 
91
111
  if options.delete(:force)
@@ -100,12 +120,17 @@ module Elasticsearch
100
120
 
101
121
  yield response if block_given?
102
122
 
103
- errors += response['items'].map { |k, v| k.values.first['error'] }.compact.length
123
+ errors += response['items'].select { |k, v| k.values.first['error'] }
104
124
  end
105
125
 
106
126
  self.refresh_index! if refresh
107
127
 
108
- return errors
128
+ case return_value
129
+ when 'errors'
130
+ errors
131
+ else
132
+ errors.size
133
+ end
109
134
  end
110
135
 
111
136
  def __batch_to_bulk(batch, transform)
@@ -336,7 +336,7 @@ module Elasticsearch
336
336
  def update_document(options={})
337
337
  if changed_attributes = self.instance_variable_get(:@__changed_attributes)
338
338
  attributes = if respond_to?(:as_indexed_json)
339
- changed_attributes.select { |k,v| self.as_indexed_json.keys.include? k }
339
+ self.as_indexed_json.select { |k,v| changed_attributes.keys.map(&:to_s).include? k.to_s }
340
340
  else
341
341
  changed_attributes
342
342
  end
@@ -102,11 +102,19 @@ module Elasticsearch
102
102
 
103
103
  # Include the paging methods in results and records
104
104
  #
105
- methods = [:current_page, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?]
105
+ methods = [:current_page, :offset, :length, :per_page, :total_entries, :total_pages, :previous_page, :next_page, :out_of_bounds?]
106
106
  Elasticsearch::Model::Response::Results.__send__ :delegate, *methods, to: :response
107
107
  Elasticsearch::Model::Response::Records.__send__ :delegate, *methods, to: :response
108
108
  end
109
109
 
110
+ def offset
111
+ (current_page - 1) * per_page
112
+ end
113
+
114
+ def length
115
+ search.definition[:size]
116
+ end
117
+
110
118
  # Main pagination method
111
119
  #
112
120
  # @example
@@ -1,5 +1,5 @@
1
1
  module Elasticsearch
2
2
  module Model
3
- VERSION = "0.1.4"
3
+ VERSION = "0.1.5"
4
4
  end
5
5
  end
@@ -1,4 +1,5 @@
1
1
  require 'test_helper'
2
+ require 'active_record'
2
3
 
3
4
  class Question < ActiveRecord::Base
4
5
  include Elasticsearch::Model
@@ -1,91 +1,5 @@
1
1
  require 'test_helper'
2
-
3
- # ----- Models definition -------------------------------------------------------------------------
4
-
5
- class Category < ActiveRecord::Base
6
- has_and_belongs_to_many :posts
7
- end
8
-
9
- class Author < ActiveRecord::Base
10
- has_many :authorships
11
-
12
- def full_name
13
- [first_name, last_name].compact.join(' ')
14
- end
15
- end
16
-
17
- class Authorship < ActiveRecord::Base
18
- belongs_to :author
19
- belongs_to :post, touch: true
20
- end
21
-
22
- class Comment < ActiveRecord::Base
23
- belongs_to :post, touch: true
24
- end
25
-
26
- class Post < ActiveRecord::Base
27
- has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
28
- after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
29
- has_many :authorships
30
- has_many :authors, through: :authorships
31
- has_many :comments
32
- end
33
-
34
- # ----- Search integration via Concern module -----------------------------------------------------
35
-
36
- module Searchable
37
- extend ActiveSupport::Concern
38
-
39
- included do
40
- include Elasticsearch::Model
41
- include Elasticsearch::Model::Callbacks
42
-
43
- # Set up the mapping
44
- #
45
- settings index: { number_of_shards: 1, number_of_replicas: 0 } do
46
- mapping do
47
- indexes :title, analyzer: 'snowball'
48
- indexes :created_at, type: 'date'
49
-
50
- indexes :authors do
51
- indexes :first_name
52
- indexes :last_name
53
- indexes :full_name, type: 'multi_field' do
54
- indexes :full_name
55
- indexes :raw, analyzer: 'keyword'
56
- end
57
- end
58
-
59
- indexes :categories, analyzer: 'keyword'
60
-
61
- indexes :comments, type: 'nested' do
62
- indexes :text
63
- indexes :author
64
- end
65
- end
66
- end
67
-
68
- # Customize the JSON serialization for Elasticsearch
69
- #
70
- def as_indexed_json(options={})
71
- {
72
- title: title,
73
- text: text,
74
- categories: categories.map(&:title),
75
- authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]),
76
- comments: comments.as_json(only: [:text, :author])
77
- }
78
- end
79
-
80
- # Update document in the index after touch
81
- #
82
- after_touch() { __elasticsearch__.index_document }
83
- end
84
- end
85
-
86
- # Include the search integration
87
- #
88
- Post.__send__ :include, Searchable
2
+ require 'active_record'
89
3
 
90
4
  module Elasticsearch
91
5
  module Model
@@ -133,6 +47,93 @@ module Elasticsearch
133
47
  end
134
48
  end
135
49
 
50
+ # ----- Models definition -------------------------------------------------------------------------
51
+
52
+ class Category < ActiveRecord::Base
53
+ has_and_belongs_to_many :posts
54
+ end
55
+
56
+ class Author < ActiveRecord::Base
57
+ has_many :authorships
58
+
59
+ def full_name
60
+ [first_name, last_name].compact.join(' ')
61
+ end
62
+ end
63
+
64
+ class Authorship < ActiveRecord::Base
65
+ belongs_to :author
66
+ belongs_to :post, touch: true
67
+ end
68
+
69
+ class Comment < ActiveRecord::Base
70
+ belongs_to :post, touch: true
71
+ end
72
+
73
+ class Post < ActiveRecord::Base
74
+ has_and_belongs_to_many :categories, after_add: [ lambda { |a,c| a.__elasticsearch__.index_document } ],
75
+ after_remove: [ lambda { |a,c| a.__elasticsearch__.index_document } ]
76
+ has_many :authorships
77
+ has_many :authors, through: :authorships
78
+ has_many :comments
79
+ end
80
+
81
+ # ----- Search integration via Concern module -----------------------------------------------------
82
+
83
+ module Searchable
84
+ extend ActiveSupport::Concern
85
+
86
+ included do
87
+ include Elasticsearch::Model
88
+ include Elasticsearch::Model::Callbacks
89
+
90
+ # Set up the mapping
91
+ #
92
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
93
+ mapping do
94
+ indexes :title, analyzer: 'snowball'
95
+ indexes :created_at, type: 'date'
96
+
97
+ indexes :authors do
98
+ indexes :first_name
99
+ indexes :last_name
100
+ indexes :full_name, type: 'multi_field' do
101
+ indexes :full_name
102
+ indexes :raw, analyzer: 'keyword'
103
+ end
104
+ end
105
+
106
+ indexes :categories, analyzer: 'keyword'
107
+
108
+ indexes :comments, type: 'nested' do
109
+ indexes :text
110
+ indexes :author
111
+ end
112
+ end
113
+ end
114
+
115
+ # Customize the JSON serialization for Elasticsearch
116
+ #
117
+ def as_indexed_json(options={})
118
+ {
119
+ title: title,
120
+ text: text,
121
+ categories: categories.map(&:title),
122
+ authors: authors.as_json(methods: [:full_name], only: [:full_name, :first_name, :last_name]),
123
+ comments: comments.as_json(only: [:text, :author])
124
+ }
125
+ end
126
+
127
+ # Update document in the index after touch
128
+ #
129
+ after_touch() { __elasticsearch__.index_document }
130
+ end
131
+ end
132
+
133
+ # Include the search integration
134
+ #
135
+ Post.__send__ :include, Searchable
136
+
136
137
  # ----- Reset the index -----------------------------------------------------------------
137
138
 
138
139
  Post.delete_all
@@ -1,23 +1,11 @@
1
1
  require 'test_helper'
2
+ require 'active_record'
2
3
 
3
4
  puts "ActiveRecord #{ActiveRecord::VERSION::STRING}", '-'*80
4
5
 
5
6
  module Elasticsearch
6
7
  module Model
7
8
  class ActiveRecordBasicIntegrationTest < Elasticsearch::Test::IntegrationTestCase
8
-
9
- class ::Article < ActiveRecord::Base
10
- include Elasticsearch::Model
11
- include Elasticsearch::Model::Callbacks
12
-
13
- settings index: { number_of_shards: 1, number_of_replicas: 0 } do
14
- mapping do
15
- indexes :title, type: 'string', analyzer: 'snowball'
16
- indexes :created_at, type: 'date'
17
- end
18
- end
19
- end
20
-
21
9
  context "ActiveRecord basic integration" do
22
10
  setup do
23
11
  ActiveRecord::Schema.define(:version => 1) do
@@ -27,6 +15,18 @@ module Elasticsearch
27
15
  end
28
16
  end
29
17
 
18
+ class ::Article < ActiveRecord::Base
19
+ include Elasticsearch::Model
20
+ include Elasticsearch::Model::Callbacks
21
+
22
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
23
+ mapping do
24
+ indexes :title, type: 'string', analyzer: 'snowball'
25
+ indexes :created_at, type: 'date'
26
+ end
27
+ end
28
+ end
29
+
30
30
  Article.delete_all
31
31
  Article.__elasticsearch__.create_index! force: true
32
32
 
@@ -1,24 +1,25 @@
1
1
  require 'test_helper'
2
+ require 'active_record'
2
3
 
3
4
  module Elasticsearch
4
5
  module Model
5
6
  class ActiveRecordCustomSerializationTest < Elasticsearch::Test::IntegrationTestCase
7
+ context "ActiveRecord model with custom JSON serialization" do
8
+ setup do
9
+ class ::ArticleWithCustomSerialization < ActiveRecord::Base
10
+ include Elasticsearch::Model
11
+ include Elasticsearch::Model::Callbacks
6
12
 
7
- class ::ArticleWithCustomSerialization < ActiveRecord::Base
8
- include Elasticsearch::Model
9
- include Elasticsearch::Model::Callbacks
10
-
11
- mapping do
12
- indexes :title
13
- end
13
+ mapping do
14
+ indexes :title
15
+ end
14
16
 
15
- def as_indexed_json(options={})
16
- as_json(options.merge root: false).slice('title')
17
- end
18
- end
17
+ def as_indexed_json(options={})
18
+ # as_json(options.merge root: false).slice('title')
19
+ { title: self.title }
20
+ end
21
+ end
19
22
 
20
- context "ActiveRecord model with custom JSON serialization" do
21
- setup do
22
23
  ActiveRecord::Schema.define(:version => 1) do
23
24
  create_table ArticleWithCustomSerialization.table_name do |t|
24
25
  t.string :title
@@ -1,22 +1,9 @@
1
1
  require 'test_helper'
2
+ require 'active_record'
2
3
 
3
4
  module Elasticsearch
4
5
  module Model
5
6
  class ActiveRecordImportIntegrationTest < Elasticsearch::Test::IntegrationTestCase
6
-
7
- class ::ImportArticle < ActiveRecord::Base
8
- include Elasticsearch::Model
9
-
10
- scope :popular, -> { where('views >= 50') }
11
-
12
- mapping do
13
- indexes :title, type: 'string'
14
- indexes :views, type: 'integer'
15
- indexes :numeric, type: 'integer'
16
- indexes :created_at, type: 'date'
17
- end
18
- end
19
-
20
7
  context "ActiveRecord importing" do
21
8
  setup do
22
9
  ActiveRecord::Schema.define(:version => 1) do
@@ -28,6 +15,19 @@ module Elasticsearch
28
15
  end
29
16
  end
30
17
 
18
+ class ::ImportArticle < ActiveRecord::Base
19
+ include Elasticsearch::Model
20
+
21
+ scope :popular, -> { where('views >= 50') }
22
+
23
+ mapping do
24
+ indexes :title, type: 'string'
25
+ indexes :views, type: 'integer'
26
+ indexes :numeric, type: 'integer'
27
+ indexes :created_at, type: 'date'
28
+ end
29
+ end
30
+
31
31
  ImportArticle.delete_all
32
32
  ImportArticle.__elasticsearch__.create_index! force: true
33
33
  ImportArticle.__elasticsearch__.client.cluster.health wait_for_status: 'yellow'
@@ -1,18 +1,9 @@
1
1
  require 'test_helper'
2
+ require 'active_record'
2
3
 
3
4
  module Elasticsearch
4
5
  module Model
5
6
  class ActiveRecordNamespacedModelIntegrationTest < Elasticsearch::Test::IntegrationTestCase
6
-
7
- module ::MyNamespace
8
- class Article < ActiveRecord::Base
9
- include Elasticsearch::Model
10
- include Elasticsearch::Model::Callbacks
11
-
12
- mapping { indexes :title }
13
- end
14
- end
15
-
16
7
  context "Namespaced ActiveRecord model integration" do
17
8
  setup do
18
9
  ActiveRecord::Schema.define(:version => 1) do
@@ -21,6 +12,15 @@ module Elasticsearch
21
12
  end
22
13
  end
23
14
 
15
+ module ::MyNamespace
16
+ class Article < ActiveRecord::Base
17
+ include Elasticsearch::Model
18
+ include Elasticsearch::Model::Callbacks
19
+
20
+ mapping { indexes :title }
21
+ end
22
+ end
23
+
24
24
  MyNamespace::Article.delete_all
25
25
  MyNamespace::Article.__elasticsearch__.create_index! force: true
26
26
 
@@ -1,26 +1,24 @@
1
1
  require 'test_helper'
2
+ require 'active_record'
2
3
 
3
4
  module Elasticsearch
4
5
  module Model
5
6
  class ActiveRecordPaginationTest < Elasticsearch::Test::IntegrationTestCase
7
+ context "ActiveRecord pagination" do
8
+ setup do
9
+ class ::ArticleForPagination < ActiveRecord::Base
10
+ include Elasticsearch::Model
6
11
 
7
- class ::ArticleForPagination < ActiveRecord::Base
8
- include Elasticsearch::Model
9
-
10
- scope :published, -> { where(published: true) }
12
+ scope :published, -> { where(published: true) }
11
13
 
12
- settings index: { number_of_shards: 1, number_of_replicas: 0 } do
13
- mapping do
14
- indexes :title, type: 'string', analyzer: 'snowball'
15
- indexes :created_at, type: 'date'
14
+ settings index: { number_of_shards: 1, number_of_replicas: 0 } do
15
+ mapping do
16
+ indexes :title, type: 'string', analyzer: 'snowball'
17
+ indexes :created_at, type: 'date'
18
+ end
19
+ end
16
20
  end
17
- end
18
- end
19
-
20
- Kaminari::Hooks.init
21
21
 
22
- context "ActiveRecord pagination" do
23
- setup do
24
22
  ActiveRecord::Schema.define(:version => 1) do
25
23
  create_table ::ArticleForPagination.table_name do |t|
26
24
  t.string :title
@@ -29,6 +27,8 @@ module Elasticsearch
29
27
  end
30
28
  end
31
29
 
30
+ Kaminari::Hooks.init
31
+
32
32
  ArticleForPagination.delete_all
33
33
  ArticleForPagination.__elasticsearch__.create_index! force: true
34
34
 
data/test/test_helper.rb CHANGED
@@ -10,15 +10,21 @@ at_exit { Elasticsearch::Test::IntegrationTestCase.__run_at_exit_hooks }
10
10
 
11
11
  puts '-'*80
12
12
 
13
- require 'test/unit'
13
+ if defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
14
+ require 'test-unit'
15
+ require 'mocha/test_unit'
16
+ else
17
+ require 'minitest/autorun'
18
+ require 'mocha/mini_test'
19
+ end
20
+
14
21
  require 'shoulda-context'
15
- require 'mocha/setup'
16
- require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || RUBY_1_8
22
+
23
+ require 'turn' unless ENV["TM_FILEPATH"] || ENV["NOTURN"] || defined?(RUBY_VERSION) && RUBY_VERSION > '2.2'
17
24
 
18
25
  require 'ansi'
19
26
  require 'oj'
20
27
 
21
- require 'active_record'
22
28
  require 'active_model'
23
29
 
24
30
  require 'kaminari'
@@ -40,7 +46,7 @@ module Elasticsearch
40
46
  def setup
41
47
  ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => ":memory:" )
42
48
  logger = ::Logger.new(STDERR)
43
- logger.formatter = lambda { |s, d, p, m| "#{m.ansi(:faint, :cyan)}\n" }
49
+ logger.formatter = lambda { |s, d, p, m| "\e[2;36m#{m}\e[0m\n" }
44
50
  ActiveRecord::Base.logger = logger unless ENV['QUIET']
45
51
 
46
52
  ActiveRecord::LogSubscriber.colorize_logging = false
@@ -104,6 +104,23 @@ class Elasticsearch::Model::AdapterActiveRecordTest < Test::Unit::TestCase
104
104
  DummyClassForActiveRecord.__find_in_batches(scope: :published) do; end
105
105
  end
106
106
 
107
+ should "preprocess the batch if option provided" do
108
+ class << DummyClassForActiveRecord
109
+ # Updates/transforms the batch while fetching it from the database
110
+ # (eg. with information from an external system)
111
+ #
112
+ def update_batch(batch)
113
+ batch.collect { |b| b.to_s + '!' }
114
+ end
115
+ end
116
+
117
+ DummyClassForActiveRecord.expects(:__find_in_batches).returns( [:a, :b] )
118
+
119
+ DummyClassForActiveRecord.__find_in_batches(preprocess: :update_batch) do |batch|
120
+ assert_same_elements ["a!", "b!"], batch
121
+ end
122
+ end
123
+
107
124
  context "when transforming models" do
108
125
  setup do
109
126
  @transform = DummyClassForActiveRecord.__transform
@@ -22,6 +22,7 @@ class Elasticsearch::Model::CallbacksTest < Test::Unit::TestCase
22
22
  ::DummyCallbacksModel.expects(:__send__).with do |method, parameter|
23
23
  assert_equal :include, method
24
24
  assert_equal DummyCallbacksAdapter::CallbacksMixin, parameter
25
+ true
25
26
  end
26
27
 
27
28
  Elasticsearch::Model::Callbacks.included(DummyCallbacksModel)
@@ -48,7 +48,7 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
48
48
  assert_equal 0, DummyImportingModel.import
49
49
  end
50
50
 
51
- should "return number of errors" do
51
+ should "return the number of errors" do
52
52
  Elasticsearch::Model::Adapter.expects(:from_class)
53
53
  .with(DummyImportingModel)
54
54
  .returns(DummyImportingAdapter)
@@ -66,6 +66,24 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
66
66
  assert_equal 1, DummyImportingModel.import
67
67
  end
68
68
 
69
+ should "return an array of error elements" do
70
+ Elasticsearch::Model::Adapter.expects(:from_class)
71
+ .with(DummyImportingModel)
72
+ .returns(DummyImportingAdapter)
73
+
74
+ DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing
75
+
76
+ client = mock('client')
77
+ client.expects(:bulk).returns({'items' => [ {'index' => {}}, {'index' => {'error' => 'FAILED'}} ]})
78
+
79
+ DummyImportingModel.stubs(:client).returns(client)
80
+ DummyImportingModel.stubs(:index_name).returns('foo')
81
+ DummyImportingModel.stubs(:document_type).returns('foo')
82
+ DummyImportingModel.stubs(:__batch_to_bulk)
83
+
84
+ assert_equal [{'index' => {'error' => 'FAILED'}}], DummyImportingModel.import(return: 'errors')
85
+ end
86
+
69
87
  should "yield the response" do
70
88
  Elasticsearch::Model::Adapter.expects(:from_class)
71
89
  .with(DummyImportingModel)
@@ -90,10 +108,12 @@ class Elasticsearch::Model::ImportingTest < Test::Unit::TestCase
90
108
  DummyImportingModel.expects(:__find_in_batches).with do |options|
91
109
  assert_equal 'bar', options[:foo]
92
110
  assert_nil options[:force]
111
+ true
93
112
  end
94
113
 
95
114
  DummyImportingModel.expects(:create_index!).with do |options|
96
115
  assert_equal true, options[:force]
116
+ true
97
117
  end
98
118
 
99
119
  DummyImportingModel.expects(:index_name).returns('foo')
@@ -164,6 +164,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
164
164
  instance.expects(:instance_variable_set).with do |name, value|
165
165
  assert_equal :@__changed_attributes, name
166
166
  assert_equal({foo: 'Two'}, value)
167
+ true
167
168
  end
168
169
 
169
170
  ::DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods
@@ -182,6 +183,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
182
183
  assert_equal 'bar', payload[:type]
183
184
  assert_equal '1', payload[:id]
184
185
  assert_equal 'JSON', payload[:body]
186
+ true
185
187
  end
186
188
 
187
189
  instance.expects(:client).returns(client)
@@ -199,6 +201,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
199
201
 
200
202
  client.expects(:index).with do |payload|
201
203
  assert_equal 'A', payload[:parent]
204
+ true
202
205
  end
203
206
 
204
207
  instance.expects(:client).returns(client)
@@ -218,6 +221,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
218
221
  assert_equal 'foo', payload[:index]
219
222
  assert_equal 'bar', payload[:type]
220
223
  assert_equal '1', payload[:id]
224
+ true
221
225
  end
222
226
 
223
227
  instance.expects(:client).returns(client)
@@ -234,6 +238,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
234
238
 
235
239
  client.expects(:delete).with do |payload|
236
240
  assert_equal 'A', payload[:parent]
241
+ true
237
242
  end
238
243
 
239
244
  instance.expects(:client).returns(client)
@@ -267,6 +272,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
267
272
  assert_equal 'bar', payload[:type]
268
273
  assert_equal '1', payload[:id]
269
274
  assert_equal({foo: 'bar'}, payload[:body][:doc])
275
+ true
270
276
  end
271
277
 
272
278
  instance.expects(:client).returns(client)
@@ -282,10 +288,32 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
282
288
  instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
283
289
 
284
290
  # Set the fake `changes` hash
285
- instance.instance_variable_set(:@__changed_attributes, {foo: 'B', bar: 'D' })
291
+ instance.instance_variable_set(:@__changed_attributes, {'foo' => 'B', 'bar' => 'D' })
286
292
 
287
293
  client.expects(:update).with do |payload|
288
- assert_equal({foo: 'B'}, payload[:body][:doc])
294
+ assert_equal({:foo => 'B'}, payload[:body][:doc])
295
+ true
296
+ end
297
+
298
+ instance.expects(:client).returns(client)
299
+ instance.expects(:index_name).returns('foo')
300
+ instance.expects(:document_type).returns('bar')
301
+ instance.expects(:id).returns('1')
302
+
303
+ instance.update_document
304
+ end
305
+
306
+ should "get attributes from as_indexed_json during partial update" do
307
+ client = mock('client')
308
+ instance = ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
309
+
310
+ instance.instance_variable_set(:@__changed_attributes, { 'foo' => { 'bar' => 'BAR'} })
311
+ # Overload as_indexed_json
312
+ instance.expects(:as_indexed_json).returns({ 'foo' => 'BAR' })
313
+
314
+ client.expects(:update).with do |payload|
315
+ assert_equal({'foo' => 'BAR'}, payload[:body][:doc])
316
+ true
289
317
  end
290
318
 
291
319
  instance.expects(:client).returns(client)
@@ -336,6 +364,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
336
364
  assert_equal 'dummy_indexing_model_for_recreates', payload[:index]
337
365
  assert_equal 1, payload[:body][:settings][:index][:number_of_shards]
338
366
  assert_equal 'keyword', payload[:body][:mappings][:dummy_indexing_model_for_recreate][:properties][:foo][:analyzer]
367
+ true
339
368
  end.returns({})
340
369
 
341
370
  DummyIndexingModelForRecreate.expects(:client).returns(client).at_least_once
@@ -414,10 +443,12 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
414
443
  should "create the custom index" do
415
444
  @indices.expects(:exists).with do |arguments|
416
445
  assert_equal 'custom-foo', arguments[:index]
446
+ true
417
447
  end
418
448
 
419
449
  @indices.expects(:create).with do |arguments|
420
450
  assert_equal 'custom-foo', arguments[:index]
451
+ true
421
452
  end
422
453
 
423
454
  DummyIndexingModelForRecreate.create_index! index: 'custom-foo'
@@ -426,6 +457,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
426
457
  should "delete the custom index" do
427
458
  @indices.expects(:delete).with do |arguments|
428
459
  assert_equal 'custom-foo', arguments[:index]
460
+ true
429
461
  end
430
462
 
431
463
  DummyIndexingModelForRecreate.delete_index! index: 'custom-foo'
@@ -434,6 +466,7 @@ class Elasticsearch::Model::IndexingTest < Test::Unit::TestCase
434
466
  should "refresh the custom index" do
435
467
  @indices.expects(:refresh).with do |arguments|
436
468
  assert_equal 'custom-foo', arguments[:index]
469
+ true
437
470
  end
438
471
 
439
472
  DummyIndexingModelForRecreate.refresh_index! index: 'custom-foo'
@@ -48,6 +48,7 @@ class Elasticsearch::Model::SearchTest < Test::Unit::TestCase
48
48
  instance.__elasticsearch__.expects(:instance_variable_set).with do |name, value|
49
49
  assert_equal :@__changed_attributes, name
50
50
  assert_equal({foo: 'Two'}, value)
51
+ true
51
52
  end
52
53
 
53
54
  ::DummyProxyModelWithCallbacks.__send__ :include, Elasticsearch::Model::Proxy
@@ -34,6 +34,7 @@ class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCas
34
34
  .with do |definition|
35
35
  assert_equal 25, definition[:from]
36
36
  assert_equal 25, definition[:size]
37
+ true
37
38
  end
38
39
  .returns(RESPONSE)
39
40
 
@@ -51,6 +52,7 @@ class Elasticsearch::Model::ResponsePaginationKaminariTest < Test::Unit::TestCas
51
52
  .with do |definition|
52
53
  assert_equal 75, definition[:from]
53
54
  assert_equal 25, definition[:size]
55
+ true
54
56
  end
55
57
  .returns(RESPONSE)
56
58
 
@@ -30,8 +30,10 @@ class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::Tes
30
30
  @expected_methods = [
31
31
  # methods needed by WillPaginate::CollectionMethods
32
32
  :current_page,
33
+ :offset,
33
34
  :per_page,
34
35
  :total_entries,
36
+ :length,
35
37
 
36
38
  # methods defined by WillPaginate::CollectionMethods
37
39
  :total_pages,
@@ -66,6 +68,19 @@ class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::Tes
66
68
  end
67
69
  end
68
70
 
71
+ context "#offset method" do
72
+ should "calculate offset using current_page and per_page" do
73
+ @response.per_page(3).page(3)
74
+ assert_equal 6, @response.offset
75
+ end
76
+ end
77
+ context "#length method" do
78
+ should "return count of paginated results" do
79
+ @response.per_page(3).page(3)
80
+ assert_equal 3, @response.length
81
+ end
82
+ end
83
+
69
84
  context "#paginate method" do
70
85
  should "set from/size using defaults" do
71
86
  @response.klass.client
@@ -73,6 +88,7 @@ class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::Tes
73
88
  .with do |definition|
74
89
  assert_equal 0, definition[:from]
75
90
  assert_equal 33, definition[:size]
91
+ true
76
92
  end
77
93
  .returns(RESPONSE)
78
94
 
@@ -90,6 +106,7 @@ class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::Tes
90
106
  .with do |definition|
91
107
  assert_equal 33, definition[:from]
92
108
  assert_equal 33, definition[:size]
109
+ true
93
110
  end
94
111
  .returns(RESPONSE)
95
112
 
@@ -107,6 +124,7 @@ class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::Tes
107
124
  .with do |definition|
108
125
  assert_equal 18, definition[:from]
109
126
  assert_equal 9, definition[:size]
127
+ true
110
128
  end
111
129
  .returns(RESPONSE)
112
130
 
@@ -118,12 +136,13 @@ class Elasticsearch::Model::ResponsePaginationWillPaginateTest < Test::Unit::Tes
118
136
  assert_equal 9, @response.search.definition[:size]
119
137
  end
120
138
 
121
- should "searches for page 1 if specified page is < 1" do
139
+ should "search for first page if specified page is < 1" do
122
140
  @response.klass.client
123
141
  .expects(:search)
124
142
  .with do |definition|
125
143
  assert_equal 0, definition[:from]
126
144
  assert_equal 33, definition[:size]
145
+ true
127
146
  end
128
147
  .returns(RESPONSE)
129
148
 
@@ -18,6 +18,7 @@ class Elasticsearch::Model::SearchRequestTest < Test::Unit::TestCase
18
18
  should "pass the search definition as a simple query" do
19
19
  @client.expects(:search).with do |params|
20
20
  assert_equal 'foo', params[:q]
21
+ true
21
22
  end
22
23
  .returns({})
23
24
 
@@ -28,6 +29,7 @@ class Elasticsearch::Model::SearchRequestTest < Test::Unit::TestCase
28
29
  should "pass the search definition as a Hash" do
29
30
  @client.expects(:search).with do |params|
30
31
  assert_equal( {foo: 'bar'}, params[:body] )
32
+ true
31
33
  end
32
34
  .returns({})
33
35
 
@@ -38,6 +40,7 @@ class Elasticsearch::Model::SearchRequestTest < Test::Unit::TestCase
38
40
  should "pass the search definition as a JSON string" do
39
41
  @client.expects(:search).with do |params|
40
42
  assert_equal( '{"foo":"bar"}', params[:body] )
43
+ true
41
44
  end
42
45
  .returns({})
43
46
 
@@ -52,6 +55,7 @@ class Elasticsearch::Model::SearchRequestTest < Test::Unit::TestCase
52
55
 
53
56
  @client.expects(:search).with do |params|
54
57
  assert_equal( {foo: 'bar'}, params[:body] )
58
+ true
55
59
  end
56
60
  .returns({})
57
61
 
@@ -63,6 +67,7 @@ class Elasticsearch::Model::SearchRequestTest < Test::Unit::TestCase
63
67
  @client.expects(:search).with do |params|
64
68
  assert_equal 'foo', params[:q]
65
69
  assert_equal 15, params[:size]
70
+ true
66
71
  end
67
72
  .returns({})
68
73
 
@@ -24,6 +24,7 @@ class Elasticsearch::Model::SearchingTest < Test::Unit::TestCase
24
24
  assert_equal DummySearchingModel, klass
25
25
  assert_equal 'foo', query
26
26
  assert_equal({default_operator: 'AND'}, options)
27
+ true
27
28
  end
28
29
  .returns( stub('search') )
29
30
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karel Minarik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-18 00:00:00.000000000 Z
11
+ date: 2014-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elasticsearch
@@ -122,20 +122,6 @@ dependencies:
122
122
  - - ! '>'
123
123
  - !ruby/object:Gem::Version
124
124
  version: '3.0'
125
- - !ruby/object:Gem::Dependency
126
- name: activerecord
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ! '>'
130
- - !ruby/object:Gem::Version
131
- version: '4.0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ! '>'
137
- - !ruby/object:Gem::Version
138
- version: '4.0'
139
125
  - !ruby/object:Gem::Dependency
140
126
  name: oj
141
127
  requirement: !ruby/object:Gem::Requirement
@@ -184,14 +170,14 @@ dependencies:
184
170
  requirements:
185
171
  - - ~>
186
172
  - !ruby/object:Gem::Version
187
- version: '4.0'
173
+ version: '4'
188
174
  type: :development
189
175
  prerelease: false
190
176
  version_requirements: !ruby/object:Gem::Requirement
191
177
  requirements:
192
178
  - - ~>
193
179
  - !ruby/object:Gem::Version
194
- version: '4.0'
180
+ version: '4'
195
181
  - !ruby/object:Gem::Dependency
196
182
  name: shoulda-context
197
183
  requirement: !ruby/object:Gem::Requirement
@@ -280,16 +266,16 @@ dependencies:
280
266
  name: ci_reporter
281
267
  requirement: !ruby/object:Gem::Requirement
282
268
  requirements:
283
- - - ! '>='
269
+ - - ~>
284
270
  - !ruby/object:Gem::Version
285
- version: '0'
271
+ version: '1.9'
286
272
  type: :development
287
273
  prerelease: false
288
274
  version_requirements: !ruby/object:Gem::Requirement
289
275
  requirements:
290
- - - ! '>='
276
+ - - ~>
291
277
  - !ruby/object:Gem::Version
292
- version: '0'
278
+ version: '1.9'
293
279
  - !ruby/object:Gem::Dependency
294
280
  name: simplecov
295
281
  requirement: !ruby/object:Gem::Requirement
@@ -332,20 +318,6 @@ dependencies:
332
318
  - - ! '>='
333
319
  - !ruby/object:Gem::Version
334
320
  version: '0'
335
- - !ruby/object:Gem::Dependency
336
- name: coveralls
337
- requirement: !ruby/object:Gem::Requirement
338
- requirements:
339
- - - ! '>='
340
- - !ruby/object:Gem::Version
341
- version: '0'
342
- type: :development
343
- prerelease: false
344
- version_requirements: !ruby/object:Gem::Requirement
345
- requirements:
346
- - - ! '>='
347
- - !ruby/object:Gem::Version
348
- version: '0'
349
321
  description: ActiveModel/Record integrations for Elasticsearch.
350
322
  email:
351
323
  - karel.minarik@elasticsearch.org