lazy_resource 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTE2MGY4NDk1ZWRjMTY2ZDAxYmQyZmViYzhiNDFlZDQ3ZGRmOTkwYw==
5
+ data.tar.gz: !binary |-
6
+ YTVjZTljYzZjMGUwOGM1MGFlNDIyYjc2OTFmOTI5MmYwOGQxZTNmNg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ ZDFkM2FmNTkwNmY5NTlmZWZhNTkyMzA2YzkwNDYyM2YwZDI5N2FiYmQ4NmU1
10
+ YWVhZGQ1ODAwZDRhYzYyNzgwOGRmNDNmZmJiZGFkOGYyYjM2ZTExOTE1Njk3
11
+ ZTc0NmIzMzRmYmRkNDRjNzAyZTE4NDhmNmQ3ZGRkODk4Yzk5MzI=
12
+ data.tar.gz: !binary |-
13
+ ZTIzMWY3ZTlhNTUxMWMyYjAzNzczNTdmYzU0ZjcyNTgxM2QzZTQ0MWQ0ZmQz
14
+ MTNlZDM5MDJjMjU1ZWNhMmJlZjE1M2VmZDI2NmU4NmJlNThjYTg2ZWZjMmFj
15
+ OTFjMTFlZWZlYzVlNzgxYWNkZjM5MzE2ODYzNTY2ZjJiOGY2NGM=
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
- --debugger
2
1
  --color
3
2
  --format documentation
data/.rvmrc CHANGED
@@ -1 +1 @@
1
- rvm use 1.9.3@lazy_resource --create
1
+ rvm use 1.9.3-p194@lazy_resource --create
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ branches:
5
+ only:
6
+ - master
data/Gemfile CHANGED
@@ -9,7 +9,7 @@ gem 'debugger'
9
9
  gem 'rake'
10
10
  gem 'twitter_cldr'
11
11
 
12
- gem 'rspec'
12
+ gem 'rspec', '2.12.0'
13
13
  gem 'simplecov', :require => false
14
14
  gem 'guard-rspec'
15
15
  gem 'growl'
@@ -0,0 +1,36 @@
1
+ Major components/features:
2
+ * Finder methods
3
+ * Attributes
4
+ * Queueing
5
+ * Generating requests
6
+ * Handling responses
7
+ * URL generation
8
+ * Mapping responses to resources
9
+ * Mapping attributes
10
+ * Associations
11
+ * Logging
12
+ * Mocking
13
+
14
+ Goals of next version:
15
+ * Remove reliance on Thread.current
16
+ * railtie?
17
+ * request?
18
+ * requests are local to a request (if a request object is available)
19
+ * Easier method of setting options that are request-wide
20
+ * currently uses an attribute on Thread.current
21
+ * Allow user to set scope of queue (request-level, application-level,
22
+ thread-level, etc.)
23
+ * Remove reliance on modules -- use separate objects
24
+ * Redefine some of the more obtuse options (e.g., :using)
25
+ * Class methods on resources should be persisted to relations
26
+ * Add support for scopes
27
+ * local_attributes (attributes that are set via to_json/as_json, but
28
+ are not actually part of the server's response)
29
+ * Allow queueing to be disabled per-request/per-model
30
+ * Allow access to attributes returned from the server for which a map
31
+ does not exist
32
+ * Fix issue where two a Resource#where that relies on data from a
33
+ different resource can cause it to prematurely process based on
34
+ previous calls to #where
35
+ * Fix issue where Relation does not respect .from= setting
36
+ * Move queueing logic into a separate module/gem?
data/README.md CHANGED
@@ -14,6 +14,8 @@ flair. Not only is it faster, it's better-looking, too.
14
14
  Don't believe me? Check out some of the examples in the `examples` directory
15
15
  to see for yourself.
16
16
 
17
+ [![Build Status](https://travis-ci.org/ahlatimer/lazy_resource.png?branch=master)](https://travis-ci.org/ahlatimer/lazy_resource)
18
+
17
19
  ## Installation
18
20
 
19
21
  Add this line to your application's Gemfile:
@@ -32,61 +34,80 @@ Or install it yourself as:
32
34
 
33
35
  ### Define a model:
34
36
 
35
- class User
36
- include LazyResource::Resource
37
+ ```ruby
38
+ class User
39
+ include LazyResource::Resource
37
40
 
38
- self.site = 'http://example.com'
41
+ self.site = 'http://example.com'
39
42
 
40
- attribute :id, Integer
41
- attribute :first_name, String
42
- attribute :last_name, String
43
- end
43
+ attribute :id, Integer
44
+ attribute :first_name, String
45
+ attribute :last_name, String
46
+ end
47
+ ```
44
48
 
45
49
  ### Then use it:
46
50
 
47
- me = User.find(1) # => GET /users/1
48
- bobs = User.where(:first_name => 'Bob') # => GET /users?first_name=Bob
49
- terry = User.new(:first_name => 'Terry', :last_name => 'Simpson')
50
- terry.save # => POST /users
51
- terry.last_name = 'Jackson'
52
- terry.save # => PUT /users/4
53
- terry.destroy # => DELETE /users/4
51
+ ```ruby
52
+ me = User.find(1) # => GET /users/1
53
+ bobs = User.where(:first_name => 'Bob') # => GET /users?first_name=Bob
54
+ terry = User.new(:first_name => 'Terry', :last_name => 'Simpson')
55
+ terry.save # => POST /users
56
+ terry.last_name = 'Jackson'
57
+ terry.save # => PUT /users/4
58
+ terry.destroy # => DELETE /users/4
59
+ ```
54
60
 
55
61
  ### What about associations?
56
62
 
57
- class Post
58
- include LazyResource::Resource
63
+ ```ruby
64
+ class Post
65
+ include LazyResource::Resource
59
66
 
60
- self.site = 'http://example.com'
67
+ self.site = 'http://example.com'
61
68
 
62
- attribute :id, Integer
63
- attribute :title, String
64
- attribute :body, String
65
- attribute :user, User
66
- end
69
+ attribute :id, Integer
70
+ attribute :title, String
71
+ attribute :body, String
72
+ attribute :user, User
73
+ end
67
74
 
68
- class User
69
- include LazyResource::Resource
70
- # Attributes that have a type in an array are has-many
71
- attribute :posts, [Post]
72
- end
75
+ class User
76
+ include LazyResource::Resource
77
+ # Attributes that have a type in an array are has-many
78
+ attribute :posts, [Post]
79
+ end
73
80
 
74
- me = User.find(1)
75
- me.posts.all # => GET /users/1/posts
81
+ me = User.find(1)
82
+ me.posts.all # => GET /users/1/posts
83
+ ```
76
84
 
77
85
  ### That's cool, but what if my end-point doesn't map with my association name?
78
86
 
79
- class Photo
80
- include LazyResource::Resource
87
+ ```ruby
88
+ class Photo
89
+ include LazyResource::Resource
90
+
91
+ attribute :id, Integer
92
+ attribute :urls, Hash
93
+ attribute :photographer, User, :from => 'users'
94
+ end
81
95
 
82
- attribute :id, Integer
83
- attribute :urls, Hash
84
- attribute :photographer, User, :from => 'users'
85
- end
96
+ # similarly, model-level
97
+ class User
98
+ self.from = 'people'
99
+ end
100
+ ```
86
101
 
87
- ### I thought you said this was non-blocking?
102
+ ### What's this about blocking less?
88
103
 
89
- It is. That original example above with me, the Bobs, Sam, and Terry? Those
104
+ Unlike ActiveResource, LazyResource doesn't initiate a request on every
105
+ find. Instead, whenever you do a find, where, etc. it throws those
106
+ requests into a queue that gets processed when you hit an accessor.
107
+ Built on Typhoeus, all of those queued requests get executed at the same
108
+ time.
109
+
110
+ That original example above with me, the Bobs, Sam, and Terry? Those
90
111
  first four requests would all get executed at the same time, when Terry
91
112
  was saved. Pretty neat, eh?
92
113
 
@@ -101,32 +122,72 @@ Here you go:
101
122
 
102
123
  Fetch associations without hitting the URL generation code.
103
124
 
104
- class Photo
105
- include LazyResource::Resource
125
+ ```ruby
126
+ class Photo
127
+ include LazyResource::Resource
128
+
129
+ attribute :id, Fixnum
106
130
 
107
- attribute :id, Fixnum
108
- attribute :photographer_url, String
109
- attribute :photographer, User, :using => :photographer_url
131
+ # define the route inline
132
+ attribute :location, Location, :route => '/location/:lat,:long'
110
133
 
111
- # or define it yourself
112
- def photographer_url
113
- "/path/to/photographer"
114
- end
115
- end
134
+ # define the route inline using a proc
135
+ attribute :model, Model, :route => lambda { "/photos/#{id}/model" }
136
+
137
+ # define the route using a method or attribute
138
+ attribute :photographer, User, :route => :photographer_url
139
+ attribute :photographer_url, String
140
+ def photographer_url
141
+ "/photographer/:name"
142
+ end
143
+ end
144
+ ```
116
145
 
117
146
  Parsing responses like { 'photo': ... }
118
147
 
119
- class Photo
120
- include LazyResource::Resource
148
+ ```ruby
149
+ class Photo
150
+ include LazyResource::Resource
121
151
 
122
- self.root_node_name = 'photo'
123
- end
152
+ self.root_node_name = 'photo'
153
+ end
154
+ ```
124
155
 
125
156
  or multiple options
126
157
 
127
- class Photo
128
- self.root_node_name = ['photo', 'photos']
129
- end
158
+ ```ruby
159
+ class Photo
160
+ include LazyResource::Resource
161
+
162
+ self.root_node_name = ['photo', 'photos']
163
+ end
164
+ ```
165
+
166
+ Parsing responses like { 'photos': ..., 'total': 100, 'page': 2, ... }
167
+
168
+ ```ruby
169
+ photos = Photo.where(:user_id => 123)
170
+ photos.other_attributes # => { 'total' => 100, 'page' => 2, ... }
171
+ ```
172
+
173
+ Sending default headers or params
174
+
175
+ Keep in mind that this can be accomplished by using, .e.g,
176
+ `.where(:access_token => current_user.access_token, :headers => { :"X-Access-Token" => current_user.access_token })`,
177
+ but I prefer this method because it keeps the logic in one place and
178
+ doesn't litter your where calls with stuff that doesn't look
179
+ ActiveRecord-y. Using Thread.current does seem a bit icky, but at least
180
+ it's in one place...
181
+
182
+ ```ruby
183
+ # in an around_filter or similar
184
+ Thread.current[:default_headers] = { :"X-Access-Token" => current_user.access_token }
185
+ Thread.current[:default_params] = { :"access_token" => current_user.access_token }
186
+ yield
187
+ # this is important, otherwise the headers/params could persist amongst various requests
188
+ Thread.current[:default_headers] = nil
189
+ Thread.current[:default_params] = nil
190
+ ```
130
191
 
131
192
  ## Contributing
132
193
 
@@ -141,12 +202,6 @@ the version number. If you want to maintain your own version, go for it,
141
202
  but put it in a separate commit so I can ignore it when I merge the rest
142
203
  of your stuff in.
143
204
 
144
- ## It's alpha, yo
145
-
146
- I'm not using this in production anywhere (yet), so use at your own
147
- risk. It's got a pretty comprehensive test suite, but I'm sure there
148
- are at least a few bugs. If you find one, [report it](https://github.com/ahlatimer/lazy_resource/issues).
149
-
150
205
  ## Recognition
151
206
 
152
207
  Thanks to:
@@ -1,8 +1,16 @@
1
1
  require 'lazy_resource'
2
2
  require 'benchmark'
3
3
 
4
+ class SimpleLogger
5
+ def info(message)
6
+ puts message
7
+ end
8
+ end
9
+
4
10
  LazyResource.configure do |config|
5
11
  config.site = "https://api.github.com"
12
+ config.logger = SimpleLogger.new
13
+ config.debug = true
6
14
  end
7
15
 
8
16
  class User
@@ -15,8 +15,8 @@ Gem::Specification.new do |gem|
15
15
  gem.require_paths = ["lib"]
16
16
  gem.version = LazyResource::VERSION
17
17
 
18
- gem.add_dependency 'activemodel', '>= 3.1.0'
19
- gem.add_dependency 'activesupport', '>= 3.1.0'
18
+ gem.add_dependency 'activemodel', '~> 3.1'
19
+ gem.add_dependency 'activesupport', '~> 3.1'
20
20
  gem.add_dependency 'json', '>= 1.5.2'
21
- gem.add_dependency 'typhoeus', '0.6.1'
21
+ gem.add_dependency 'typhoeus', '0.6.6'
22
22
  end
@@ -7,6 +7,7 @@ require 'active_support'
7
7
  require 'active_support/core_ext/class/attribute_accessors'
8
8
  require 'active_support/core_ext/class/attribute'
9
9
  require 'active_support/core_ext/hash/indifferent_access'
10
+ require 'active_support/core_ext/hash/reverse_merge'
10
11
  require 'active_support/core_ext/kernel/reporting'
11
12
  require 'active_support/core_ext/module/delegation'
12
13
  require 'active_support/core_ext/module/aliasing'
@@ -21,6 +22,8 @@ require 'active_support/core_ext/uri'
21
22
  require 'lazy_resource/version'
22
23
  require 'lazy_resource/errors'
23
24
 
25
+ require 'lazy_resource/log_subscriber'
26
+
24
27
  require 'lazy_resource/ext/typhoeus'
25
28
 
26
29
  module LazyResource
@@ -55,4 +58,18 @@ module LazyResource
55
58
  def self.debug
56
59
  @debug = @debug.nil? ? false : @debug
57
60
  end
61
+
62
+ def self.max_concurrency
63
+ @max_concurrency ||= 200
64
+ end
65
+
66
+ def self.max_concurrency=(max)
67
+ @max_concurrency = max
68
+ end
69
+
70
+ def self.deprecate(message, file, line)
71
+ if self.logger && self.debug
72
+ self.logger.info "#{message} from #{file}##{line}"
73
+ end
74
+ end
58
75
  end
@@ -17,7 +17,7 @@ module LazyResource
17
17
 
18
18
  def fetch_all
19
19
  self.resource_queue.send_to_request_queue! if self.respond_to?(:resource_queue)
20
- self.request_queue.run if self.respond_to?(:request_queue)
20
+ self.request_queue.run if self.respond_to?(:request_queue) && self.request_queue.items_queued?
21
21
  end
22
22
 
23
23
  def attributes
@@ -55,13 +55,27 @@ module LazyResource
55
55
  end
56
56
 
57
57
  def create_getter(name, type, options={})
58
+ line = __LINE__ + 2
58
59
  method = <<-RUBY
59
60
  def #{name}
60
61
  self.class.fetch_all if !fetched
62
+ raise self.request_error if self.request_error.present?
61
63
  RUBY
62
64
 
65
+ route = options[:using] || options[:route]
66
+ if options[:using]
67
+ LazyResource.deprecate("Attribute option :using is deprecated. Please use :route instead.", __FILE__, __LINE__)
68
+ end
69
+
70
+ if route.is_a?(Proc)
71
+ route_method_name = "_#{name}_route".to_sym
72
+ define_method(route_method_name, route)
73
+ protected(route_method_name)
74
+ route = route_method_name
75
+ end
76
+
63
77
  if type.is_a?(Array) && type.first.include?(LazyResource::Resource)
64
- if options[:using].nil?
78
+ if route.nil?
65
79
  method << <<-RUBY
66
80
  if @#{name}.nil?
67
81
  @#{name} = #{type.first}.where(:"\#{self.class.element_name}_id" => self.primary_key)
@@ -69,20 +83,17 @@ module LazyResource
69
83
  RUBY
70
84
  else
71
85
  method << <<-RUBY
72
- return [] if self.#{options[:using]}.nil?
73
-
74
86
  if @#{name}.nil?
75
- @#{name} = LazyResource::Relation.new(#{type.first}, :fetched => true)
76
- request = LazyResource::Request.new(self.#{options[:using]}, @#{name}, :headers => @#{name}.headers)
77
- @#{name}.fetched = false
78
- self.class.request_queue.queue(request)
87
+ route = self.respond_to?("#{route}") ? self.send("#{route}") : "#{route}"
88
+ route = route.is_a?(Proc) ? route.call : route
89
+ @#{name} = #{type.first}.where(:"\#{self.class.element_name}_id" => self.primary_key, :_route => route)
79
90
  end
80
91
 
81
92
  @#{name}
82
93
  RUBY
83
94
  end
84
95
  elsif type.include?(LazyResource::Resource)
85
- if options[:using].nil?
96
+ if route.nil?
86
97
  method << <<-RUBY
87
98
  if @#{name}.nil?
88
99
  @#{name} = #{type}.where(:"\#{self.class.element_name}_id" => self.primary_key)
@@ -90,12 +101,10 @@ module LazyResource
90
101
  RUBY
91
102
  else
92
103
  method << <<-RUBY
93
- return [] if self.#{options[:using]}.nil?
94
-
95
104
  if @#{name}.nil?
96
- @#{name} = #{type}.new
97
- request = LazyResource::Request.new(self.#{options[:using]}, @#{name})
98
- self.class.request_queue.queue(request)
105
+ route = self.respond_to?("#{route}") ? self.send("#{route}") : "#{route}"
106
+ route = route.is_a?(Proc) ? route.call : route
107
+ @#{name} = #{type}.where(:"\#{self.class.element_name}_id" => self.primary_key, :_route => route)
99
108
  end
100
109
 
101
110
  @#{name}
@@ -108,7 +117,7 @@ module LazyResource
108
117
  end
109
118
  RUBY
110
119
 
111
- class_eval method, __FILE__, __LINE__ + 1
120
+ class_eval method, __FILE__, line
112
121
  end
113
122
 
114
123
  def create_question(name, type, options={})