lazy_resource 0.4.0 → 0.5.0
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.
- checksums.yaml +15 -0
- data/.rspec +0 -1
- data/.rvmrc +1 -1
- data/.travis.yml +6 -0
- data/Gemfile +1 -1
- data/NOTES.md +36 -0
- data/README.md +115 -60
- data/examples/github.rb +8 -0
- data/lazy_resource.gemspec +3 -3
- data/lib/lazy_resource.rb +17 -0
- data/lib/lazy_resource/attributes.rb +24 -15
- data/lib/lazy_resource/ext/typhoeus.rb +43 -10
- data/lib/lazy_resource/log_subscriber.rb +17 -0
- data/lib/lazy_resource/mapping.rb +1 -1
- data/lib/lazy_resource/relation.rb +11 -1
- data/lib/lazy_resource/request.rb +36 -16
- data/lib/lazy_resource/resource.rb +6 -5
- data/lib/lazy_resource/resource_queue.rb +33 -7
- data/lib/lazy_resource/url_generation.rb +6 -4
- data/lib/lazy_resource/version.rb +1 -1
- data/spec/lazy_resource/attributes_spec.rb +44 -23
- data/spec/lazy_resource/ext/typhoeus_spec.rb +44 -13
- data/spec/lazy_resource/lazy_resource_spec.rb +26 -1
- data/spec/lazy_resource/log_subscriber_spec.rb +46 -0
- data/spec/lazy_resource/relation_spec.rb +25 -0
- data/spec/lazy_resource/request_spec.rb +11 -4
- data/spec/lazy_resource/resource_queue_spec.rb +47 -0
- data/spec/lazy_resource/resource_spec.rb +20 -6
- data/spec/lazy_resource/url_generation_spec.rb +10 -4
- data/spec/spec_helper.rb +1 -1
- metadata +20 -31
checksums.yaml
ADDED
@@ -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
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm use 1.9.3@lazy_resource --create
|
1
|
+
rvm use 1.9.3-p194@lazy_resource --create
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/NOTES.md
ADDED
@@ -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
|
+
[](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
|
-
|
36
|
-
|
37
|
+
```ruby
|
38
|
+
class User
|
39
|
+
include LazyResource::Resource
|
37
40
|
|
38
|
-
|
41
|
+
self.site = 'http://example.com'
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
58
|
-
|
63
|
+
```ruby
|
64
|
+
class Post
|
65
|
+
include LazyResource::Resource
|
59
66
|
|
60
|
-
|
67
|
+
self.site = 'http://example.com'
|
61
68
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
attribute :id, Integer
|
70
|
+
attribute :title, String
|
71
|
+
attribute :body, String
|
72
|
+
attribute :user, User
|
73
|
+
end
|
67
74
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
96
|
+
# similarly, model-level
|
97
|
+
class User
|
98
|
+
self.from = 'people'
|
99
|
+
end
|
100
|
+
```
|
86
101
|
|
87
|
-
###
|
102
|
+
### What's this about blocking less?
|
88
103
|
|
89
|
-
|
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
|
-
|
105
|
-
|
125
|
+
```ruby
|
126
|
+
class Photo
|
127
|
+
include LazyResource::Resource
|
128
|
+
|
129
|
+
attribute :id, Fixnum
|
106
130
|
|
107
|
-
|
108
|
-
|
109
|
-
attribute :photographer, User, :using => :photographer_url
|
131
|
+
# define the route inline
|
132
|
+
attribute :location, Location, :route => '/location/:lat,:long'
|
110
133
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
120
|
-
|
148
|
+
```ruby
|
149
|
+
class Photo
|
150
|
+
include LazyResource::Resource
|
121
151
|
|
122
|
-
|
123
|
-
|
152
|
+
self.root_node_name = 'photo'
|
153
|
+
end
|
154
|
+
```
|
124
155
|
|
125
156
|
or multiple options
|
126
157
|
|
127
|
-
|
128
|
-
|
129
|
-
|
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:
|
data/examples/github.rb
CHANGED
@@ -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
|
data/lazy_resource.gemspec
CHANGED
@@ -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', '
|
19
|
-
gem.add_dependency 'activesupport', '
|
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.
|
21
|
+
gem.add_dependency 'typhoeus', '0.6.6'
|
22
22
|
end
|
data/lib/lazy_resource.rb
CHANGED
@@ -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
|
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
|
-
|
76
|
-
|
77
|
-
@#{name}
|
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
|
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
|
-
|
97
|
-
|
98
|
-
self.class.
|
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__,
|
120
|
+
class_eval method, __FILE__, line
|
112
121
|
end
|
113
122
|
|
114
123
|
def create_question(name, type, options={})
|