classproxy 0.7.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/LICENSE +20 -0
- data/README.md +131 -0
- data/Rakefile +33 -0
- data/lib/classproxy/classproxy.rb +160 -0
- data/lib/classproxy/version.rb +3 -0
- data/lib/classproxy.rb +2 -0
- metadata +71 -0
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
|
data/lib/classproxy.rb
ADDED
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: []
|