lazy_resource 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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