perry 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +2 -1
- data/lib/perry/adapters/abstract_adapter.rb +189 -64
- data/lib/perry/adapters/bertrpc_adapter.rb +3 -2
- data/lib/perry/adapters/restful_http_adapter.rb +36 -11
- data/lib/perry/association.rb +43 -14
- data/lib/perry/base.rb +33 -17
- data/lib/perry/{cacheable → middlewares/cache_records}/entry.rb +7 -7
- data/lib/perry/middlewares/cache_records/scopes.rb +18 -0
- data/lib/perry/{cacheable → middlewares/cache_records}/store.rb +1 -1
- data/lib/perry/middlewares/cache_records.rb +67 -0
- data/lib/perry/middlewares/model_bridge.rb +66 -0
- data/lib/perry/middlewares.rb +8 -0
- data/lib/perry/persistence/response.rb +75 -0
- data/lib/perry/persistence.rb +40 -2
- data/lib/perry/processors/preload_associations.rb +61 -0
- data/lib/perry/processors.rb +5 -0
- data/lib/perry/relation/finder_methods.rb +8 -4
- data/lib/perry/relation/modifiers.rb +37 -0
- data/lib/perry/relation/query_methods.rb +19 -11
- data/lib/perry/relation.rb +9 -4
- data/lib/perry/version.rb +1 -1
- data/lib/perry.rb +5 -0
- metadata +31 -9
- data/lib/perry/association_preload.rb +0 -69
- data/lib/perry/cacheable.rb +0 -80
data/Rakefile
CHANGED
@@ -24,6 +24,7 @@ spec = Gem::Specification.new do |s|
|
|
24
24
|
|
25
25
|
s.add_dependency("activesupport", [">= 2.3.0"])
|
26
26
|
s.add_dependency("bertrpc", [">= 1.3.0"])
|
27
|
+
s.add_dependency("json", [">=1.4.6"])
|
27
28
|
end
|
28
29
|
|
29
30
|
Rake::GemPackageTask.new(spec) do |pkg|
|
@@ -52,7 +53,7 @@ begin
|
|
52
53
|
t.libs = ['test']
|
53
54
|
t.test_files = FileList["test/**/*_test.rb"]
|
54
55
|
t.verbose = true
|
55
|
-
t.rcov_opts = ['--text-report', "-x #{Gem.path}", '-x /Library/Ruby', '-x /usr/lib/ruby']
|
56
|
+
t.rcov_opts = ['--text-report', "-x #{Gem.path.join(',')}", '-x /Library/Ruby', '-x /usr/lib/ruby']
|
56
57
|
end
|
57
58
|
|
58
59
|
task :default => :coverage
|
@@ -1,92 +1,217 @@
|
|
1
1
|
require 'perry/logger'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
##
|
4
|
+
# = Perry::Adapters::AbstractAdapter
|
5
|
+
#
|
6
|
+
# This is the base class from which all adapters should inherit from. Subclasses should overwrite
|
7
|
+
# one or all of read, write, and/or delete. They should also register themselves with a
|
8
|
+
# unique name using the register_as class method.
|
9
|
+
#
|
10
|
+
# Adapters contain a stack of code that is executed on each request. Here is a diagram of the basic
|
11
|
+
# anatomy of the adaper stack:
|
12
|
+
#
|
13
|
+
# +----------------+
|
14
|
+
# | Perry::Base |
|
15
|
+
# +----------------+
|
16
|
+
# |
|
17
|
+
# +----------------+
|
18
|
+
# | Processors |
|
19
|
+
# +----------------+
|
20
|
+
# |
|
21
|
+
# +----------------+
|
22
|
+
# | ModelBridge |
|
23
|
+
# +----------------+
|
24
|
+
# |
|
25
|
+
# +----------------+
|
26
|
+
# | Middlewares |
|
27
|
+
# +----------------+
|
28
|
+
# |
|
29
|
+
# +----------------+
|
30
|
+
# | Adapter |
|
31
|
+
# +----------------+
|
32
|
+
#
|
33
|
+
# Each request is routed through registred processors, the ModelBridge, and registered middlewares
|
34
|
+
# before reaching the adapter. After the adapter does its operation the return value passes through
|
35
|
+
# each item in the stack allowing stack items to do both custom pre and post processing to every
|
36
|
+
# request.
|
37
|
+
#
|
38
|
+
# == Configuration
|
39
|
+
#
|
40
|
+
# You can configure your adapters using the configure method on Perry::Base
|
41
|
+
#
|
42
|
+
# configure(:read) do |config|
|
43
|
+
# config.adapter_var_1 = :custom_value
|
44
|
+
# config.adapter_var_2 = [:some, :values]
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# This block creates a new configuration context. Each context is merged onto the previous context
|
48
|
+
# allowing subclasses to override configuration set by their parent class.
|
49
|
+
#
|
50
|
+
# == Middlewares
|
51
|
+
#
|
52
|
+
# Middlewares allow you to add custom logic between the model and the adapter. A good example is
|
53
|
+
# caching. A caching middleware could be implemented that intercepted a request to the adapter and
|
54
|
+
# returned the cached value for that request. If the request is a cache miss it could pass the
|
55
|
+
# request on to the adapter, and then cache the result for subsequent calls of the same request.
|
56
|
+
#
|
57
|
+
# This is an example mof a no-op middleware:
|
58
|
+
#
|
59
|
+
# class NoOpMiddleware
|
60
|
+
#
|
61
|
+
# def initialize(adapter, config={})
|
62
|
+
# @adapter = adapter
|
63
|
+
# @config = config
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# def call(options)
|
67
|
+
# @adapter.call(options)
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# Though this doesn't do anything it serves to demonstrate the basic structure of a middleware.
|
73
|
+
# Logic could be added to perform caching, custom querying, or custom result processing.
|
74
|
+
#
|
75
|
+
# Middlewares can also be chained to perform several independent actions. Middlewares are configured
|
76
|
+
# through a custom configuration method:
|
77
|
+
#
|
78
|
+
# configure(:read) do |config|
|
79
|
+
# config.add_middleware(MyMiddleware, :config => 'var', :foo => 'bar')
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# == ModelBridge
|
83
|
+
#
|
84
|
+
# The ModelBridge is simply a middleware that is always installed. It instantiates the records from
|
85
|
+
# the data returned by the adapter. It "bridges" the raw data to the mapped object.
|
86
|
+
#
|
87
|
+
# == Processors
|
88
|
+
#
|
89
|
+
# Much like middlewares, processors allow you to insert logic into the request stack. The
|
90
|
+
# differentiation is that processors are able to manipulate the instantiated objects rather than
|
91
|
+
# just the raw data. Processors have access to the objects immediately before passing the data back
|
92
|
+
# to the model space.
|
93
|
+
#
|
94
|
+
# The interface for a processor is identical to that of a middleware. The return value of the call
|
95
|
+
# to adapter; however, is an array of Perry::Base objects rather than Hashes of attributes.
|
96
|
+
#
|
97
|
+
# Configuration is also very similar to middlewares:
|
98
|
+
#
|
99
|
+
# configure(:read) do |config|
|
100
|
+
# config.add_processor(MyProcessor, :config => 'var', :foo => 'bar')
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
#
|
104
|
+
class Perry::Adapters::AbstractAdapter
|
105
|
+
include Perry::Logger
|
106
|
+
|
107
|
+
attr_accessor :config
|
108
|
+
attr_reader :type
|
109
|
+
@@registered_adapters ||= {}
|
110
|
+
|
111
|
+
# Accepts type as :read, :write, or :delete and a base configuration context for this adapter.
|
112
|
+
def initialize(type, config)
|
113
|
+
@type = type.to_sym
|
114
|
+
@configuration_contexts = config.is_a?(Array) ? config : [config]
|
115
|
+
end
|
6
116
|
|
7
|
-
|
8
|
-
|
9
|
-
|
117
|
+
# Wrapper to the standard init method that will lookup the adapter's class based on its registered
|
118
|
+
# symbol name.
|
119
|
+
def self.create(type, config)
|
120
|
+
klass = @@registered_adapters[type.to_sym]
|
121
|
+
klass.new(type, config)
|
122
|
+
end
|
10
123
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
124
|
+
# Return a new adapter of the same type that adds the given configuration context
|
125
|
+
def extend_adapter(config)
|
126
|
+
config = config.is_a?(Array) ? config : [config]
|
127
|
+
self.class.create(self.type, @configuration_contexts + config)
|
128
|
+
end
|
15
129
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
130
|
+
# return the merged configuration object
|
131
|
+
def config
|
132
|
+
@config ||= build_configuration
|
133
|
+
end
|
20
134
|
|
21
|
-
|
22
|
-
|
23
|
-
|
135
|
+
# runs the adapter in the specified type mode -- designed to work with the middleware stack
|
136
|
+
def call(mode, options)
|
137
|
+
@stack ||= self.stack_items.inject(self.method(mode)) do |below, (above_klass, above_config)|
|
138
|
+
above_klass.new(below, above_config)
|
24
139
|
end
|
25
140
|
|
26
|
-
|
27
|
-
|
28
|
-
|
141
|
+
options[:mode] = mode.to_sym
|
142
|
+
@stack.call(options)
|
143
|
+
end
|
29
144
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
145
|
+
# Return an array of added middlewares
|
146
|
+
def middlewares
|
147
|
+
self.config[:middlewares] || []
|
148
|
+
end
|
34
149
|
|
35
|
-
|
36
|
-
|
150
|
+
# Return an array of added processors
|
151
|
+
def processors
|
152
|
+
self.config[:processors] || []
|
153
|
+
end
|
37
154
|
|
38
|
-
|
39
|
-
|
40
|
-
|
155
|
+
# Abstract read method -- overridden by subclasses
|
156
|
+
def read(options)
|
157
|
+
raise(NotImplementedError,
|
158
|
+
"You must not use the abstract adapter. Implement an adapter that extends the " +
|
159
|
+
"Perry::Adapters::AbstractAdapter class and overrides this method.")
|
160
|
+
end
|
41
161
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
162
|
+
# Abstract write method -- overridden by subclasses
|
163
|
+
def write(object)
|
164
|
+
raise(NotImplementedError,
|
165
|
+
"You must not use the abstract adapter. Implement an adapter that extends the " +
|
166
|
+
"Perry::Adapters::AbstractAdapter class and overrides this method.")
|
167
|
+
end
|
47
168
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
169
|
+
# Abstract delete method -- overridden by subclasses
|
170
|
+
def delete(object)
|
171
|
+
raise(NotImplementedError,
|
172
|
+
"You must not use the abstract adapter. Implement an adapter that extends the " +
|
173
|
+
"Perry::Adapters::AbstractAdapter class and overrides this method.")
|
174
|
+
end
|
53
175
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
176
|
+
# New adapters should register themselves using this method
|
177
|
+
def self.register_as(name)
|
178
|
+
@@registered_adapters[name.to_sym] = self
|
179
|
+
end
|
59
180
|
|
60
|
-
|
61
|
-
@@registered_adapters[name.to_sym] = self
|
62
|
-
end
|
181
|
+
protected
|
63
182
|
|
64
|
-
|
183
|
+
def stack_items
|
184
|
+
(processors + [Perry::Middlewares::ModelBridge] + middlewares).reverse
|
185
|
+
end
|
65
186
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
187
|
+
# TRP: Run each configure block in order of class hierarchy / definition and merge the results.
|
188
|
+
def build_configuration
|
189
|
+
@configuration_contexts.inject({}) do |sum, config|
|
190
|
+
if config.is_a?(Hash)
|
191
|
+
sum.merge(config)
|
192
|
+
else
|
193
|
+
AdapterConfig.new(sum).tap { |ac| config.call(ac) }.marshal_dump
|
74
194
|
end
|
75
195
|
end
|
196
|
+
end
|
76
197
|
|
77
|
-
|
198
|
+
class AdapterConfig < OpenStruct
|
78
199
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
200
|
+
def add_middleware(klass, config={})
|
201
|
+
self.middlewares ||= []
|
202
|
+
self.middlewares << [klass, config]
|
203
|
+
end
|
83
204
|
|
84
|
-
|
85
|
-
|
86
|
-
|
205
|
+
def add_processor(klass, config={})
|
206
|
+
self.processors ||= []
|
207
|
+
self.processors << [klass, config]
|
208
|
+
end
|
87
209
|
|
210
|
+
def to_hash
|
211
|
+
marshal_dump
|
88
212
|
end
|
89
213
|
|
90
214
|
end
|
215
|
+
|
91
216
|
end
|
92
217
|
|
@@ -12,9 +12,10 @@ module Perry::Adapters
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def read(options)
|
15
|
-
|
15
|
+
query = options[:relation].to_hash
|
16
|
+
log(query, "RPC #{config[:service]}") {
|
16
17
|
self.service.call.send(self.namespace).send(self.service_name,
|
17
|
-
|
18
|
+
query.merge(config[:default_options] || {}))
|
18
19
|
}
|
19
20
|
end
|
20
21
|
|
@@ -5,20 +5,22 @@ module Perry::Adapters
|
|
5
5
|
class RestfulHTTPAdapter < Perry::Adapters::AbstractAdapter
|
6
6
|
register_as :restful_http
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@configuration_contexts << { :primary_key => :id }
|
8
|
+
class KeyError < Perry::PerryError
|
9
|
+
def message
|
10
|
+
'(restful_http_adapter) request not sent because primary key value was nil'
|
11
|
+
end
|
13
12
|
end
|
14
13
|
|
15
|
-
|
14
|
+
attr_reader :last_response
|
15
|
+
|
16
|
+
def write(options)
|
17
|
+
object = options[:object]
|
16
18
|
params = build_params_from_attributes(object)
|
17
19
|
object.new_record? ? post_http(object, params) : put_http(object, params)
|
18
20
|
end
|
19
21
|
|
20
|
-
def delete(
|
21
|
-
delete_http(object)
|
22
|
+
def delete(options)
|
23
|
+
delete_http(options[:object])
|
22
24
|
end
|
23
25
|
|
24
26
|
protected
|
@@ -55,7 +57,19 @@ module Perry::Adapters
|
|
55
57
|
self.log(params, "#{method.to_s.upcase} #{req_uri}") do
|
56
58
|
@last_response = Net::HTTP.new(req_uri.host, req_uri.port).start { |http| http.request(request) }
|
57
59
|
end
|
58
|
-
|
60
|
+
|
61
|
+
Perry::Persistence::Response.new.tap do |response|
|
62
|
+
response.status = @last_response.code.to_i if @last_response
|
63
|
+
response.success = parse_response_code(@last_response)
|
64
|
+
response.meta = http_headers(@last_response).to_hash if @last_response
|
65
|
+
response.raw = @last_response.body if @last_response
|
66
|
+
response.raw_format = config[:format] ? config[:format].gsub(/\W/, '').to_sym : nil
|
67
|
+
end
|
68
|
+
rescue KeyError => ex
|
69
|
+
Perry::Persistence::Response.new.tap do |response|
|
70
|
+
response.success = false
|
71
|
+
response.parsed = { :base => ex.message }
|
72
|
+
end
|
59
73
|
end
|
60
74
|
|
61
75
|
def parse_response_code(response)
|
@@ -69,7 +83,8 @@ module Perry::Adapters
|
|
69
83
|
|
70
84
|
def build_params_from_attributes(object)
|
71
85
|
if self.config[:post_body_wrapper]
|
72
|
-
|
86
|
+
defaults = self.config[:default_options]
|
87
|
+
params = defaults ? defaults.dup : {}
|
73
88
|
params.merge!(object.write_options[:default_options]) if object.write_options.is_a?(Hash) && object.write_options[:default_options].is_a?(Hash)
|
74
89
|
|
75
90
|
object.attributes.each do |attribute, value|
|
@@ -84,7 +99,11 @@ module Perry::Adapters
|
|
84
99
|
|
85
100
|
def build_uri(object, method)
|
86
101
|
url = [self.config[:host].gsub(%r{/$}, ''), self.config[:service]]
|
87
|
-
|
102
|
+
unless object.new_record?
|
103
|
+
primary_key = self.config[:primary_key] || object.primary_key
|
104
|
+
pk_value = object.send(primary_key) or raise KeyError
|
105
|
+
url << pk_value
|
106
|
+
end
|
88
107
|
uri = URI.parse "#{url.join('/')}#{self.config[:format]}"
|
89
108
|
|
90
109
|
# TRP: method DELETE has no POST body so we have to append any default options onto the query string
|
@@ -95,5 +114,11 @@ module Perry::Adapters
|
|
95
114
|
uri
|
96
115
|
end
|
97
116
|
|
117
|
+
def http_headers(response)
|
118
|
+
response.to_hash.inject({}) do |clean_headers, (key, values)|
|
119
|
+
clean_headers.merge(key => values.length > 1 ? values : values.first)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
98
123
|
end
|
99
124
|
end
|
data/lib/perry/association.rb
CHANGED
@@ -43,8 +43,14 @@ module Perry::Association
|
|
43
43
|
raise NotImplementedError, "You must define collection? in subclasses."
|
44
44
|
end
|
45
45
|
|
46
|
-
def primary_key
|
47
|
-
options[:primary_key]
|
46
|
+
def primary_key(object=nil)
|
47
|
+
if options[:primary_key]
|
48
|
+
options[:primary_key]
|
49
|
+
elsif is_a?(BelongsTo)
|
50
|
+
target_klass(object).primary_key
|
51
|
+
elsif is_a?(Has)
|
52
|
+
source_klass.primary_key
|
53
|
+
end
|
48
54
|
end
|
49
55
|
|
50
56
|
def foreign_key
|
@@ -52,10 +58,18 @@ module Perry::Association
|
|
52
58
|
end
|
53
59
|
|
54
60
|
def target_klass(object=nil)
|
61
|
+
eager_loading = object.is_a?(Array)
|
55
62
|
if options[:polymorphic] && object
|
56
63
|
poly_type = object.is_a?(Perry::Base) ? object.send("#{id}_type") : object
|
57
64
|
end
|
58
65
|
|
66
|
+
# This is an eager loading attempt
|
67
|
+
if eager_loading && !eager_loadable?
|
68
|
+
raise(Perry::AssociationPreloadNotSupported,
|
69
|
+
"This association cannot be eager loaded. It has config with procs or it is a " +
|
70
|
+
"polymorphic belongs_to association.")
|
71
|
+
end
|
72
|
+
|
59
73
|
klass = if poly_type
|
60
74
|
type_string = [
|
61
75
|
options[:polymorphic_namespace],
|
@@ -92,9 +106,10 @@ module Perry::Association
|
|
92
106
|
# TRP: Only eager loadable if association query does not depend on instance
|
93
107
|
# data
|
94
108
|
def eager_loadable?
|
95
|
-
Perry::Relation::FINDER_OPTIONS.inject(
|
96
|
-
condition
|
109
|
+
dynamic_config = Perry::Relation::FINDER_OPTIONS.inject(false) do |condition, key|
|
110
|
+
condition || options[key].respond_to?(:call)
|
97
111
|
end
|
112
|
+
!dynamic_config && !(self.polymorphic? && self.is_a?(BelongsTo))
|
98
113
|
end
|
99
114
|
|
100
115
|
protected
|
@@ -113,7 +128,8 @@ module Perry::Association
|
|
113
128
|
|
114
129
|
# TRP: Make sure the value looks like a variable syntaxtually
|
115
130
|
def sanitize_type_attribute(string)
|
116
|
-
string
|
131
|
+
string =~ /^[a-zA-Z]\w*/
|
132
|
+
Regexp.last_match.to_s
|
117
133
|
end
|
118
134
|
|
119
135
|
end
|
@@ -154,8 +170,15 @@ module Perry::Association
|
|
154
170
|
# where(:id => source[:foo_id])
|
155
171
|
#
|
156
172
|
def scope(object)
|
157
|
-
if object
|
158
|
-
|
173
|
+
if object.is_a? Array
|
174
|
+
keys = object.collect(&self.foreign_key.to_sym)
|
175
|
+
keys = nil if keys.empty?
|
176
|
+
else
|
177
|
+
keys = object[self.foreign_key]
|
178
|
+
end
|
179
|
+
if keys
|
180
|
+
scope = base_scope(object)
|
181
|
+
scope.where(self.primary_key(object) => keys)
|
159
182
|
end
|
160
183
|
end
|
161
184
|
|
@@ -192,13 +215,19 @@ module Perry::Association
|
|
192
215
|
# where(:parent_id => source[:id], :parent_type => source.class)
|
193
216
|
#
|
194
217
|
def scope(object)
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
218
|
+
if object.is_a? Array
|
219
|
+
keys = object.collect(&self.primary_key.to_sym)
|
220
|
+
keys = nil if keys.empty?
|
221
|
+
klass = object.first.class
|
222
|
+
else
|
223
|
+
keys = object[self.primary_key]
|
224
|
+
klass = object.class
|
225
|
+
end
|
226
|
+
if keys
|
227
|
+
scope = base_scope(object).where(self.foreign_key => keys)
|
228
|
+
scope = scope.where(polymorphic_type => Perry::Base.base_class_name(klass)) if polymorphic?
|
229
|
+
scope
|
200
230
|
end
|
201
|
-
s
|
202
231
|
end
|
203
232
|
|
204
233
|
def polymorphic_type
|
@@ -288,7 +317,7 @@ module Perry::Association
|
|
288
317
|
else
|
289
318
|
relation = relation.where(
|
290
319
|
proc do
|
291
|
-
{ target_association.primary_key => proxy_ids.call }
|
320
|
+
{ target_association.primary_key(options[:source_type]) => proxy_ids.call }
|
292
321
|
end
|
293
322
|
)
|
294
323
|
end
|
data/lib/perry/base.rb
CHANGED
@@ -2,28 +2,29 @@
|
|
2
2
|
require 'perry/errors'
|
3
3
|
require 'perry/associations/contains'
|
4
4
|
require 'perry/associations/external'
|
5
|
-
require 'perry/association_preload'
|
6
|
-
require 'perry/cacheable'
|
7
5
|
require 'perry/serialization'
|
8
6
|
require 'perry/relation'
|
9
7
|
require 'perry/scopes'
|
10
8
|
require 'perry/adapters'
|
11
|
-
|
9
|
+
require 'perry/middlewares'
|
10
|
+
require 'perry/processors'
|
12
11
|
|
13
12
|
class Perry::Base
|
14
13
|
include Perry::Associations::Contains
|
15
14
|
include Perry::Associations::External
|
16
|
-
include Perry::AssociationPreload
|
17
15
|
include Perry::Serialization
|
18
16
|
include Perry::Scopes
|
19
17
|
|
20
|
-
|
18
|
+
DEFAULT_PRIMARY_KEY = :id
|
19
|
+
|
20
|
+
attr_accessor :attributes, :new_record, :saved, :read_options, :write_options
|
21
21
|
alias :new_record? :new_record
|
22
|
+
alias :saved? :saved
|
23
|
+
alias :persisted? :saved?
|
22
24
|
|
23
|
-
class_inheritable_accessor :read_adapter, :write_adapter,
|
25
|
+
class_inheritable_accessor :read_adapter, :write_adapter,
|
24
26
|
:defined_attributes, :scoped_methods, :defined_associations
|
25
27
|
|
26
|
-
self.cacheable = false
|
27
28
|
self.defined_associations = {}
|
28
29
|
self.defined_attributes = []
|
29
30
|
|
@@ -36,6 +37,14 @@ class Perry::Base
|
|
36
37
|
@attributes[attribute.to_s]
|
37
38
|
end
|
38
39
|
|
40
|
+
def errors
|
41
|
+
@errors ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def primary_key
|
45
|
+
self.class.primary_key
|
46
|
+
end
|
47
|
+
|
39
48
|
protected
|
40
49
|
|
41
50
|
# TRP: Common interface for setting attributes to keep things consistent; if
|
@@ -61,7 +70,22 @@ class Perry::Base
|
|
61
70
|
|
62
71
|
delegate :find, :first, :all, :search, :apply_finder_options, :to => :scoped
|
63
72
|
delegate :select, :group, :order, :joins, :where, :having, :limit, :offset,
|
64
|
-
:from, :
|
73
|
+
:from, :includes, :to => :scoped
|
74
|
+
delegate :modifiers, :to => :scoped
|
75
|
+
|
76
|
+
def primary_key
|
77
|
+
@primary_key || DEFAULT_PRIMARY_KEY
|
78
|
+
end
|
79
|
+
|
80
|
+
# Allows you to specify an attribute other than :id to use as your models
|
81
|
+
# primary key.
|
82
|
+
#
|
83
|
+
def set_primary_key(attribute)
|
84
|
+
unless defined_attributes.include?(attribute.to_s)
|
85
|
+
raise Perry::PerryError.new("cannot set primary key to non-existent attribute")
|
86
|
+
end
|
87
|
+
@primary_key = attribute.to_sym
|
88
|
+
end
|
65
89
|
|
66
90
|
def new_from_data_store(hash)
|
67
91
|
if hash.nil?
|
@@ -96,8 +120,7 @@ class Perry::Base
|
|
96
120
|
protected
|
97
121
|
|
98
122
|
def fetch_records(relation)
|
99
|
-
|
100
|
-
self.read_adapter.read(options).collect { |hash| self.new_from_data_store(hash) }.compact.tap { |result| eager_load_associations(result, relation) }
|
123
|
+
self.read_adapter.call(:read, :relation => relation).compact
|
101
124
|
end
|
102
125
|
|
103
126
|
def read_with(adapter_type)
|
@@ -136,13 +159,6 @@ class Perry::Base
|
|
136
159
|
end
|
137
160
|
end
|
138
161
|
|
139
|
-
def configure_cacheable(options={})
|
140
|
-
unless cacheable
|
141
|
-
self.send(:include, Perry::Cacheable)
|
142
|
-
self.enable_caching(options)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
162
|
# TRP: Used to declare attributes -- only attributes that are declared will be available
|
147
163
|
def attributes(*attributes)
|
148
164
|
return self.defined_attributes if attributes.empty?
|
@@ -1,18 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
class Perry::Middlewares::CacheRecords
|
2
|
+
|
3
3
|
class Entry
|
4
|
-
|
4
|
+
|
5
5
|
attr_accessor :value, :expire_at
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(value, expire_at)
|
8
8
|
self.value = value
|
9
9
|
self.expire_at = expire_at
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def expired?
|
13
13
|
Time.now > self.expire_at rescue true
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Perry::Middlewares::CacheRecords::Scopes
|
2
|
+
def self.included(model)
|
3
|
+
model.class_eval do
|
4
|
+
|
5
|
+
# Using the :fresh scope will skip the cache and execute query regardless of
|
6
|
+
# whether a cached result is available or not.
|
7
|
+
scope :fresh, (lambda do |*args|
|
8
|
+
val = args.first.nil? ? true : args.first
|
9
|
+
modifiers(:fresh => val)
|
10
|
+
end)
|
11
|
+
|
12
|
+
# Using the :reset_cache scope in a query will delete all entries from the
|
13
|
+
# cache store before running the query. Whenever possible, you should use
|
14
|
+
# the :fresh scope instead of :reset_cache.
|
15
|
+
scope :reset_cache, modifiers(:reset_cache => true)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|