tire 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -9,4 +9,5 @@ coverage/
9
9
  scratch/
10
10
  examples/*.html
11
11
  *.log
12
- .rvmrc
12
+ .rvmrc
13
+ tags
data/README.markdown CHANGED
@@ -356,7 +356,7 @@ When you now save a record:
356
356
  :published_on => Time.now
357
357
  ```
358
358
 
359
- it is automatically added into the index, because of the included callbacks.
359
+ it is automatically added into an index called 'articles', because of the included callbacks.
360
360
  (You may want to skip them in special cases, like when your records are indexed via some external
361
361
  mechanism, let's say a _CouchDB_ or _RabbitMQ_
362
362
  [river](http://www.elasticsearch.org/blog/2010/09/28/the_river.html).
@@ -687,6 +687,8 @@ Of course, not all validations or `ActionPack` helpers will be available to your
687
687
  but if you can live with that, you've just got a schema-free, highly-scalable storage
688
688
  and retrieval engine for your data.
689
689
 
690
+ This will result in Article instances being stored in an index called 'test_articles' when used in tests but in the index 'development_articles' when used in the development environment.
691
+
690
692
  Please be sure to peruse the [integration test suite](https://github.com/karmi/tire/tree/master/test/integration)
691
693
  for examples of the API and _ActiveModel_ integration usage.
692
694
 
data/lib/tire.rb CHANGED
@@ -6,7 +6,8 @@ require 'tire/rubyext/hash'
6
6
  require 'tire/rubyext/symbol'
7
7
  require 'tire/logger'
8
8
  require 'tire/configuration'
9
- require 'tire/client'
9
+ require 'tire/http/response'
10
+ require 'tire/http/client'
10
11
  require 'tire/search'
11
12
  require 'tire/search/query'
12
13
  require 'tire/search/sort'
@@ -7,7 +7,7 @@ module Tire
7
7
  end
8
8
 
9
9
  def self.client(klass=nil)
10
- @client = klass || @client || Client::RestClient
10
+ @client = klass || @client || HTTP::Client::RestClient
11
11
  end
12
12
 
13
13
  def self.wrapper(klass=nil)
@@ -0,0 +1,51 @@
1
+ module Tire
2
+
3
+ module HTTP
4
+
5
+ module Client
6
+
7
+ class RestClient
8
+
9
+ def self.get(url, data=nil)
10
+ perform ::RestClient::Request.new(:method => :get, :url => url, :payload => data).execute
11
+ rescue Exception => e
12
+ Response.new e.http_body, e.http_code
13
+ end
14
+
15
+ def self.post(url, data)
16
+ perform ::RestClient.post(url, data)
17
+ rescue Exception => e
18
+ Response.new e.http_body, e.http_code
19
+ end
20
+
21
+ def self.put(url, data)
22
+ perform ::RestClient.put(url, data)
23
+ rescue Exception => e
24
+ Response.new e.http_body, e.http_code
25
+ end
26
+
27
+ def self.delete(url)
28
+ perform ::RestClient.delete(url)
29
+ rescue Exception => e
30
+ Response.new e.http_body, e.http_code
31
+ end
32
+
33
+ def self.head(url)
34
+ perform ::RestClient.head(url)
35
+ rescue Exception => e
36
+ Response.new e.http_body, e.http_code
37
+ end
38
+
39
+ private
40
+
41
+ def self.perform(response)
42
+ Response.new response.body, response.code, response.headers
43
+ end
44
+
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,55 @@
1
+ require 'curb'
2
+
3
+ module Tire
4
+
5
+ module HTTP
6
+
7
+ module Client
8
+
9
+ class Curb
10
+ @client = ::Curl::Easy.new
11
+ # @client.verbose = true
12
+
13
+ def self.get(url, data=nil)
14
+ @client.url = url
15
+ @client.post_body = data
16
+ # FIXME: Curb cannot post bodies with GET requests?
17
+ # Roy Fielding seems to approve:
18
+ # <http://tech.groups.yahoo.com/group/rest-discuss/message/9962>
19
+ @client.http_post
20
+ Response.new @client.body_str, @client.response_code
21
+ end
22
+
23
+ def self.post(url, data)
24
+ @client.url = url
25
+ @client.post_body = data
26
+ @client.http_post
27
+ Response.new @client.body_str, @client.response_code
28
+ end
29
+
30
+ def self.put(url, data)
31
+ @client.url = url
32
+ @client.put_data = data
33
+ @client.http_put
34
+ Response.new @client.body_str, @client.response_code
35
+ end
36
+
37
+ def self.delete(url)
38
+ @client.url = url
39
+ @client.http_delete
40
+ Response.new @client.body_str, @client.response_code
41
+ end
42
+
43
+ def self.head(url)
44
+ @client.url = url
45
+ @client.http_head
46
+ Response.new @client.body_str, @client.response_code
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,23 @@
1
+ module Tire
2
+
3
+ module HTTP
4
+
5
+ class Response
6
+ attr_reader :body, :code, :headers
7
+
8
+ def initialize(body, code, headers={})
9
+ @body, @code, @headers = body, code.to_i, headers
10
+ end
11
+
12
+ def success?
13
+ code > 0 && code < 400
14
+ end
15
+
16
+ def failure?
17
+ ! success?
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
data/lib/tire/index.rb CHANGED
@@ -9,30 +9,24 @@ module Tire
9
9
  end
10
10
 
11
11
  def exists?
12
- !!Configuration.client.head("#{Configuration.url}/#{@name}")
13
- rescue Exception => error
14
- false
12
+ Configuration.client.head("#{Configuration.url}/#{@name}").success?
15
13
  end
16
14
 
17
15
  def delete
18
- # FIXME: RestClient does not return response for DELETE requests?
19
16
  @response = Configuration.client.delete "#{Configuration.url}/#{@name}"
20
- return @response.body =~ /error/ ? false : true
21
- rescue Exception => error
22
- false
17
+ return @response.success?
23
18
  ensure
24
19
  curl = %Q|curl -X DELETE "#{Configuration.url}/#{@name}"|
25
- logged(error, 'DELETE', curl)
20
+ logged(@response.body, 'DELETE', curl)
26
21
  end
27
22
 
28
23
  def create(options={})
29
24
  @options = options
30
25
  @response = Configuration.client.post "#{Configuration.url}/#{@name}", MultiJson.encode(options)
31
- rescue Exception => error
32
- false
26
+ @response.success? ? @response : false
33
27
  ensure
34
28
  curl = %Q|curl -X POST "#{Configuration.url}/#{@name}" -d '#{MultiJson.encode(options)}'|
35
- logged(error, 'CREATE', curl)
29
+ logged(@response.body, 'CREATE', curl)
36
30
  end
37
31
 
38
32
  def mapping
@@ -83,16 +77,16 @@ module Tire
83
77
  count = 0
84
78
 
85
79
  begin
86
- Configuration.client.post("#{Configuration.url}/_bulk", payload.join("\n"))
80
+ response = Configuration.client.post("#{Configuration.url}/_bulk", payload.join("\n"))
81
+ raise RuntimeError, "#{response.code} > #{response.body}" if response.failure?
82
+ response
87
83
  rescue Exception => error
88
84
  if count < tries
89
85
  count += 1
90
- STDERR.puts "[ERROR] #{error.message}:#{error.http_body rescue nil}, retrying (#{count})..."
86
+ STDERR.puts "[ERROR] #{error.message}, retrying (#{count})..."
91
87
  retry
92
88
  else
93
- STDERR.puts "[ERROR] Too many exceptions occured, giving up..."
94
- STDERR.puts "Response: #{error.http_body rescue nil}"
95
- raise
89
+ STDERR.puts "[ERROR] Too many exceptions occured, giving up. The HTTP response was: #{error.message}"
96
90
  end
97
91
  ensure
98
92
  curl = %Q|curl -X POST "#{Configuration.url}/_bulk" -d '{... data omitted ...}'|
@@ -135,7 +129,7 @@ module Tire
135
129
  raise ArgumentError, "Please pass a document ID" unless id
136
130
 
137
131
  result = Configuration.client.delete "#{Configuration.url}/#{@name}/#{type}/#{id}"
138
- MultiJson.decode(result) if result
132
+ MultiJson.decode(result.body) if result.success?
139
133
  end
140
134
 
141
135
  def retrieve(type, id)
@@ -9,6 +9,9 @@ module Tire
9
9
 
10
10
  # Get or set the index name for this model, based on arguments.
11
11
  #
12
+ # By default, uses ActiveSupport inflection, so a class named `Article`
13
+ # will be stored in the `articles` index.
14
+ #
12
15
  # To get the index name:
13
16
  #
14
17
  # Article.index_name
@@ -19,13 +22,51 @@ module Tire
19
22
  #
20
23
  def index_name name=nil
21
24
  @index_name = name if name
22
- @index_name || klass.model_name.plural
25
+ @index_name || [index_prefix, klass.model_name.plural].compact.join('_')
23
26
  end
24
27
 
25
- # Get the document type for this model, based on the class name.
28
+ # Set or get index prefix for all models or for a specific model.
26
29
  #
27
- def document_type
28
- klass.model_name.singular
30
+ # To set the prefix for all models (preferably in an initializer inside Rails):
31
+ #
32
+ # Tire::Model::Search.index_prefix Rails.env
33
+ #
34
+ # To set the prefix for specific model:
35
+ #
36
+ # class Article
37
+ # # ...
38
+ # index_prefix 'my_prefix'
39
+ # end
40
+ #
41
+ # TODO: Maybe this would be more sane with ActiveSupport extensions such as `class_attribute`?
42
+ #
43
+ @@__index_prefix__ = nil
44
+ def index_prefix(*args)
45
+ # Uses class or instance variable depending on the context
46
+ if args.size > 0
47
+ value = args.pop
48
+ self.is_a?(Module) ? ( @@__index_prefix__ = value ) : ( @__index_prefix__ = value )
49
+ end
50
+ self.is_a?(Module) ? ( @@__index_prefix__ || nil ) : ( @__index_prefix__ || @@__index_prefix__ || nil )
51
+ end
52
+ extend self
53
+
54
+ # Get or set the document type for this model, based on arguments.
55
+ #
56
+ # By default, uses ActiveSupport inflection, so a class named `Article`
57
+ # will be stored as the `article` type.
58
+ #
59
+ # To get the document type:
60
+ #
61
+ # Article.document_type
62
+ #
63
+ # To set the document type:
64
+ #
65
+ # Article.document_type 'my-custom-type'
66
+ #
67
+ def document_type name=nil
68
+ @document_type = name if name
69
+ @document_type || klass.model_name.singular
29
70
  end
30
71
  end
31
72
 
@@ -44,6 +44,12 @@ module Tire
44
44
  include Persistence::Attributes::InstanceMethods
45
45
 
46
46
  include Persistence::Storage
47
+
48
+ ['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches'].each do |attr|
49
+ define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
50
+ define_method("#{attr}") { @attributes[attr] }
51
+ end
52
+
47
53
  end
48
54
 
49
55
  end
@@ -19,6 +19,12 @@ module Tire
19
19
  #
20
20
  module Search
21
21
 
22
+ # Alias for Tire::Model::Naming::ClassMethods.index_prefix
23
+ #
24
+ def self.index_prefix(*args)
25
+ Naming::ClassMethods.index_prefix(*args)
26
+ end
27
+
22
28
  module ClassMethods
23
29
 
24
30
  # Returns search results for a given query.
@@ -152,6 +158,14 @@ module Tire
152
158
  end
153
159
  end
154
160
 
161
+ def matches
162
+ @attributes['matches']
163
+ end
164
+
165
+ def matches=(value)
166
+ @attributes ||= {}; @attributes['matches'] = value
167
+ end
168
+
155
169
  end
156
170
 
157
171
  module Loader
@@ -192,13 +206,6 @@ module Tire
192
206
  include Tire::Model::Percolate::InstanceMethods
193
207
  include InstanceMethods
194
208
 
195
- ['_score', '_type', '_index', '_version', 'sort', 'highlight', 'matches'].each do |attr|
196
- # TODO: Find a sane way to add attributes like _score for ActiveRecord -
197
- # `define_attribute_methods [attr]` does not work in AR.
198
- define_method("#{attr}=") { |value| @attributes ||= {}; @attributes[attr] = value }
199
- define_method("#{attr}") { @attributes[attr] }
200
- end
201
-
202
209
  INTERFACE = public_instance_methods.map(&:to_sym) - Object.public_instance_methods.map(&:to_sym)
203
210
 
204
211
  attr_reader :instance
@@ -267,6 +274,7 @@ module Tire
267
274
  Results::Item.send :include, Loader
268
275
  end
269
276
 
277
+
270
278
  end
271
279
 
272
280
  end
@@ -7,8 +7,12 @@ module Tire
7
7
  @total
8
8
  end
9
9
 
10
+ def per_page
11
+ (@options[:per_page] || @options[:size] || 10 ).to_i
12
+ end
13
+
10
14
  def total_pages
11
- ( @total.to_f / (@options[:per_page] || @options[:size] || 10 ).to_i ).ceil
15
+ ( @total.to_f / per_page ).ceil
12
16
  end
13
17
 
14
18
  def current_page
@@ -27,6 +31,10 @@ module Tire
27
31
  current_page < total_pages ? (current_page + 1) : nil
28
32
  end
29
33
 
34
+ def offset
35
+ per_page * (current_page - 1)
36
+ end
37
+
30
38
  def out_of_bounds?
31
39
  current_page > total_pages
32
40
  end
data/lib/tire/search.rb CHANGED
@@ -68,14 +68,15 @@ module Tire
68
68
 
69
69
  def perform
70
70
  @response = Configuration.client.get(@url, self.to_json)
71
+ if @response.failure?
72
+ STDERR.puts "[REQUEST FAILED] #{self.to_curl}\n"
73
+ return false
74
+ end
71
75
  @json = MultiJson.decode(@response.body)
72
76
  @results = Results::Collection.new(@json, @options)
73
- self
74
- rescue Exception => error
75
- STDERR.puts "[REQUEST FAILED] #{self.to_curl}\n"
76
- raise
77
+ return self
77
78
  ensure
78
- logged(error)
79
+ logged
79
80
  end
80
81
 
81
82
  def to_curl
@@ -104,21 +105,20 @@ module Tire
104
105
 
105
106
  Configuration.logger.log_request '_search', indices, to_curl
106
107
 
107
- code = @response ? @response.code : error.message
108
108
  took = @json['took'] rescue nil
109
109
 
110
110
  if Configuration.logger.level.to_s == 'debug'
111
111
  # FIXME: Depends on RestClient implementation
112
- body = if @response
112
+ body = if @json
113
113
  defined?(Yajl) ? Yajl::Encoder.encode(@json, :pretty => true) : MultiJson.encode(@json)
114
114
  else
115
- error.http_body rescue ''
115
+ @response.body
116
116
  end
117
117
  else
118
118
  body = ''
119
119
  end
120
120
 
121
- Configuration.logger.log_response code, took, body
121
+ Configuration.logger.log_response @response.code, took, body
122
122
  end
123
123
  end
124
124