elasticsearch-model 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
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