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 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: []