tire 0.5.3 → 0.5.4

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