lazy_resource 0.1.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.
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/Gemfile +14 -0
- data/Guardfile +5 -0
- data/LICENSE +22 -0
- data/README.md +127 -0
- data/Rakefile +6 -0
- data/examples/github.rb +64 -0
- data/lazy_resource.gemspec +22 -0
- data/lib/lazy_resource.rb +39 -0
- data/lib/lazy_resource/attributes.rb +131 -0
- data/lib/lazy_resource/errors.rb +72 -0
- data/lib/lazy_resource/http_mock.rb +23 -0
- data/lib/lazy_resource/mapping.rb +73 -0
- data/lib/lazy_resource/relation.rb +132 -0
- data/lib/lazy_resource/request.rb +63 -0
- data/lib/lazy_resource/resource.rb +197 -0
- data/lib/lazy_resource/resource_queue.rb +40 -0
- data/lib/lazy_resource/types.rb +53 -0
- data/lib/lazy_resource/url_generation.rb +98 -0
- data/lib/lazy_resource/version.rb +3 -0
- data/spec/fixtures/comment.rb +6 -0
- data/spec/fixtures/post.rb +6 -0
- data/spec/fixtures/user.rb +9 -0
- data/spec/lazy_resource/attributes_spec.rb +157 -0
- data/spec/lazy_resource/errors_spec.rb +48 -0
- data/spec/lazy_resource/mapping_spec.rb +237 -0
- data/spec/lazy_resource/relation_spec.rb +169 -0
- data/spec/lazy_resource/request_spec.rb +143 -0
- data/spec/lazy_resource/resource_queue_spec.rb +66 -0
- data/spec/lazy_resource/resource_spec.rb +463 -0
- data/spec/lazy_resource/types_spec.rb +109 -0
- data/spec/lazy_resource/url_generation_spec.rb +155 -0
- data/spec/spec_helper.rb +25 -0
- metadata +165 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@lazy_resource --create
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in lazy_resource.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
# Development gems
|
7
|
+
gem 'pry'
|
8
|
+
gem 'debugger'
|
9
|
+
gem 'rake'
|
10
|
+
|
11
|
+
gem 'rspec'
|
12
|
+
gem 'simplecov', :require => false
|
13
|
+
gem 'guard-rspec'
|
14
|
+
gem 'growl'
|
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Andrew Latimer
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# LazyResource
|
2
|
+
|
3
|
+
[ActiveResource](http://github.com/rails/activeresource) with its feet
|
4
|
+
up. The block less, do more consumer of delicious APIs.
|
5
|
+
|
6
|
+
LazyResource is `ActiveRecord` made less blocking. Built on top of
|
7
|
+
[Typhoeus](https://github.com/typhoeus/typhoeus), it queues up your requests to make your
|
8
|
+
API consumer a whole lot quicker. Work smarter, not harder.
|
9
|
+
|
10
|
+
It also has a simple, readable, easy-to-use API, borrowing some of the
|
11
|
+
best parts of ActiveResource with a bit of ActiveRecord method-chaining
|
12
|
+
flair. Not only is it faster, it's better-looking, too.
|
13
|
+
|
14
|
+
Don't believe me? Check out some of the examples in the `examples` directory
|
15
|
+
to see for yourself.
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
gem 'lazy_resource'
|
22
|
+
|
23
|
+
And then execute:
|
24
|
+
|
25
|
+
$ bundle
|
26
|
+
|
27
|
+
Or install it yourself as:
|
28
|
+
|
29
|
+
$ gem install lazy_resource
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### Define a model:
|
34
|
+
|
35
|
+
class User
|
36
|
+
include LazyResource::Resource
|
37
|
+
|
38
|
+
self.site = 'http://example.com'
|
39
|
+
|
40
|
+
attribute :id, Integer
|
41
|
+
attribute :first_name, String
|
42
|
+
attribute :last_name, String
|
43
|
+
end
|
44
|
+
|
45
|
+
### Then use it:
|
46
|
+
|
47
|
+
me = User.find(1) # => GET /users/1
|
48
|
+
bobs = User.where(:first_name => 'Bob') # => GET /users?first_name=Bob
|
49
|
+
sam = User.find_by_first_name('Sam') # => GET /users?first_name=Sam
|
50
|
+
terry = User.new(:first_name => 'Terry', :last_name => 'Simpson')
|
51
|
+
terry.save # => POST /users
|
52
|
+
terry.last_name = 'Jackson'
|
53
|
+
terry.save # => PUT /users/4
|
54
|
+
terry.destroy # => DELETE /users/4
|
55
|
+
|
56
|
+
### What about associations?
|
57
|
+
|
58
|
+
class Post
|
59
|
+
include LazyResource::Resource
|
60
|
+
|
61
|
+
self.site = 'http://example.com'
|
62
|
+
|
63
|
+
attribute :id, Integer
|
64
|
+
attribute :title, String
|
65
|
+
attribute :body, String
|
66
|
+
attribute :user, User
|
67
|
+
end
|
68
|
+
|
69
|
+
class User
|
70
|
+
include LazyResource::Resource
|
71
|
+
# Attributes that have a type in an array are has-many
|
72
|
+
attribute :posts, [Post]
|
73
|
+
end
|
74
|
+
|
75
|
+
me = User.find(1)
|
76
|
+
me.posts.all # => GET /users/1/posts
|
77
|
+
|
78
|
+
### That's cool, but what if my end-point doesn't map with my association name?
|
79
|
+
|
80
|
+
class Photo
|
81
|
+
include LazyResource::Resource
|
82
|
+
|
83
|
+
attribute :id, Integer
|
84
|
+
attribute :urls, Hash
|
85
|
+
attribute :photographer, User, :from => 'users'
|
86
|
+
end
|
87
|
+
|
88
|
+
### I thought you said this was non-blocking?
|
89
|
+
|
90
|
+
It is. That original example above with me, the Bobs, Sam, and Terry? Those
|
91
|
+
first four requests would all get executed at the same time, when Terry
|
92
|
+
was saved. Pretty neat, eh?
|
93
|
+
|
94
|
+
### That's great, but could you show me some examples that are a bit more complex?
|
95
|
+
|
96
|
+
Sure thing! Take a look at the files in the `examples` directory, or
|
97
|
+
read through the specs.
|
98
|
+
|
99
|
+
## Contributing
|
100
|
+
|
101
|
+
1. Fork it
|
102
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
103
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
104
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
105
|
+
5. Create new Pull Request
|
106
|
+
|
107
|
+
Make sure you have some decent test coverage, and please don't bump up
|
108
|
+
the version number. If you want to maintain your own version, go for it,
|
109
|
+
but put it in a separate commit so I can ignore it when I merge the rest
|
110
|
+
of your stuff in.
|
111
|
+
|
112
|
+
## Recognition
|
113
|
+
|
114
|
+
Thanks to:
|
115
|
+
|
116
|
+
* [Typhoeus](http://github.com/typhoeus/typhoeus) for the http request
|
117
|
+
queuing code that forms the foundation of LazyResource.
|
118
|
+
* [ActiveResource](http://github.com/rails/activeresource) for the idea
|
119
|
+
(and a bit of code).
|
120
|
+
* [Get Satisfaction](http://getsatisfaction.com) for putting food on my
|
121
|
+
table.
|
122
|
+
|
123
|
+
## TODO
|
124
|
+
|
125
|
+
* Clean up `LazyResource::Attributes#create_setter`
|
126
|
+
* Add more specs for associations
|
127
|
+
|
data/Rakefile
ADDED
data/examples/github.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'lazy_resource'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
LazyResource.configure do |config|
|
5
|
+
config.site = "https://api.github.com"
|
6
|
+
end
|
7
|
+
|
8
|
+
class User
|
9
|
+
include LazyResource::Resource
|
10
|
+
end
|
11
|
+
|
12
|
+
class Repo
|
13
|
+
include LazyResource::Resource
|
14
|
+
|
15
|
+
attribute :id, Fixnum
|
16
|
+
attribute :pushed_at, DateTime
|
17
|
+
attribute :owner, User
|
18
|
+
attribute :clone_url, String
|
19
|
+
attribute :name, String
|
20
|
+
attribute :description, String
|
21
|
+
end
|
22
|
+
|
23
|
+
class User
|
24
|
+
include LazyResource::Resource
|
25
|
+
|
26
|
+
self.primary_key_name = 'login'
|
27
|
+
|
28
|
+
attribute :id, Fixnum
|
29
|
+
attribute :login, String
|
30
|
+
attribute :name, String
|
31
|
+
attribute :bio, String
|
32
|
+
attribute :company, String
|
33
|
+
attribute :number_of_public_repos, Fixnum, :from => 'public_repos'
|
34
|
+
attribute :repos, [Repo]
|
35
|
+
end
|
36
|
+
|
37
|
+
dhh = User.find('dhh')
|
38
|
+
dhh.repos.each do |repo|
|
39
|
+
puts "#{dhh.name} pushed to #{repo.name} on #{repo.pushed_at.strftime("%D")}"
|
40
|
+
end
|
41
|
+
|
42
|
+
puts "Fetching 10 users serially..."
|
43
|
+
Benchmark.bm do |x|
|
44
|
+
x.report do
|
45
|
+
names = []
|
46
|
+
10.times do |i|
|
47
|
+
u = User.find(i + 1)
|
48
|
+
names << u.name
|
49
|
+
end
|
50
|
+
|
51
|
+
puts names
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
puts "\nFetching 10 users in parallel..."
|
56
|
+
Benchmark.bm do |x|
|
57
|
+
x.report do
|
58
|
+
users = []
|
59
|
+
10.times do |i|
|
60
|
+
users << User.find(i + 1)
|
61
|
+
end
|
62
|
+
puts users.map { |user| user.name }
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/lazy_resource/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Andrew Latimer"]
|
6
|
+
gem.email = ["andrew@elpasoera.com"]
|
7
|
+
gem.description = %q{ActiveResource with it's feet up. The write less, do more consumer of delicious APIs.}
|
8
|
+
gem.summary = %q{ActiveResource with it's feet up. The write less, do more consumer of delicious APIs.}
|
9
|
+
gem.homepage = "http://github.com/ahlatimer/lazy_resource"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "lazy_resource"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = LazyResource::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency 'activemodel', '>= 3.1.0'
|
19
|
+
gem.add_dependency 'activesupport', '>= 3.1.0'
|
20
|
+
gem.add_dependency 'json', '>= 1.5.2'
|
21
|
+
gem.add_dependency 'typhoeus', '>= 0.2.4'
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support'
|
3
|
+
require 'json'
|
4
|
+
require 'typhoeus'
|
5
|
+
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
8
|
+
require 'active_support/core_ext/class/attribute'
|
9
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
10
|
+
require 'active_support/core_ext/kernel/reporting'
|
11
|
+
require 'active_support/core_ext/module/delegation'
|
12
|
+
require 'active_support/core_ext/module/aliasing'
|
13
|
+
require 'active_support/core_ext/object/blank'
|
14
|
+
require 'active_support/core_ext/object/to_query'
|
15
|
+
require 'active_support/core_ext/object/duplicable'
|
16
|
+
require 'set'
|
17
|
+
require 'uri'
|
18
|
+
|
19
|
+
require 'active_support/core_ext/uri'
|
20
|
+
|
21
|
+
require 'lazy_resource/version'
|
22
|
+
require 'lazy_resource/errors'
|
23
|
+
|
24
|
+
module LazyResource
|
25
|
+
extend ActiveSupport::Autoload
|
26
|
+
|
27
|
+
autoload :Attributes
|
28
|
+
autoload :Mapping
|
29
|
+
autoload :Relation
|
30
|
+
autoload :Request
|
31
|
+
autoload :Resource
|
32
|
+
autoload :ResourceQueue
|
33
|
+
autoload :Types
|
34
|
+
autoload :UrlGeneration
|
35
|
+
|
36
|
+
def self.configure(&block)
|
37
|
+
yield Resource
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module LazyResource
|
2
|
+
module Attributes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Dirty
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def attribute(name, type, options={})
|
8
|
+
attributes[name] = { :type => type, :options => options }
|
9
|
+
|
10
|
+
create_getter(name, type, options) unless options[:skip_getter]
|
11
|
+
create_setter(name, type, options) unless options[:skip_setter]
|
12
|
+
create_question(name, type, options) unless options[:skip_question] || options[:skip_getter]
|
13
|
+
|
14
|
+
@attribute_methods_generated = false
|
15
|
+
define_attribute_methods [name]
|
16
|
+
end
|
17
|
+
|
18
|
+
def fetch_all
|
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)
|
21
|
+
end
|
22
|
+
|
23
|
+
def attributes
|
24
|
+
@attributes ||= {}
|
25
|
+
end
|
26
|
+
|
27
|
+
def primary_key_name
|
28
|
+
@primary_key_name ||= 'id'
|
29
|
+
end
|
30
|
+
|
31
|
+
def primary_key_name=(pk)
|
32
|
+
@primary_key_name = pk
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_writer :element_name
|
36
|
+
def element_name
|
37
|
+
@element_name ||= model_name.element
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_writer :collection_name
|
41
|
+
def collection_name
|
42
|
+
@collection_name ||= ActiveSupport::Inflector.pluralize(element_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def create_setter(name, type, options={})
|
47
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
48
|
+
def #{name}=(value)
|
49
|
+
self.class.fetch_all if !fetched?
|
50
|
+
|
51
|
+
#{name}_will_change! unless @#{name} == value || (!fetched? && persisted?)
|
52
|
+
@#{name} = value
|
53
|
+
end
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_getter(name, type, options={})
|
58
|
+
method = <<-RUBY
|
59
|
+
def #{name}
|
60
|
+
self.class.fetch_all if !fetched
|
61
|
+
RUBY
|
62
|
+
|
63
|
+
if type.is_a?(Array) && type.first.include?(LazyResource::Resource)
|
64
|
+
if options[:using].nil?
|
65
|
+
method << <<-RUBY
|
66
|
+
if @#{name}.nil?
|
67
|
+
@#{name} = #{type.first}.where(:"\#{self.class.element_name}_id" => self.primary_key)
|
68
|
+
end
|
69
|
+
RUBY
|
70
|
+
else
|
71
|
+
method << <<-RUBY
|
72
|
+
return [] if self.#{options[:using]}.nil?
|
73
|
+
|
74
|
+
if @#{name}.nil?
|
75
|
+
@#{name} = LazyResource::Relation.new(#{type.first}, :fetched => true)
|
76
|
+
@#{name}.fetched = false
|
77
|
+
request = LazyResource::Request.new(self.#{options[:using]}, @#{name})
|
78
|
+
self.class.request_queue.queue(request)
|
79
|
+
end
|
80
|
+
|
81
|
+
@#{name}
|
82
|
+
RUBY
|
83
|
+
end
|
84
|
+
elsif type.include?(LazyResource::Resource)
|
85
|
+
if options[:using].nil?
|
86
|
+
method << <<-RUBY
|
87
|
+
if @#{name}.nil?
|
88
|
+
@#{name} = #{type}.where(:"\#{self.class.element_name}_id" => self.primary_key)
|
89
|
+
end
|
90
|
+
RUBY
|
91
|
+
else
|
92
|
+
method << <<-RUBY
|
93
|
+
return [] if self.#{options[:using]}.nil?
|
94
|
+
|
95
|
+
if @#{name}.nil?
|
96
|
+
@#{name} = #{type}.new
|
97
|
+
request = LazyResource::Request.new(self.#{options[:using]}, @#{name})
|
98
|
+
self.class.request_queue.queue(request)
|
99
|
+
end
|
100
|
+
|
101
|
+
@#{name}
|
102
|
+
RUBY
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
method << <<-RUBY
|
107
|
+
@#{name}
|
108
|
+
end
|
109
|
+
RUBY
|
110
|
+
|
111
|
+
class_eval method, __FILE__, __LINE__ + 1
|
112
|
+
end
|
113
|
+
|
114
|
+
def create_question(name, type, options={})
|
115
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
116
|
+
def #{name}?
|
117
|
+
!!self.#{name}
|
118
|
+
end
|
119
|
+
RUBY
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def primary_key
|
124
|
+
self.send(self.class.primary_key_name)
|
125
|
+
end
|
126
|
+
|
127
|
+
included do
|
128
|
+
extend ActiveModel::Naming
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|