classproxy 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Pablo Fernandez
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ class-proxy
2
+ ===========
3
+
4
+ A generic (yet ActiveRecord compliant) class proxy to setup proxy methods for your classes.
5
+
6
+ ## Using
7
+
8
+ The `ClassProxy` module just needs to be included in a class to get the capabilities
9
+ provided by this gem.
10
+
11
+ ### Example
12
+
13
+ ```ruby
14
+ class UserDb
15
+ include MongoMapper::Document
16
+ include ClassProxy
17
+
18
+ primary_fetch { |args| where(args).first or (raise NotFound) }
19
+ fallback_fetch { |args| Octokit.user(args[:username]) }
20
+ after_fallback_fetch { |obj| UserDb.new(username: obj.login) }
21
+
22
+ key :name, String
23
+ key :reverse_name, String
24
+ key :username, String
25
+ key :public_repos, String
26
+ key :username_uppercase, String
27
+
28
+ # Use fallback_fetch since obj is requested
29
+ proxy_methods reverse_name: lambda { |obj| obj.name.reverse }
30
+
31
+ # No obj in the lambda, use the UserDb#username method here
32
+ proxy_methods username_uppercase: lambda { username.upcase }
33
+ end
34
+ ```
35
+
36
+ With this class now the following can be done:
37
+
38
+ ```ruby
39
+ > user = UserDb.fetch(username: 'heelhook')
40
+ => #<UserDb _id: 779813, name: "Pablo Fernandez", public_repos: "25", username: "heelhook">
41
+ ```
42
+
43
+ Since `Octokit.user` returned an object which responded to `name` and our `UserDb` class
44
+ has a corresponding attribute, `:name` was set for us.
45
+
46
+ ```ruby
47
+ > user.name
48
+ => "Pablo Fernandez"
49
+ ```
50
+
51
+ Yet `reverse_name` is not included, so when we call it, the `proxy_method` associated with it
52
+ is used.
53
+
54
+ ```ruby
55
+ > user.reverse_name
56
+ => "zednanreF olbaP"
57
+ ```
58
+
59
+ Since that `proxy_method`'s `lambda` requested an `|obj|`, the method `fallback_fetch` was used
60
+ and the object returned is used for `obj.name.reverse`
61
+
62
+ #### Using `proxy_methods` without new `fallback_fetch` calls
63
+
64
+ Let's see what's currently loaded.
65
+
66
+ ```ruby
67
+ > user.no_proxy_username_uppercase
68
+ => nil
69
+ ```
70
+
71
+ Using the proxy. We already have the username in our object, so our `username_uppercase` proxy method will
72
+ just use that (no `|obj|` is used).
73
+
74
+ ```ruby
75
+ > user.username_uppercase
76
+ => "HEELHOOK"
77
+ ```
78
+
79
+ #### Saving
80
+
81
+ Here the `fallback_fetch` will not be used since the object has been persisted.
82
+
83
+ ```ruby
84
+ > user.save
85
+ => true
86
+ > user = UserDb.fetch(username: 'heelhook')
87
+ => #<UserDb _id: 779813, name: "Pablo Fernandez", public_repos: "25", username: "heelhook">
88
+ ```
89
+
90
+ Like any
91
+
92
+ ## Compatibility
93
+
94
+ ClassProxy is tested against MRI 1.9.3.
95
+
96
+ ## Credits
97
+
98
+ Pablo Fernandez: heelhook at littleq . net
99
+
100
+ ## Contributing
101
+
102
+ Once you've made your great commits:
103
+
104
+ 1. Fork
105
+ 2. Create a topic branch - `git checkout -b my_branch`
106
+ 3. Push to your branch - `git push origin my_branch`
107
+ 4. Create a [Pull Request](https://help.github.com/pull-requests/) from your branch
108
+ 5. That's it!
109
+
110
+ ## License
111
+
112
+ Copyright (c) 2012 Pablo Fernandez
113
+
114
+ Permission is hereby granted, free of charge, to any person obtaining
115
+ a copy of this software and associated documentation files (the
116
+ "Software"), to deal in the Software without restriction, including
117
+ without limitation the rights to use, copy, modify, merge, publish,
118
+ distribute, sublicense, and/or sell copies of the Software, and to
119
+ permit persons to whom the Software is furnished to do so, subject to
120
+ the following conditions:
121
+
122
+ The above copyright notice and this permission notice shall be
123
+ included in all copies or substantial portions of the Software.
124
+
125
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
126
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
127
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
128
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
129
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
130
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
131
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require "bundler"
2
+
3
+ Bundler.setup
4
+ Bundler.require(:default)
5
+ require 'rake'
6
+ require 'rspec/core/rake_task'
7
+
8
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
9
+ require 'classproxy/version'
10
+
11
+ RSpec::Core::RakeTask.new("spec") do |spec|
12
+ spec.pattern = "spec/**/*_spec.rb"
13
+ end
14
+
15
+ desc "Run watchr"
16
+ task :watchr do
17
+ sh %{bundle exec watchr .watchr}
18
+ end
19
+
20
+ task gem: :build
21
+ task :build do
22
+ system "gem build classproxy.gemspec"
23
+ end
24
+
25
+ task release: :build do
26
+ version = ClassProxy::VERSION
27
+ system "git tag -a v#{version} -m 'Tagging #{version}'"
28
+ system "git push --tags"
29
+ system "gem push classproxy-#{version}"
30
+ system "rm classproxy-#{version}"
31
+ end
32
+
33
+ task default: :spec
@@ -0,0 +1,160 @@
1
+ # This module provides a primitive for data models to establish a main
2
+ # data source (as a cache or for any data conversion/merging operation)
3
+ module ClassProxy
4
+ extend ActiveSupport::Concern
5
+
6
+ class NotFound < StandardError; end
7
+
8
+ module ClassMethods
9
+ # This method establishes how a the cache should be hit, it receives a
10
+ # hash with the data used for the query. If this method doesn't find
11
+ # anything the fallbacks will be triggered
12
+ #
13
+ # @example Use with Active Record
14
+ # model.primary_fetch { |query| where(query).first }
15
+ def primary_fetch(&block); @primary_fetch = block; end
16
+
17
+ # Method used as fallback when the cache misses a hit fetch
18
+ #
19
+ # @example Using Github API for queries
20
+ # class GithubUser
21
+ # include ClassProxy
22
+ #
23
+ # fallback_fetch { |args| Octokit.user(args[:login]) }
24
+ # end
25
+ def fallback_fetch(&block); @fallback_fetch = block; end
26
+
27
+ # Method that post-processes fallbacks, useful to reconvert data
28
+ # from a fallback format into the model format
29
+ #
30
+ # @example Store Github API name in a different attribute
31
+ # class GithubUser
32
+ # include ClassProxy
33
+ #
34
+ # fallback_fetch { |args| Octokit.user(args[:login]) }
35
+ # after_fallback_fetch do |obj|
36
+ # # obj is what `fallback_fetch` returns
37
+ # GithubUser.new(name: obj.name, login: obj.login)
38
+ # end
39
+ #
40
+ # attr_accessor :name, :login
41
+ # end
42
+ def after_fallback_fetch(&block); @after_fallback_method = block; end
43
+
44
+ # Establish attributes to proxy along with an alternative proc of how
45
+ # the attribute should be loaded
46
+ #
47
+ # @example Load method using uppercase
48
+ # class GithubUser
49
+ # include ClassProxy
50
+ #
51
+ # fallback_fetch { |args| Octokit.user(args[:login]) }
52
+ # after_fallback_fetch { |obj| GithubUser.new(name: obj.name, login: obj.login) }
53
+ #
54
+ # attr_accessor :name, :login
55
+ #
56
+ # proxy_methods uppercase_login: lambda { login.upcase }
57
+ # end
58
+ #
59
+ # user = GithubUser.find(login: 'heelhook')
60
+ # user.login # -> 'heelhook'
61
+ # user.uppercase_login # -> 'HEELHOOK'
62
+ def proxy_methods(*methods)
63
+ @_methods ||= {}
64
+
65
+ methods.each do |method|
66
+ if method.is_a? Symbol
67
+ # If given a symbol, store as a method to overwrite and use the default loader
68
+ proxy_method method, @default_proc
69
+ elsif method.is_a? Hash
70
+ # If its a hash it will include methods to overwrite along with
71
+ # custom loaders
72
+ method.each { |method_name, proc| proxy_method method_name, proc }
73
+ end
74
+ end
75
+ end
76
+
77
+ # Method to find a record using a hash as the criteria
78
+ #
79
+ # @example
80
+ # class GithubUser
81
+ # include MongoMapper::Document
82
+ # include ClassProxy
83
+ #
84
+ # primary_fetch { |args| where(args).first or (raise NotFound) }
85
+ # fallback_fetch { |args| Octokit.user(args[:login]) }
86
+ # end
87
+ #
88
+ # GithubUser.fetch(login: 'heelhook') # -> Uses primary_fetch
89
+ # # -> and, if NotFound, fallback_fetch
90
+ #
91
+ # @param [ Hash ] args The criteria to use
92
+ # @options options [ true, false] :skip_fallback Don't use fallback methods
93
+ def fetch(args, options={})
94
+ @primary_fetch.is_a?(Proc) ? @primary_fetch.call(args) : (raise NotFound)
95
+ rescue NotFound
96
+ return nil if options[:skip_fallback]
97
+
98
+ fallback_obj = @fallback_fetch.call(args)
99
+
100
+ # Use the after_fallback_method
101
+ obj = @after_fallback_method.is_a?(Proc) ? @after_fallback_method[fallback_obj] : self.new
102
+
103
+ # Go through the keys of the return object and try to use setters
104
+ if fallback_obj and obj and fallback_obj.respond_to? :keys and fallback_obj.keys.respond_to? :each
105
+ fallback_obj.keys.each do |key|
106
+ next unless obj.respond_to? "#{key}="
107
+ obj.send("#{key}=", fallback_obj.send(key))
108
+ end
109
+ end
110
+
111
+ return obj
112
+ end
113
+
114
+ private
115
+
116
+ def proxy_method(method_name, proc)
117
+ self.class_eval do
118
+ alias_method "no_proxy_#{method_name}".to_sym, method_name
119
+
120
+ define_method(method_name) do |*args|
121
+ # Use the no_proxy one first
122
+ v = self.send("no_proxy_#{method_name}".to_sym, *args)
123
+
124
+ # TODO -- Cache if this also returned nil so the fallback is not used
125
+ # constantly on actual nil values
126
+
127
+ # Since AR calls the getter method when using the setter method
128
+ # to establish the dirty attribute, since the getter is being replaced
129
+ # here and the setter is being used when appropriate, the @mutex_in_call_for
130
+ # prevents endless recursion.
131
+ if v == nil and @mutex_in_call_for != method_name
132
+ @mutex_in_call_for = method_name
133
+ method = "_run_fallback_#{method_name}".to_sym
134
+ v = if self.method(method).arity == 1
135
+ fallback_fetch_method = self.class.instance_variable_get(:@fallback_fetch)
136
+ fallback_obj = fallback_fetch_method.call(self)
137
+ self.send(method, fallback_obj)
138
+ else
139
+ self.send(method)
140
+ end
141
+ self.send("#{method_name}=".to_sym, v) if v and self.respond_to?("#{method_name}=")
142
+ @mutex_in_call_for = nil
143
+ end
144
+
145
+ return v
146
+ end
147
+ end
148
+
149
+ # Now define the fallback that is going to be used
150
+ self.send(:define_method, "_run_fallback_#{method_name}", &proc)
151
+ end
152
+ end
153
+
154
+ @default_proc = Proc.new { "hi" }
155
+
156
+ def self.included(receiver)
157
+ receiver.extend ClassMethods
158
+
159
+ end
160
+ end
@@ -0,0 +1,3 @@
1
+ module ClassProxy
2
+ VERSION = '0.7.0'
3
+ end
data/lib/classproxy.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'classproxy/classproxy'
2
+ require 'classproxy/version'
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: classproxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pablo Fernandez
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: A generic (yet ActiveRecord compliant) class proxy to setup proxy methods
31
+ for your classes
32
+ email:
33
+ - heelhook@littleq.net
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - lib/classproxy/classproxy.rb
39
+ - lib/classproxy/version.rb
40
+ - lib/classproxy.rb
41
+ - LICENSE
42
+ - README.md
43
+ - Rakefile
44
+ homepage:
45
+ licenses: []
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ segments:
57
+ - 0
58
+ hash: 1757276134507409389
59
+ required_rubygems_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubyforge_project:
67
+ rubygems_version: 1.8.24
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: A generic class proxy for your classes
71
+ test_files: []