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 ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --debugger
2
+ --color
3
+ --format documentation
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
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2, :cli => "--color --format documentation" do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/lazy_resource/(.+)\.rb$}) { |m| "spec/lazy_resource/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
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
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task :default => :spec
@@ -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