tire 0.5.3 → 0.5.4

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.
@@ -8,6 +8,7 @@ rvm:
8
8
  - 1.9.3
9
9
  - 1.8.7
10
10
  - ree
11
+ - jruby-19mode
11
12
 
12
13
  env:
13
14
  - TEST_COMMAND="rake test:unit"
@@ -15,8 +16,10 @@ env:
15
16
 
16
17
  script: "bundle exec $TEST_COMMAND"
17
18
 
18
- before_install:
19
- - sudo service elasticsearch start
19
+ services:
20
+ - elasticsearch
21
+ - redis
22
+ - mongodb
20
23
 
21
24
  matrix:
22
25
  exclude:
data/Gemfile CHANGED
@@ -2,3 +2,9 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in tire.gemspec
4
4
  gemspec
5
+
6
+ platform :jruby do
7
+ gem "jdbc-sqlite3"
8
+ gem "activerecord-jdbcsqlite3-adapter"
9
+ gem "json" if defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
10
+ end
@@ -29,6 +29,7 @@ require 'tire/search/highlight'
29
29
  require 'tire/search/scan'
30
30
  require 'tire/search/script_field'
31
31
  require 'tire/multi_search'
32
+ require 'tire/count'
32
33
  require 'tire/results/pagination'
33
34
  require 'tire/results/collection'
34
35
  require 'tire/results/item'
@@ -77,7 +77,7 @@ module Tire
77
77
  @attributes = { :indices => IndexCollection.new([]) }
78
78
 
79
79
  attributes.each_pair do |key, value|
80
- if key.to_s =~ /index|indices/
80
+ if ['index','indices'].include? key.to_s
81
81
  @attributes[:indices] = IndexCollection.new(value)
82
82
  else
83
83
  @attributes[key.to_sym] = value
@@ -0,0 +1,85 @@
1
+ module Tire
2
+ module Search
3
+ class CountRequestFailed < StandardError; end
4
+
5
+ class Count
6
+
7
+ attr_reader :indices, :types, :query, :response, :json
8
+
9
+ def initialize(indices=nil, options={}, &block)
10
+ @indices = Array(indices)
11
+ @types = Array(options.delete(:type)).map { |type| Utils.escape(type) }
12
+ @options = options
13
+
14
+ @path = ['/', @indices.join(','), @types.join(','), '_count'].compact.join('/').squeeze('/')
15
+
16
+ if block_given?
17
+ @query = Query.new
18
+ block.arity < 1 ? @query.instance_eval(&block) : block.call(@query)
19
+ end
20
+ end
21
+
22
+ def url
23
+ Configuration.url + @path
24
+ end
25
+
26
+ def params
27
+ options = @options.except(:wrapper)
28
+ options.empty? ? '' : '?' + options.to_param
29
+ end
30
+
31
+ def perform
32
+ @response = Configuration.client.get self.url + self.params, self.to_json
33
+ if @response.failure?
34
+ STDERR.puts "[REQUEST FAILED] #{self.to_curl}\n"
35
+ raise CountRequestFailed, @response.to_s
36
+ end
37
+ @json = MultiJson.decode(@response.body)
38
+ @value = @json['count']
39
+ return self
40
+ ensure
41
+ logged
42
+ end
43
+
44
+ def value
45
+ @value || (perform and return @value)
46
+ end
47
+
48
+ def to_json(options={})
49
+ @query.to_json if @query
50
+ end
51
+
52
+ def to_curl
53
+ if to_json
54
+ to_json_escaped = to_json.gsub("'",'\u0027')
55
+ %Q|curl -X GET '#{url}#{params.empty? ? '?' : params.to_s + '&'}pretty' -d '#{to_json_escaped}'|
56
+ else
57
+ %Q|curl -X GET '#{url}#{params.empty? ? '?' : params.to_s + '&'}pretty'|
58
+ end
59
+ end
60
+
61
+ def logged(endpoint='_count')
62
+ if Configuration.logger
63
+
64
+ Configuration.logger.log_request endpoint, indices, to_curl
65
+
66
+ code = @response.code rescue nil
67
+
68
+ if Configuration.logger.level.to_s == 'debug'
69
+ body = if @json
70
+ MultiJson.encode( @json, :pretty => Configuration.pretty)
71
+ else
72
+ MultiJson.encode( MultiJson.load(@response.body), :pretty => Configuration.pretty) rescue ''
73
+ end
74
+ else
75
+ body = ''
76
+ end
77
+
78
+ Configuration.logger.log_response code || 'N/A', 'N/A', body || 'N/A'
79
+ end
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+ end
@@ -94,6 +94,10 @@ module Tire
94
94
  alias :multisearch :multi_search
95
95
  alias :msearch :multi_search
96
96
 
97
+ def count(indices=nil, options={}, &block)
98
+ Search::Count.new(indices, options, &block).value
99
+ end
100
+
97
101
  def index(name, &block)
98
102
  Index.new(name, &block)
99
103
  end
@@ -77,7 +77,8 @@ module Tire
77
77
  params[:percolate] = "*" if params[:percolate] === true
78
78
  end
79
79
 
80
- params[:parent] = options[:parent] if options[:parent]
80
+ params[:parent] = options[:parent] if options[:parent]
81
+ params[:routing] = options[:routing] if options[:routing]
81
82
 
82
83
  params_encoded = params.empty? ? '' : "?#{params.to_param}"
83
84
 
@@ -15,6 +15,7 @@ module Tire
15
15
 
16
16
  def import options={}, &block
17
17
  options = { :method => 'paginate' }.update options
18
+ index = options[:index] ? Tire::Index.new(options.delete(:index)) : self.index
18
19
  index.import klass, options, &block
19
20
  end
20
21
 
@@ -36,15 +36,13 @@ module Tire
36
36
  extend ActiveModel::Callbacks
37
37
  define_model_callbacks :save, :destroy
38
38
 
39
- include Tire::Model::Search
40
- include Tire::Model::Callbacks
41
-
42
39
  extend Persistence::Finders::ClassMethods
43
40
  extend Persistence::Attributes::ClassMethods
44
41
  include Persistence::Attributes::InstanceMethods
45
-
46
42
  include Persistence::Storage
47
43
 
44
+ include Tire::Model::Search
45
+
48
46
  ['_score', '_type', '_index', '_version', 'sort', 'highlight', '_explanation'].each do |attr|
49
47
  define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
50
48
  define_method("#{attr}") { @attributes[attr] }
@@ -52,6 +52,11 @@ module Tire
52
52
  # Save property casting (when relevant):
53
53
  property_types[name.to_sym] = options[:class] if options[:class]
54
54
 
55
+ # Define default value for colletions:
56
+ if options[:class].is_a?(Array)
57
+ property_defaults[name.to_sym] ||= []
58
+ end
59
+
55
60
  # Store mapping for the property:
56
61
  mapping[name] = options
57
62
  self
@@ -6,29 +6,23 @@ module Tire
6
6
  # Provides infrastructure for storing records in _ElasticSearch_.
7
7
  #
8
8
  module Storage
9
-
10
9
  def self.included(base)
11
-
12
10
  base.class_eval do
13
11
  extend ClassMethods
14
- include InstanceMethods
12
+ include InstanceMethods
15
13
  end
16
-
17
14
  end
18
15
 
19
16
  module ClassMethods
20
-
21
17
  def create(args={})
22
18
  document = new(args)
23
19
  return false unless document.valid?
24
20
  document.save
25
21
  document
26
22
  end
27
-
28
23
  end
29
24
 
30
25
  module InstanceMethods
31
-
32
26
  def update_attribute(name, value)
33
27
  __update_attributes name => value
34
28
  save
@@ -39,11 +33,26 @@ module Tire
39
33
  save
40
34
  end
41
35
 
36
+ def update_index
37
+ send :_run_update_elasticsearch_index_callbacks do
38
+ if destroyed?
39
+ index.remove self
40
+ else
41
+ response = index.store( self, {:percolate => percolator} )
42
+ self.id ||= response['_id']
43
+ self._index = response['_index']
44
+ self._type = response['_type']
45
+ self._version = response['_version']
46
+ self.matches = response['matches']
47
+ self
48
+ end
49
+ end
50
+ end
51
+
42
52
  def save
43
53
  return false unless valid?
44
54
  run_callbacks :save do
45
- # Document#id is set in the +update_elasticsearch_index+ method,
46
- # where we have access to the JSON response
55
+ update_index
47
56
  end
48
57
  self
49
58
  end
@@ -51,6 +60,7 @@ module Tire
51
60
  def destroy
52
61
  run_callbacks :destroy do
53
62
  @destroyed = true
63
+ update_index
54
64
  end
55
65
  self.freeze
56
66
  end
@@ -58,9 +68,7 @@ module Tire
58
68
  def destroyed? ; !!@destroyed; end
59
69
  def persisted? ; !!id && !!_version; end
60
70
  def new_record? ; !persisted?; end
61
-
62
71
  end
63
-
64
72
  end
65
73
 
66
74
  end
@@ -145,11 +145,7 @@ module Tire
145
145
  if instance.destroyed?
146
146
  index.remove instance
147
147
  else
148
- response = index.store( instance, {:percolate => percolator} )
149
- instance.id ||= response['_id'] if instance.respond_to?(:id=)
150
- instance._index = response['_index'] if instance.respond_to?(:_index=)
151
- instance._type = response['_type'] if instance.respond_to?(:_type=)
152
- instance._version = response['_version'] if instance.respond_to?(:_version=)
148
+ response = index.store( instance, {:percolate => percolator} )
153
149
  instance.tire.matches = response['matches'] if instance.tire.respond_to?(:matches=)
154
150
  self
155
151
  end
@@ -57,6 +57,14 @@ module Tire
57
57
  end
58
58
  alias :[] :slice
59
59
 
60
+ def to_ary
61
+ self
62
+ end
63
+
64
+ def as_json(options=nil)
65
+ to_a.map { |item| item.as_json(options) }
66
+ end
67
+
60
68
  def error
61
69
  @response['error']
62
70
  end
@@ -69,10 +77,6 @@ module Tire
69
77
  ! success?
70
78
  end
71
79
 
72
- def to_ary
73
- self
74
- end
75
-
76
80
  # Handles _source prefixed fields properly: strips the prefix and converts fields to nested Hashes
77
81
  #
78
82
  def __parse_fields__(fields={})
@@ -65,6 +65,16 @@ module Tire
65
65
  end
66
66
  end
67
67
 
68
+ def as_json(options=nil)
69
+ hash = to_hash
70
+ hash.respond_to?(:with_indifferent_access) ? hash.with_indifferent_access.as_json(options) : hash.as_json(options)
71
+ end
72
+
73
+ def to_json(options=nil)
74
+ as_json.to_json(options)
75
+ end
76
+ alias_method :to_indexed_json, :to_json
77
+
68
78
  # Let's pretend we're someone else in Rails
69
79
  #
70
80
  def class
@@ -78,11 +88,6 @@ module Tire
78
88
  %Q|<Item#{self.class.to_s == 'Tire::Results::Item' ? '' : " (#{self.class})"} #{s.join(', ')}>|
79
89
  end
80
90
 
81
- def to_json(options=nil)
82
- @attributes.to_json(options)
83
- end
84
- alias_method :to_indexed_json, :to_json
85
-
86
91
  end
87
92
 
88
93
  end
@@ -146,7 +146,8 @@ module Tire
146
146
  end
147
147
 
148
148
  def to_curl
149
- %Q|curl -X GET '#{url}#{params.empty? ? '?' : params.to_s + '&'}pretty' -d '#{to_json}'|
149
+ to_json_escaped = to_json.gsub("'",'\u0027')
150
+ %Q|curl -X GET '#{url}#{params.empty? ? '?' : params.to_s + '&'}pretty' -d '#{to_json_escaped}'|
150
151
  end
151
152
 
152
153
  def to_hash
@@ -27,8 +27,9 @@ module Tire
27
27
  end
28
28
 
29
29
  def date(field, options={})
30
- interval = options.delete(:interval) || 'day'
31
- @value[:date_histogram] = { :field => field, :interval => interval }.update(options)
30
+ interval = { :interval => options.delete(:interval) || 'day' }
31
+ fields = options[:value_field] || options[:value_script] ? { :key_field => field } : { :field => field }
32
+ @value[:date_histogram] = {}.update(fields).update(interval).update(options)
32
33
  self
33
34
  end
34
35
 
@@ -1,11 +1,18 @@
1
1
  module Tire
2
- VERSION = "0.5.3"
2
+ VERSION = "0.5.4"
3
3
 
4
4
  CHANGELOG =<<-END
5
5
  IMPORTANT CHANGES LATELY:
6
6
 
7
- * Added `Tire::Results::Collection#each_with_hit`
8
- * Added support for passing partial document to `Index#update`
9
- * Added `nested` query support
7
+ * Added the support for the Count API
8
+ * Escape single quotes in `to_curl` serialization
9
+ * Added JRuby compatibility
10
+ * Added proper `as_json` support for `Results::Collection` and `Results::Item` classes
11
+ * Added extracting the `routing` information in the `Index#store` method
12
+ * Refactored the `update_index` method for search and persistence integration
13
+ * Cast collection properties in Model::Persistence as empty Array by default
14
+ * Allow passing `:index` option to `MyModel.import`
15
+ * Update to Mocha ~> 0.13
16
+ * Update to MultiJson ~> 1.3
10
17
  END
11
18
  end
@@ -5,7 +5,7 @@ module Tire
5
5
  class CountIntegrationTest < Test::Unit::TestCase
6
6
  include Test::Integration
7
7
 
8
- context "Count" do
8
+ context "Count with search type" do
9
9
 
10
10
  should "return total number of hits for the query, but no hits" do
11
11
  s = Tire.search 'articles-test', :search_type => 'count' do
@@ -30,5 +30,35 @@ module Tire
30
30
 
31
31
  end
32
32
 
33
+ context "Count with the count method" do
34
+ setup { Tire.index('articles-test-count') { delete; create and store(title: 'Test') and refresh } }
35
+ teardown { Tire.index('articles-test-count') { delete } }
36
+
37
+ should "return number of documents in the index" do
38
+ assert_equal 5, Tire.count('articles-test')
39
+ end
40
+
41
+ should "return number of documents in the index for specific query" do
42
+ # Tire.configure { logger STDERR, level: 'debug' }
43
+ count = Tire.count('articles-test') do
44
+ term :tags, 'ruby'
45
+ end
46
+ assert_equal 2, count
47
+ end
48
+
49
+ should "return number of documents in multiple indices" do
50
+ assert_equal 6, Tire.count(['articles-test', 'articles-test-count'])
51
+ end
52
+
53
+ should "allow access to the JSON and response" do
54
+ c = Tire::Search::Count.new('articles-test')
55
+ c.perform
56
+ assert_equal 5, c.value
57
+ assert_equal 0, c.json['_shards']['failed']
58
+ assert c.response.success?, "Response should be successful: #{c.response.inspect}"
59
+ end
60
+
61
+ end
62
+
33
63
  end
34
64
  end
@@ -114,7 +114,7 @@ module Tire
114
114
 
115
115
  context "date histogram" do
116
116
 
117
- should "return aggregated values for all results" do
117
+ should "return aggregated counts for each bucket" do
118
118
  s = Tire.search('articles-test') do
119
119
  query { all }
120
120
  facet 'published_on' do
@@ -124,7 +124,35 @@ module Tire
124
124
 
125
125
  facets = s.results.facets['published_on']['entries']
126
126
  assert_equal 4, facets.size, facets.inspect
127
- assert_equal 2, facets.entries[1]["count"], facets.inspect
127
+ assert_equal 2, facets.entries[1]['count'], facets.inspect
128
+ end
129
+
130
+ should "return value statistics for each bucket" do
131
+ s = Tire.search('articles-test', search_type: 'count') do
132
+ query { all }
133
+ facet 'published_on' do
134
+ date :published_on, value_field: 'words'
135
+ end
136
+ end
137
+
138
+ facets = s.results.facets['published_on']['entries']
139
+ assert_equal 4, facets.size, facets.inspect
140
+ assert_equal 2, facets.entries[1]['count'], facets.inspect
141
+ assert_equal 625.0, facets.entries[1]['total'], facets.inspect
142
+ end
143
+
144
+ should "return value statistics for each bucket by script" do
145
+ s = Tire.search('articles-test', search_type: 'count') do
146
+ query { all }
147
+ facet 'published_on' do
148
+ date :published_on, value_script: "doc.title.value.length()"
149
+ end
150
+ end
151
+
152
+ facets = s.results.facets['published_on']['entries']
153
+ assert_equal 4, facets.size, facets.inspect
154
+ assert_equal 2, facets.entries[1]['count'], facets.inspect
155
+ assert_equal 8.0, facets.entries[1]['total'], facets.inspect # Two + Three => 8 characters
128
156
  end
129
157
 
130
158
  end
@@ -45,6 +45,13 @@ module Tire
45
45
  end
46
46
  end
47
47
 
48
+ should "be serialized to JSON" do
49
+ s = Tire.search('articles-test') { query { string 'title:one' } }
50
+
51
+ assert_not_nil s.results.as_json(only: 'title').first['title']
52
+ assert_nil s.results.as_json(only: 'title').first['published_on']
53
+ end
54
+
48
55
  end
49
56
  end
50
57
 
@@ -6,17 +6,28 @@ require 'bundler/setup'
6
6
  require 'pathname'
7
7
  require 'test/unit'
8
8
 
9
+ JRUBY = defined?(JRUBY_VERSION)
10
+
9
11
  if ENV['JSON_LIBRARY']
10
12
  puts "Using '#{ENV['JSON_LIBRARY']}' JSON library"
11
13
  require ENV['JSON_LIBRARY']
14
+ elsif JRUBY
15
+ require 'json'
12
16
  else
13
17
  require 'yajl/json_gem'
14
18
  end
15
- require 'sqlite3'
19
+
20
+ if JRUBY
21
+ require 'jdbc/sqlite3'
22
+ require 'active_record'
23
+ require 'active_record/connection_adapters/jdbcsqlite3_adapter'
24
+ else
25
+ require 'sqlite3'
26
+ end
16
27
 
17
28
  require 'shoulda'
18
- require 'turn/autorun' unless ENV["TM_FILEPATH"] || defined?(RUBY_VERSION) && RUBY_VERSION < '1.9'
19
- require 'mocha'
29
+ require 'turn/autorun' unless ENV["TM_FILEPATH"] || JRUBY
30
+ require 'mocha/setup'
20
31
 
21
32
  require 'active_support/core_ext/hash/indifferent_access'
22
33
 
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ module Tire
4
+ class CountTest < Test::Unit::TestCase
5
+
6
+ context "Count" do
7
+ setup { Configuration.reset }
8
+
9
+ should "be initialized with single index" do
10
+ c = Search::Count.new('index')
11
+ assert_equal ['index'], c.indices
12
+ assert_match %r|/index/_count|, c.url
13
+ end
14
+
15
+ should "count all documents by the leaving index empty" do
16
+ c = Search::Count.new
17
+ assert c.indices.empty?, "#{c.indices.inspect} should be empty"
18
+ assert_match %r|localhost:9200/_count|, c.url
19
+ end
20
+
21
+ should "limit count with document type" do
22
+ c = Search::Count.new('index', :type => 'bar')
23
+ assert_equal ['bar'], c.types
24
+ assert_match %r|index/bar/_count|, c.url
25
+ end
26
+
27
+ should "pass URL parameters" do
28
+ c = Search::Count.new('index', :routing => 123)
29
+ assert ! c.params.empty?
30
+ assert_match %r|routing=123|, c.params
31
+ end
32
+
33
+ should "evaluate the query" do
34
+ Search::Query.any_instance.expects(:instance_eval)
35
+
36
+ c = Search::Count.new('index') { string 'foo' }
37
+ assert_not_nil c.query
38
+ end
39
+
40
+ should "allow access to the JSON and the response" do
41
+ Configuration.client.expects(:get).returns(mock_response( '{"count":1}', 200 ))
42
+ c = Search::Count.new('index')
43
+ c.perform
44
+ assert_equal 1, c.json['count']
45
+ assert_equal 200, c.response.code
46
+ end
47
+
48
+ should "return curl snippet for debugging" do
49
+ c = Search::Count.new('index') { term :title, 'foo' }
50
+ assert_match %r|curl \-X GET 'http://localhost:9200/index/_count\?pretty' -d |, c.to_curl
51
+ assert_match %r|"term"\s*:\s*"foo"|, c.to_curl
52
+ end
53
+
54
+ should "log the request when logger is set" do
55
+ Configuration.logger STDERR
56
+
57
+ Configuration.client.expects(:get).returns(mock_response( '{"count":1}', 200 ))
58
+ Configuration.logger.expects(:log_request).returns(true)
59
+ Configuration.logger.expects(:log_response).returns(true)
60
+
61
+ Search::Count.new('index').value
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+ end
@@ -1,5 +1,4 @@
1
1
  require 'test_helper'
2
- require 'tire/http/clients/curb'
3
2
 
4
3
  module Tire
5
4
  module HTTP
@@ -46,30 +45,34 @@ module Tire
46
45
 
47
46
  end
48
47
 
49
- context "Curb" do
50
- setup do
51
- Configuration.client Client::Curb
52
- end
48
+ if defined?(Curl)
49
+ require 'tire/http/clients/curb'
53
50
 
54
- teardown do
55
- Configuration.client Client::RestClient
56
- end
51
+ context "Curb" do
52
+ setup do
53
+ Configuration.client Client::Curb
54
+ end
57
55
 
58
- should "use POST method if request body passed" do
59
- ::Curl::Easy.any_instance.expects(:http_post)
56
+ teardown do
57
+ Configuration.client Client::RestClient
58
+ end
60
59
 
61
- response = Configuration.client.get "http://localhost:3000", '{ "query_string" : { "query" : "apple" }}'
62
- end
60
+ should "use POST method if request body passed" do
61
+ ::Curl::Easy.any_instance.expects(:http_post)
63
62
 
64
- should "use GET method if request body is nil" do
65
- ::Curl::Easy.any_instance.expects(:http_get)
63
+ response = Configuration.client.get "http://localhost:3000", '{ "query_string" : { "query" : "apple" }}'
64
+ end
65
+
66
+ should "use GET method if request body is nil" do
67
+ ::Curl::Easy.any_instance.expects(:http_get)
68
+
69
+ response = Configuration.client.get "http://localhost:9200/articles/article/1"
70
+ end
66
71
 
67
- response = Configuration.client.get "http://localhost:9200/articles/article/1"
68
72
  end
69
73
 
70
74
  end
71
75
 
72
-
73
76
  end
74
77
  end
75
78
 
@@ -94,7 +94,7 @@ module Tire
94
94
  a['add']['alias'] == 'alias_martha'
95
95
  end
96
96
  end.returns(mock_response('{}'), 200)
97
-
97
+
98
98
  a = Alias.find('alias_martha')
99
99
  a.indices.push 'index_A'
100
100
  a.save
@@ -109,7 +109,7 @@ module Tire
109
109
  a['remove']['alias'] == 'alias_martha'
110
110
  end
111
111
  end.returns(mock_response('{}'), 200)
112
-
112
+
113
113
  a = Alias.find('alias_martha')
114
114
  a.indices.delete 'index_A'
115
115
  a.save
@@ -120,7 +120,7 @@ module Tire
120
120
  # puts json
121
121
  MultiJson.decode(json)['actions'].all? { |a| a['add']['routing'] == 'martha' }
122
122
  end.returns(mock_response('{}'), 200)
123
-
123
+
124
124
  a = Alias.find('alias_martha')
125
125
  a.routing('martha')
126
126
  a.save
@@ -271,5 +271,65 @@ module Tire
271
271
  end
272
272
 
273
273
  end
274
+
275
+ context "aliases with index and search routing values" do
276
+ setup do
277
+ json =<<-JSON
278
+ {
279
+ "index_A": {
280
+ "aliases": {}
281
+ },
282
+ "index_B": {
283
+ "aliases": {
284
+ "alias_john": {
285
+ "filter": {
286
+ "term": {
287
+ "user": "john"
288
+ }
289
+ }
290
+ },
291
+ "alias_martha": {
292
+ "filter": {
293
+ "term": {
294
+ "user": "martha"
295
+ }
296
+ }
297
+ }
298
+ }
299
+ },
300
+ "index_C": {
301
+ "aliases": {
302
+ "alias_martha": {
303
+ "filter": {
304
+ "term": {
305
+ "user": "martha"
306
+ }
307
+ },
308
+ "index_routing": "1",
309
+ "search_routing": "2"
310
+ }
311
+ }
312
+ }
313
+ }
314
+ JSON
315
+ Configuration.client.expects(:get).
316
+ returns( mock_response(json), 200).
317
+ at_least_once
318
+ end
319
+
320
+ should "find all aliases" do
321
+ aliases = Alias.all
322
+ # p aliases
323
+ assert_equal 2, aliases.size
324
+ assert_equal ['index_B', 'index_C'], aliases.select { |a| a.name == 'alias_martha'}.first.indices.to_a.sort
325
+ end
326
+
327
+ should "find an alias" do
328
+ a = Alias.find('alias_martha')
329
+ assert_instance_of Alias, a
330
+ assert_equal ['index_B', 'index_C'], a.indices.to_a.sort
331
+ end
332
+ end
333
+
274
334
  end
275
335
  end
@@ -288,6 +288,14 @@ module Tire
288
288
  @index.store '{"foo" : "bar"}'
289
289
  end
290
290
 
291
+ should "extract the routing information from options" do
292
+ Configuration.client.expects(:post).with do |url, payload|
293
+ assert_match /routing=abc/, url
294
+ end.returns(mock_response('{"ok":true,"_id":"123"}'))
295
+
296
+ @index.store( {:id => 123, :title => 'Test'}, {:routing => 'abc'} )
297
+ end
298
+
291
299
  context "document with ID" do
292
300
 
293
301
  should "store Hash it under its ID property" do
@@ -60,7 +60,11 @@ module Tire
60
60
  # Add 1 to every "document" and return them
61
61
  documents.map { |d| d + 1 }
62
62
  end
63
+ end
63
64
 
65
+ should "store the documents in a different index" do
66
+ Tire::Index.expects(:new).with('new_index').returns( mock('index') { expects(:import) } )
67
+ ImportModel.import :index => 'new_index'
64
68
  end
65
69
 
66
70
  end
@@ -319,6 +319,12 @@ module Tire
319
319
  assert_equal 'A', article.stats.meta.tags
320
320
  end
321
321
 
322
+ should "create empty collection for missing value" do
323
+ article = PersistentArticleWithCastedCollection.new :title => 'Test'
324
+ assert_respond_to article.comments, :each
325
+ assert article.comments.empty?, "article.comments should be empty: " + article.inspect
326
+ end
327
+
322
328
  end
323
329
 
324
330
  context "when initializing" do
@@ -8,7 +8,7 @@ module Tire
8
8
  setup do
9
9
  begin; Object.send(:remove_const, :Rails); rescue; end
10
10
  Configuration.reset
11
- @default_response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 1, '_source' => {:title => 'Test'}},
11
+ @default_response = { 'hits' => { 'hits' => [{'_id' => 1, '_score' => 1, '_source' => {:title => 'Test', :author => 'John'}},
12
12
  {'_id' => 2},
13
13
  {'_id' => 3}],
14
14
  'max_score' => 1.0 } }
@@ -38,7 +38,7 @@ module Tire
38
38
  assert_equal [3], Results::Collection.new(@default_response)[-1,1].map {|res| res[:id]}
39
39
  end
40
40
 
41
- should "be initialized with parsed json" do
41
+ should "be initialized with parsed JSON" do
42
42
  assert_nothing_raised do
43
43
  collection = Results::Collection.new( @default_response )
44
44
  assert_equal 3, collection.results.count
@@ -79,6 +79,23 @@ module Tire
79
79
  assert_equal 1.0, collection.max_score
80
80
  end
81
81
 
82
+ context "serialization" do
83
+
84
+ should "be serialized to JSON" do
85
+ collection = Results::Collection.new(@default_response)
86
+ assert_instance_of Array, collection.as_json
87
+ assert_equal 'Test', collection.as_json.first['title']
88
+ assert_equal 'John', collection.as_json.first['author']
89
+ end
90
+
91
+ should "pass options to as_json" do
92
+ collection = Results::Collection.new(@default_response)
93
+ assert_equal 'Test', collection.as_json(:only => 'title').first['title']
94
+ assert_nil collection.as_json(:only => 'title').first['author']
95
+ end
96
+
97
+ end
98
+
82
99
  context "with error response" do
83
100
  setup do
84
101
  @collection = Results::Collection.new({'error' => 'SearchPhaseExecutionException...'})
@@ -120,6 +120,12 @@ module Tire
120
120
  assert_equal '1925', @document.to_hash[:awards][:best_fiction][:year]
121
121
  end
122
122
 
123
+ should "be convertible to JSON" do
124
+ assert_instance_of Hash, @document.as_json
125
+ assert_equal 'Test', @document.as_json(:only => 'title')['title']
126
+ assert_nil @document.as_json(:only => 'title')['author']
127
+ end
128
+
123
129
  should "be inspectable" do
124
130
  assert_match /<Item .* title|Item .* author/, @document.inspect
125
131
  end
@@ -115,7 +115,7 @@ module Tire::Search
115
115
 
116
116
  should "encode custom options" do
117
117
  f = Facet.new('date') { date :published_on, :value_field => 'price' }
118
- assert_equal( {:date=>{:date_histogram=>{:field=>'published_on',:interval=>'day',:value_field=>'price' } } }.to_json,
118
+ assert_equal( {:date=>{:date_histogram=>{:key_field=>'published_on',:interval=>'day',:value_field=>'price' } } }.to_json,
119
119
  f.to_json )
120
120
  end
121
121
 
@@ -62,6 +62,10 @@ module Tire
62
62
  assert_respond_to Tire, :msearch
63
63
  end
64
64
 
65
+ should "count documents" do
66
+ assert_respond_to Tire, :count
67
+ end
68
+
65
69
  context "when retrieving results" do
66
70
 
67
71
  should "not call the #perform method immediately" do
@@ -28,25 +28,29 @@ Gem::Specification.new do |s|
28
28
  #
29
29
  s.add_dependency "rake"
30
30
  s.add_dependency "rest-client", "~> 1.6"
31
- s.add_dependency "multi_json", "~> 1.0"
31
+ s.add_dependency "multi_json", "~> 1.3"
32
32
  s.add_dependency "activemodel", ">= 3.0"
33
33
  s.add_dependency "hashr", "~> 0.0.19"
34
34
 
35
35
  # = Development dependencies
36
36
  #
37
- s.add_development_dependency "bundler", "~> 1.0"
38
- s.add_development_dependency "yajl-ruby", "~> 1.0"
37
+ s.add_development_dependency "bundler", "~> 1.0"
39
38
  s.add_development_dependency "shoulda"
40
- s.add_development_dependency "mocha"
39
+ s.add_development_dependency "mocha", "~> 0.13"
41
40
  s.add_development_dependency "minitest", "~> 2.12"
42
41
  s.add_development_dependency "activerecord", ">= 3.0"
43
- s.add_development_dependency "sqlite3"
44
42
  s.add_development_dependency "mongoid", "~> 2.2"
45
- s.add_development_dependency "bson_ext"
46
43
  s.add_development_dependency "redis-persistence"
47
- s.add_development_dependency "curb"
48
- s.add_development_dependency "oj"
49
- s.add_development_dependency "turn", "~> 0.9" if defined?(RUBY_VERSION) && RUBY_VERSION > '1.9'
44
+ s.add_development_dependency "faraday"
45
+
46
+ unless defined?(JRUBY_VERSION)
47
+ s.add_development_dependency "yajl-ruby", "~> 1.0"
48
+ s.add_development_dependency "sqlite3"
49
+ s.add_development_dependency "bson_ext"
50
+ s.add_development_dependency "curb"
51
+ s.add_development_dependency "oj"
52
+ s.add_development_dependency "turn", "~> 0.9"
53
+ end
50
54
 
51
55
  s.description = <<-DESC
52
56
  Tire is a Ruby client for the ElasticSearch search engine/database.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tire
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-03 00:00:00.000000000 Z
12
+ date: 2013-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70315094856180 !ruby/object:Gem::Requirement
16
+ requirement: &70237728923180 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70315094856180
24
+ version_requirements: *70237728923180
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rest-client
27
- requirement: &70315094855080 !ruby/object:Gem::Requirement
27
+ requirement: &70237728922400 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,21 +32,21 @@ dependencies:
32
32
  version: '1.6'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70315094855080
35
+ version_requirements: *70237728922400
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: multi_json
38
- requirement: &70315094853880 !ruby/object:Gem::Requirement
38
+ requirement: &70237728921620 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
42
42
  - !ruby/object:Gem::Version
43
- version: '1.0'
43
+ version: '1.3'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70315094853880
46
+ version_requirements: *70237728921620
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: activemodel
49
- requirement: &70315094851380 !ruby/object:Gem::Requirement
49
+ requirement: &70237728920960 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '3.0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70315094851380
57
+ version_requirements: *70237728920960
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: hashr
60
- requirement: &70315094850020 !ruby/object:Gem::Requirement
60
+ requirement: &70237728920260 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,21 +65,10 @@ dependencies:
65
65
  version: 0.0.19
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70315094850020
68
+ version_requirements: *70237728920260
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: bundler
71
- requirement: &70315094863680 !ruby/object:Gem::Requirement
72
- none: false
73
- requirements:
74
- - - ~>
75
- - !ruby/object:Gem::Version
76
- version: '1.0'
77
- type: :development
78
- prerelease: false
79
- version_requirements: *70315094863680
80
- - !ruby/object:Gem::Dependency
81
- name: yajl-ruby
82
- requirement: &70315094860660 !ruby/object:Gem::Requirement
71
+ requirement: &70237728919580 !ruby/object:Gem::Requirement
83
72
  none: false
84
73
  requirements:
85
74
  - - ~>
@@ -87,10 +76,10 @@ dependencies:
87
76
  version: '1.0'
88
77
  type: :development
89
78
  prerelease: false
90
- version_requirements: *70315094860660
79
+ version_requirements: *70237728919580
91
80
  - !ruby/object:Gem::Dependency
92
81
  name: shoulda
93
- requirement: &70315094860020 !ruby/object:Gem::Requirement
82
+ requirement: &70237728919000 !ruby/object:Gem::Requirement
94
83
  none: false
95
84
  requirements:
96
85
  - - ! '>='
@@ -98,21 +87,21 @@ dependencies:
98
87
  version: '0'
99
88
  type: :development
100
89
  prerelease: false
101
- version_requirements: *70315094860020
90
+ version_requirements: *70237728919000
102
91
  - !ruby/object:Gem::Dependency
103
92
  name: mocha
104
- requirement: &70315094859360 !ruby/object:Gem::Requirement
93
+ requirement: &70237728944780 !ruby/object:Gem::Requirement
105
94
  none: false
106
95
  requirements:
107
- - - ! '>='
96
+ - - ~>
108
97
  - !ruby/object:Gem::Version
109
- version: '0'
98
+ version: '0.13'
110
99
  type: :development
111
100
  prerelease: false
112
- version_requirements: *70315094859360
101
+ version_requirements: *70237728944780
113
102
  - !ruby/object:Gem::Dependency
114
103
  name: minitest
115
- requirement: &70315094858240 !ruby/object:Gem::Requirement
104
+ requirement: &70237728944020 !ruby/object:Gem::Requirement
116
105
  none: false
117
106
  requirements:
118
107
  - - ~>
@@ -120,10 +109,10 @@ dependencies:
120
109
  version: '2.12'
121
110
  type: :development
122
111
  prerelease: false
123
- version_requirements: *70315094858240
112
+ version_requirements: *70237728944020
124
113
  - !ruby/object:Gem::Dependency
125
114
  name: activerecord
126
- requirement: &70315094857060 !ruby/object:Gem::Requirement
115
+ requirement: &70237728943340 !ruby/object:Gem::Requirement
127
116
  none: false
128
117
  requirements:
129
118
  - - ! '>='
@@ -131,10 +120,21 @@ dependencies:
131
120
  version: '3.0'
132
121
  type: :development
133
122
  prerelease: false
134
- version_requirements: *70315094857060
123
+ version_requirements: *70237728943340
135
124
  - !ruby/object:Gem::Dependency
136
- name: sqlite3
137
- requirement: &70315095012780 !ruby/object:Gem::Requirement
125
+ name: mongoid
126
+ requirement: &70237728942600 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: '2.2'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *70237728942600
135
+ - !ruby/object:Gem::Dependency
136
+ name: redis-persistence
137
+ requirement: &70237728942140 !ruby/object:Gem::Requirement
138
138
  none: false
139
139
  requirements:
140
140
  - - ! '>='
@@ -142,21 +142,32 @@ dependencies:
142
142
  version: '0'
143
143
  type: :development
144
144
  prerelease: false
145
- version_requirements: *70315095012780
145
+ version_requirements: *70237728942140
146
146
  - !ruby/object:Gem::Dependency
147
- name: mongoid
148
- requirement: &70315095007180 !ruby/object:Gem::Requirement
147
+ name: faraday
148
+ requirement: &70237728941680 !ruby/object:Gem::Requirement
149
+ none: false
150
+ requirements:
151
+ - - ! '>='
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: *70237728941680
157
+ - !ruby/object:Gem::Dependency
158
+ name: yajl-ruby
159
+ requirement: &70237728941160 !ruby/object:Gem::Requirement
149
160
  none: false
150
161
  requirements:
151
162
  - - ~>
152
163
  - !ruby/object:Gem::Version
153
- version: '2.2'
164
+ version: '1.0'
154
165
  type: :development
155
166
  prerelease: false
156
- version_requirements: *70315095007180
167
+ version_requirements: *70237728941160
157
168
  - !ruby/object:Gem::Dependency
158
- name: bson_ext
159
- requirement: &70315095095540 !ruby/object:Gem::Requirement
169
+ name: sqlite3
170
+ requirement: &70237728940740 !ruby/object:Gem::Requirement
160
171
  none: false
161
172
  requirements:
162
173
  - - ! '>='
@@ -164,10 +175,10 @@ dependencies:
164
175
  version: '0'
165
176
  type: :development
166
177
  prerelease: false
167
- version_requirements: *70315095095540
178
+ version_requirements: *70237728940740
168
179
  - !ruby/object:Gem::Dependency
169
- name: redis-persistence
170
- requirement: &70315095094620 !ruby/object:Gem::Requirement
180
+ name: bson_ext
181
+ requirement: &70237728940240 !ruby/object:Gem::Requirement
171
182
  none: false
172
183
  requirements:
173
184
  - - ! '>='
@@ -175,10 +186,10 @@ dependencies:
175
186
  version: '0'
176
187
  type: :development
177
188
  prerelease: false
178
- version_requirements: *70315095094620
189
+ version_requirements: *70237728940240
179
190
  - !ruby/object:Gem::Dependency
180
191
  name: curb
181
- requirement: &70315095088820 !ruby/object:Gem::Requirement
192
+ requirement: &70237728939820 !ruby/object:Gem::Requirement
182
193
  none: false
183
194
  requirements:
184
195
  - - ! '>='
@@ -186,10 +197,10 @@ dependencies:
186
197
  version: '0'
187
198
  type: :development
188
199
  prerelease: false
189
- version_requirements: *70315095088820
200
+ version_requirements: *70237728939820
190
201
  - !ruby/object:Gem::Dependency
191
202
  name: oj
192
- requirement: &70315095113580 !ruby/object:Gem::Requirement
203
+ requirement: &70237728939400 !ruby/object:Gem::Requirement
193
204
  none: false
194
205
  requirements:
195
206
  - - ! '>='
@@ -197,10 +208,10 @@ dependencies:
197
208
  version: '0'
198
209
  type: :development
199
210
  prerelease: false
200
- version_requirements: *70315095113580
211
+ version_requirements: *70237728939400
201
212
  - !ruby/object:Gem::Dependency
202
213
  name: turn
203
- requirement: &70315095112560 !ruby/object:Gem::Requirement
214
+ requirement: &70237728938900 !ruby/object:Gem::Requirement
204
215
  none: false
205
216
  requirements:
206
217
  - - ~>
@@ -208,7 +219,7 @@ dependencies:
208
219
  version: '0.9'
209
220
  type: :development
210
221
  prerelease: false
211
- version_requirements: *70315095112560
222
+ version_requirements: *70237728938900
212
223
  description: ! " Tire is a Ruby client for the ElasticSearch search engine/database.\n\n
213
224
  \ It provides Ruby-like API for fluent communication with the ElasticSearch server\n
214
225
  \ and blends with ActiveModel class for convenient usage in Rails applications.\n\n
@@ -236,6 +247,7 @@ files:
236
247
  - lib/tire.rb
237
248
  - lib/tire/alias.rb
238
249
  - lib/tire/configuration.rb
250
+ - lib/tire/count.rb
239
251
  - lib/tire/dsl.rb
240
252
  - lib/tire/http/client.rb
241
253
  - lib/tire/http/clients/curb.rb
@@ -332,6 +344,7 @@ files:
332
344
  - test/test_helper.rb
333
345
  - test/unit/active_model_lint_test.rb
334
346
  - test/unit/configuration_test.rb
347
+ - test/unit/count_test.rb
335
348
  - test/unit/http_client_test.rb
336
349
  - test/unit/http_response_test.rb
337
350
  - test/unit/index_alias_test.rb
@@ -360,9 +373,14 @@ homepage: http://github.com/karmi/tire
360
373
  licenses: []
361
374
  post_install_message: ! "================================================================================\n\n
362
375
  \ Please check the documentation at <http://karmi.github.com/tire/>.\n\n--------------------------------------------------------------------------------\n\n
363
- \ IMPORTANT CHANGES LATELY:\n\n * Added `Tire::Results::Collection#each_with_hit`\n
364
- \ * Added support for passing partial document to `Index#update`\n * Added `nested`
365
- query support\n\n See the full changelog at <http://github.com/karmi/tire/commits/v0.5.3>.\n\n--------------------------------------------------------------------------------\n"
376
+ \ IMPORTANT CHANGES LATELY:\n\n * Added the support for the Count API\n * Escape
377
+ single quotes in `to_curl` serialization\n * Added JRuby compatibility\n * Added
378
+ proper `as_json` support for `Results::Collection` and `Results::Item` classes\n
379
+ \ * Added extracting the `routing` information in the `Index#store` method\n *
380
+ Refactored the `update_index` method for search and persistence integration\n *
381
+ Cast collection properties in Model::Persistence as empty Array by default\n *
382
+ Allow passing `:index` option to `MyModel.import`\n * Update to Mocha ~> 0.13\n
383
+ \ * Update to MultiJson ~> 1.3\n\n See the full changelog at <http://github.com/karmi/tire/commits/v0.5.4>.\n\n--------------------------------------------------------------------------------\n"
366
384
  rdoc_options:
367
385
  - --charset=UTF-8
368
386
  require_paths:
@@ -445,6 +463,7 @@ test_files:
445
463
  - test/test_helper.rb
446
464
  - test/unit/active_model_lint_test.rb
447
465
  - test/unit/configuration_test.rb
466
+ - test/unit/count_test.rb
448
467
  - test/unit/http_client_test.rb
449
468
  - test/unit/http_response_test.rb
450
469
  - test/unit/index_alias_test.rb