merb 0.2.0 → 0.3.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/README +105 -95
- data/Rakefile +4 -5
- data/examples/skeleton.tar +0 -0
- data/examples/skeleton/dist/conf/router.rb +5 -2
- data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +0 -1
- data/lib/merb.rb +10 -3
- data/lib/merb/core_ext.rb +3 -1
- data/lib/merb/core_ext/merb_class.rb +85 -51
- data/lib/merb/core_ext/merb_inflections.rb +112 -0
- data/lib/merb/core_ext/merb_inflector.rb +275 -0
- data/lib/merb/core_ext/merb_object.rb +0 -55
- data/lib/merb/core_ext/merb_string.rb +1 -13
- data/lib/merb/merb_controller.rb +26 -20
- data/lib/merb/merb_dispatcher.rb +10 -3
- data/lib/merb/merb_exceptions.rb +6 -1
- data/lib/merb/merb_handler.rb +1 -1
- data/lib/merb/merb_mailer.rb +3 -2
- data/lib/merb/merb_request.rb +56 -0
- data/lib/merb/merb_router.rb +24 -38
- data/lib/merb/mixins/controller_mixin.rb +34 -5
- data/lib/merb/mixins/render_mixin.rb +7 -7
- data/lib/merb/mixins/responder_mixin.rb +17 -10
- data/lib/merb/mixins/view_context_mixin.rb +13 -2
- data/lib/merb/session/merb_ar_session.rb +4 -4
- data/lib/merb/session/merb_memory_session.rb +4 -4
- data/lib/merb/template/erubis.rb +1 -1
- data/lib/merb/template/haml.rb +20 -14
- data/lib/tasks/merb.rake +11 -2
- metadata +4 -2
data/README
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
Copyright (c) 2006 Ezra Zygmuntowicz
|
3
3
|
|
4
|
-
Merb.
|
4
|
+
Merb.
|
5
5
|
|
6
|
-
|
7
|
-
for simple dynamic pages.
|
6
|
+
Lightweight MVC Ruby app server. For high performance dynamic pages.
|
8
7
|
|
9
8
|
** Dependencies **
|
10
9
|
mongrel
|
@@ -33,6 +32,7 @@ params into params and the cookies into cookies when it instantiates your
|
|
33
32
|
controller class.
|
34
33
|
|
35
34
|
*RouteMatcher and route compiler*
|
35
|
+
|
36
36
|
Reads your route definition and compiles
|
37
37
|
a method on the fly that will match the request path against each route and do the right thing.
|
38
38
|
|
@@ -42,93 +42,52 @@ note the r.resource :posts macro. That makes it possible to use a restfull crud
|
|
42
42
|
|
43
43
|
Merb::RouteMatcher.prepare do |r|
|
44
44
|
r.resources :posts
|
45
|
-
r.
|
45
|
+
r.default_routes
|
46
46
|
r.add '/', :controller => 'files', :action => 'index'
|
47
47
|
end
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
lambda{|path|
|
52
|
-
case path
|
53
|
-
when Regexp.new('/posts/([^/,?]+)[;]edit')
|
54
|
-
@sections[:id] = $1
|
55
|
-
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"edit"}}.update(@sections)
|
56
|
-
when Regexp.new('/posts/new[;]([^/,?]+)')
|
57
|
-
@sections[:action] = $1
|
58
|
-
return {:rest=>true, :controller=>"posts", :allowed=>{:post=>"new", :get=>"new", :delete=>"new", :put=>"new"}}.update(@sections)
|
59
|
-
when Regexp.new('/posts/new')
|
60
|
-
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"new"}}.update(@sections)
|
61
|
-
when Regexp.new('/posts/([^/,?]+)\.([^/,?]+)')
|
62
|
-
@sections[:id] = $1
|
63
|
-
@sections[:format] = $2
|
64
|
-
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"show", :delete=>"destroy", :put=>"update"}}.update(@sections)
|
65
|
-
when Regexp.new('/posts\.([^/,?]+)')
|
66
|
-
@sections[:format] = $1
|
67
|
-
return {:rest=>true, :controller=>"posts", :allowed=>{:post=>"create", :get=>"index"}}.update(@sections)
|
68
|
-
when Regexp.new('/posts/([^/,?]+)')
|
69
|
-
@sections[:id] = $1
|
70
|
-
return {:rest=>true, :controller=>"posts", :allowed=>{:get=>"show", :delete=>"destroy", :put=>"update"}}.update(@sections)
|
71
|
-
when Regexp.new('/posts/?')
|
72
|
-
return {:rest=>true, :controller=>"posts", :allowed=>{:post=>"create", :get=>"index"}}.update(@sections)
|
73
|
-
when /\A\/([^\/;.,?]+)(?:\/?\Z|\/([^\/;.,?]+)\/?)(?:\/?\Z|\/([^\/;.,?]+)\/?)\Z/
|
74
|
-
@sections[:controller] = $1
|
75
|
-
@sections[:action] = $2 || 'index'
|
76
|
-
@sections[:id] = $3 if $3
|
77
|
-
return @sections
|
78
|
-
when Regexp.new('/')
|
79
|
-
return {:controller=>"files", :action=>"index"}.update(@sections)
|
80
|
-
else
|
81
|
-
return {:controller=>'Noroutefound', :action=>'noroute'}
|
82
|
-
end
|
83
|
-
}
|
49
|
+
The r.default_routes routes adds the standard routes:
|
84
50
|
|
51
|
+
/controller/action/id.xml
|
52
|
+
/controller/action/id
|
53
|
+
/controller/action.xml
|
54
|
+
/controller/action
|
55
|
+
/controller.xml # index action
|
56
|
+
/controller # index action
|
85
57
|
|
86
|
-
*Restful Controller*
|
87
58
|
|
88
|
-
restful controllers use a different dispatch system based on the request method verbs.
|
89
59
|
|
90
|
-
|
91
|
-
|
92
|
-
# GET /posts.xml
|
93
|
-
def index
|
94
|
-
end
|
95
|
-
|
96
|
-
# GET /posts/1
|
97
|
-
# GET /posts/1.xml
|
98
|
-
def show
|
99
|
-
end
|
100
|
-
|
101
|
-
# GET /posts/new
|
102
|
-
def new
|
103
|
-
end
|
104
|
-
|
105
|
-
# GET /posts/1;edit
|
106
|
-
def edit
|
107
|
-
end
|
108
|
-
|
109
|
-
# POST /posts
|
110
|
-
# POST /posts.xml
|
111
|
-
def create
|
112
|
-
end
|
113
|
-
|
114
|
-
# PUT /posts/1
|
115
|
-
# PUT /posts/1.xml
|
116
|
-
def update
|
117
|
-
end
|
118
|
-
|
119
|
-
# DELETE /posts/1
|
120
|
-
# DELETE /posts/1.xml
|
121
|
-
def destroy
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
*Simple Controllers*
|
127
|
-
classes with built in render method and template handling
|
60
|
+
*Controllers*
|
61
|
+
Classes with built in render method and template handling
|
128
62
|
with instance vars available in the views automatically. Merb also supports
|
129
63
|
layouts. It will look for a layout named after your controller class first and
|
130
64
|
then fall back to application.herb if no layout exists named after your controller.
|
131
|
-
You can use
|
65
|
+
You can use render :layout => :none.
|
66
|
+
|
67
|
+
Merb does not automatically render for you in your controller actions, you have
|
68
|
+
to call render yourself. I consider this a big advantage over the way rails does
|
69
|
+
it for a few reasons. The main reason is that in rails you can only render once
|
70
|
+
per action, so it knows if you haven’t rendered it shoudl auto render. Merb on
|
71
|
+
the other hand, returns to the browser whatever the return value of your
|
72
|
+
controller’s action method is. This opens up more possibilities imho because
|
73
|
+
now you can return any string from your action and that will be sent down
|
74
|
+
the pipe. So Merb’s render method just returns a string and needs to be the
|
75
|
+
last thing you call in your action. You can render multiple times and capture
|
76
|
+
the results into @ivars and then render a master template with many embeded
|
77
|
+
templates. Also if you return a handle on a File or IO object from your action
|
78
|
+
then merb will hand that over to mongrel to be streamed out to the client. And
|
79
|
+
if you return a Proc object from your action, it will be called and the
|
80
|
+
return value sent to the client.
|
81
|
+
|
82
|
+
That last point has some cool connotations if you think about it. Merb does
|
83
|
+
have a mutex lock around the call to your controller’s action anywhere that
|
84
|
+
you can call AR objects. Merb’s lock is way smaller then rails giant lock
|
85
|
+
though and allows for many more concurrent requests to be handled by one
|
86
|
+
process. By returning a Proc object from your action, you allow merb to
|
87
|
+
release the lock and the proc is called in multi threaded way. This allows
|
88
|
+
for all kinds of cool streaming and ‘futures’ where you return the proc and
|
89
|
+
release the mutex. It’s basically like handing over the proc to mongrel and
|
90
|
+
mongrel handles calling it in a thread safe manner.
|
132
91
|
|
133
92
|
|
134
93
|
class Test < Merb::Controller
|
@@ -141,6 +100,7 @@ end
|
|
141
100
|
|
142
101
|
You can also render partials like so:
|
143
102
|
<%= partial(:comments) %>
|
103
|
+
|
144
104
|
This assumes a _comments.rhtml file in the same view dir as the current
|
145
105
|
controller/view
|
146
106
|
|
@@ -179,14 +139,56 @@ $('comments').update('<%=js partial(:posts) %>');
|
|
179
139
|
</ul>
|
180
140
|
|
181
141
|
|
142
|
+
*Restful Controllers*
|
143
|
+
|
144
|
+
restful controllers use a different dispatch system based on the request method verbs. Merb
|
145
|
+
supports multi return values based on the accept header via respond_to
|
146
|
+
|
147
|
+
class Posts < Merb::Controller
|
148
|
+
# GET /posts
|
149
|
+
# GET /posts.xml
|
150
|
+
def index
|
151
|
+
@posts = Post.find :all
|
152
|
+
respond_to {|format|
|
153
|
+
format.html { render }
|
154
|
+
format.js { render :js => 'index' }
|
155
|
+
format.xml { render :xml => @posts.to_xml }
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
# GET /posts/1
|
160
|
+
# GET /posts/1.xml
|
161
|
+
def show
|
162
|
+
end
|
163
|
+
|
164
|
+
# GET /posts/new
|
165
|
+
def new
|
166
|
+
end
|
167
|
+
|
168
|
+
# GET /posts/1;edit
|
169
|
+
def edit
|
170
|
+
end
|
171
|
+
|
172
|
+
# POST /posts
|
173
|
+
# POST /posts.xml
|
174
|
+
def create
|
175
|
+
end
|
176
|
+
|
177
|
+
# PUT /posts/1
|
178
|
+
# PUT /posts/1.xml
|
179
|
+
def update
|
180
|
+
end
|
181
|
+
|
182
|
+
# DELETE /posts/1
|
183
|
+
# DELETE /posts/1.xml
|
184
|
+
def destroy
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
182
189
|
*Controllers have powerful before and after filters*
|
183
190
|
|
184
|
-
Use the before method in your controllers. before accepts either a symbol
|
185
|
-
or a Proc/lambda object. If you give it a symbol it will call a method with
|
186
|
-
the same name as the symbol. If you give it a proc that takes one argument
|
187
|
-
it will call the proc with the current controller as that argument. You can
|
188
|
-
use :only and :exclude as options to your filters to exclude or include actionsfrom certain filters. :only and :exclude take :symbols or [:sym, :sam]
|
189
|
-
array of symbols.
|
191
|
+
Use the before method in your controllers. before accepts either a symbol, string or a Proc/lambda object. If you give it a symbol it will call a method with the same name as the symbol. If you give it a proc that takes one argument it will call the proc with the current controller as that argument. You can use :only and :exclude as options to your filters to exclude or include actionsfrom certain filters. :only and :exclude take :symbols or [:sym, :sam] array of symbols.
|
190
192
|
|
191
193
|
class Foo < Merb::Controller
|
192
194
|
|
@@ -224,21 +226,23 @@ throw :halt, Proc.new{ |c| c.redirect "/foo" }
|
|
224
226
|
|
225
227
|
# halts the chain and returns whatever is in the string
|
226
228
|
|
227
|
-
throw :halt, "<h1>You don't have permissions
|
229
|
+
throw :halt, "<h1>You don't have permissions dude!</h1>"
|
228
230
|
|
229
231
|
or even render templates:
|
230
232
|
|
231
233
|
throw :halt, render 'foo'
|
232
234
|
throw :halt, partial 'foo'
|
233
235
|
|
234
|
-
After filters
|
236
|
+
After filters accept a symbol, string or Proc and call that proc with the controller:
|
235
237
|
|
236
238
|
after Proc.new {|c| Tidy.new(c.body) }, :only => :index
|
237
239
|
|
238
|
-
Sessions are available when you start merb with the sql_session set to true or the
|
239
|
-
|
240
|
-
Helpers: dist/app/helpers/global_helper.rb will be available to all of your views. Helpers named afdter your controller plus _helper.rb will be included in the views for that controller only.
|
240
|
+
Sessions are available when you start merb with the sql_session set to true or the
|
241
|
+
memory_session set to true. See generated app for migration too add session table.
|
241
242
|
|
243
|
+
Helpers: dist/app/helpers/global_helper.rb will be available to all of your views.
|
244
|
+
Helpers named after your controller plus _helper.rb will be included in the views
|
245
|
+
for that controller only.
|
242
246
|
|
243
247
|
*The merb server*
|
244
248
|
right now you add your routes in
|
@@ -264,7 +268,7 @@ $merb -i
|
|
264
268
|
*File uploads*
|
265
269
|
This is one of the things that Merb was written for. Rails doesn't allow
|
266
270
|
multiple concurrent file uploads at once without blocking an entire rails backend for each file upload. Merb allows multiple file uploads at once.
|
267
|
-
|
271
|
+
When a file is uploaded with Merb, it gets put in a Tempfile. So
|
268
272
|
you just want to copy it to the right place on the filesystem.
|
269
273
|
|
270
274
|
def upload
|
@@ -275,6 +279,13 @@ def upload
|
|
275
279
|
render
|
276
280
|
end
|
277
281
|
|
282
|
+
A file upload will have a hash of params like this:
|
283
|
+
{
|
284
|
+
:filename => File.basename(filename),
|
285
|
+
:content_type => content_type,
|
286
|
+
:tempfile => <Tempfile>,
|
287
|
+
:size => File.size(body)
|
288
|
+
}
|
278
289
|
|
279
290
|
*Merb app layout*
|
280
291
|
|
@@ -303,5 +314,4 @@ merb_app:
|
|
303
314
|
lib
|
304
315
|
public
|
305
316
|
plugins
|
306
|
-
schema
|
307
|
-
</pre>
|
317
|
+
schema
|
data/Rakefile
CHANGED
@@ -15,7 +15,7 @@ include FileUtils
|
|
15
15
|
|
16
16
|
|
17
17
|
NAME = "merb"
|
18
|
-
VERS = "0.
|
18
|
+
VERS = "0.3.0"
|
19
19
|
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
20
20
|
|
21
21
|
setup_clean [ "pkg", "lib/*.bundle", "*.gem",
|
@@ -91,8 +91,8 @@ end
|
|
91
91
|
desc "rdoc to rubyforge"
|
92
92
|
task :doc_rforge do
|
93
93
|
sh %{rake doc}
|
94
|
-
sh %{sudo chmod -R 755 doc
|
95
|
-
sh %{scp -r -p doc
|
94
|
+
sh %{sudo chmod -R 755 doc}
|
95
|
+
sh %{scp -r -p doc/* ezmobius@rubyforge.org:/var/www/gforge-projects/merb}
|
96
96
|
end
|
97
97
|
|
98
98
|
|
@@ -132,8 +132,7 @@ end
|
|
132
132
|
|
133
133
|
desc 'Run all tests, specs and finish with rcov'
|
134
134
|
task :aok do
|
135
|
-
sh %{rake
|
136
|
-
sh %{rake spec}
|
135
|
+
sh %{rake specs}
|
137
136
|
end
|
138
137
|
|
139
138
|
desc "Run all specs"
|
data/examples/skeleton.tar
CHANGED
Binary file
|
@@ -11,9 +11,12 @@
|
|
11
11
|
|
12
12
|
puts "Compiling routes.."
|
13
13
|
Merb::RouteMatcher.prepare do |r|
|
14
|
+
# restfull routes
|
15
|
+
# r.resources :posts
|
16
|
+
|
14
17
|
# default route, usually you don't want to change this
|
15
|
-
r.
|
18
|
+
r.default_routes
|
16
19
|
|
17
20
|
# change this for your home page to be avaiable at /
|
18
|
-
r.add '/', :controller => '
|
21
|
+
#r.add '/', :controller => 'whatever', :action =>'index'
|
19
22
|
end
|
data/lib/merb.rb
CHANGED
@@ -12,7 +12,7 @@ end
|
|
12
12
|
|
13
13
|
|
14
14
|
module Merb
|
15
|
-
VERSION='0.
|
15
|
+
VERSION='0.3.0' unless defined?VERSION
|
16
16
|
class Server
|
17
17
|
class << self
|
18
18
|
def config
|
@@ -69,10 +69,17 @@ MERB_LOGGER.level = case (Merb::Server.log_level.downcase rescue '')
|
|
69
69
|
Logger::INFO
|
70
70
|
end
|
71
71
|
|
72
|
-
|
72
|
+
module Mongrel::Const
|
73
|
+
HTTP_COOKIE = 'HTTP_COOKIE'.freeze
|
74
|
+
QUERY_STRING = 'QUERY_STRING'.freeze
|
75
|
+
CONTENT_TYPE_TEXT_HTML_HASH = {'Content-Type' =>'text/html'}
|
76
|
+
APPLICATION_JSON = 'application/json'.freeze
|
77
|
+
TEXT_JSON = 'text/x-json'.freeze
|
78
|
+
UPCASE_CONTENT_TYPE = 'CONTENT_TYPE'.freeze
|
79
|
+
end
|
73
80
|
|
74
81
|
lib = File.join(__DIR__, 'merb')
|
75
|
-
Dir.entries(lib).sort.each {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
|
82
|
+
Dir.entries(lib).sort.select{|f| f !~ /merb\/session\// }.each {|fn| require File.join(lib, fn) if fn =~ /\.rb$/}
|
76
83
|
|
77
84
|
require File.join(__DIR__, 'merb/vendor/paginator/paginator')
|
78
85
|
|
data/lib/merb/core_ext.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
corelib = __DIR__+'/merb/core_ext'
|
2
2
|
|
3
|
-
%w[
|
3
|
+
%w[ merb_inflector
|
4
|
+
merb_class
|
4
5
|
merb_kernel
|
5
6
|
merb_object
|
6
7
|
merb_enumerable
|
@@ -9,4 +10,5 @@ corelib = __DIR__+'/merb/core_ext'
|
|
9
10
|
merb_hash
|
10
11
|
merb_numeric
|
11
12
|
merb_symbol
|
13
|
+
|
12
14
|
].each {|fn| require File.join(corelib, fn)}
|
@@ -1,106 +1,140 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
syms.flatten.each do |sym|
|
5
|
-
class_eval(<<-EOS, __FILE__, __LINE__)
|
6
|
-
unless defined? @@#{sym}
|
7
|
-
@@#{sym} = nil
|
8
|
-
end
|
1
|
+
# Retain for backward compatibility. Methods are now included in Class.
|
2
|
+
module ClassInheritableAttributes # :nodoc:
|
3
|
+
end
|
9
4
|
|
5
|
+
# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
|
6
|
+
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
|
7
|
+
# to, for example, an array without those additions being shared with either their parent, siblings, or
|
8
|
+
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
|
9
|
+
class Class # :nodoc:
|
10
|
+
def class_inheritable_reader(*syms)
|
11
|
+
syms.each do |sym|
|
12
|
+
next if sym.is_a?(Hash)
|
13
|
+
class_eval <<-EOS
|
10
14
|
def self.#{sym}
|
11
|
-
|
15
|
+
read_inheritable_attribute(:#{sym})
|
12
16
|
end
|
13
17
|
|
14
18
|
def #{sym}
|
15
|
-
|
19
|
+
self.class.#{sym}
|
16
20
|
end
|
17
21
|
EOS
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
|
-
def
|
22
|
-
syms.
|
23
|
-
|
24
|
-
|
25
|
-
@@#{sym} = nil
|
26
|
-
end
|
27
|
-
|
25
|
+
def class_inheritable_writer(*syms)
|
26
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
27
|
+
syms.each do |sym|
|
28
|
+
class_eval <<-EOS
|
28
29
|
def self.#{sym}=(obj)
|
29
|
-
|
30
|
+
write_inheritable_attribute(:#{sym}, obj)
|
30
31
|
end
|
31
32
|
|
33
|
+
#{"
|
32
34
|
def #{sym}=(obj)
|
33
|
-
|
35
|
+
self.class.#{sym} = obj
|
34
36
|
end
|
37
|
+
" unless options[:instance_writer] == false }
|
35
38
|
EOS
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
|
-
def
|
40
|
-
|
41
|
-
cattr_writer(*syms)
|
42
|
-
end
|
43
|
-
|
44
|
-
def shared_reader(*syms)
|
42
|
+
def class_inheritable_array_writer(*syms)
|
43
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
45
44
|
syms.each do |sym|
|
46
45
|
class_eval <<-EOS
|
47
|
-
def self.#{sym}
|
48
|
-
|
46
|
+
def self.#{sym}=(obj)
|
47
|
+
write_inheritable_array(:#{sym}, obj)
|
49
48
|
end
|
50
49
|
|
51
|
-
|
52
|
-
|
50
|
+
#{"
|
51
|
+
def #{sym}=(obj)
|
52
|
+
self.class.#{sym} = obj
|
53
53
|
end
|
54
|
+
" unless options[:instance_writer] == false }
|
54
55
|
EOS
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
58
|
-
def
|
59
|
+
def class_inheritable_hash_writer(*syms)
|
60
|
+
options = syms.last.is_a?(Hash) ? syms.pop : {}
|
59
61
|
syms.each do |sym|
|
60
62
|
class_eval <<-EOS
|
61
63
|
def self.#{sym}=(obj)
|
62
|
-
|
64
|
+
write_inheritable_hash(:#{sym}, obj)
|
63
65
|
end
|
64
66
|
|
67
|
+
#{"
|
65
68
|
def #{sym}=(obj)
|
66
69
|
self.class.#{sym} = obj
|
67
70
|
end
|
71
|
+
" unless options[:instance_writer] == false }
|
68
72
|
EOS
|
69
73
|
end
|
70
74
|
end
|
71
75
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
76
|
+
def class_inheritable_accessor(*syms)
|
77
|
+
class_inheritable_reader(*syms)
|
78
|
+
class_inheritable_writer(*syms)
|
79
|
+
end
|
80
|
+
|
81
|
+
def class_inheritable_array(*syms)
|
82
|
+
class_inheritable_reader(*syms)
|
83
|
+
class_inheritable_array_writer(*syms)
|
84
|
+
end
|
85
|
+
|
86
|
+
def class_inheritable_hash(*syms)
|
87
|
+
class_inheritable_reader(*syms)
|
88
|
+
class_inheritable_hash_writer(*syms)
|
75
89
|
end
|
76
90
|
|
77
|
-
def
|
78
|
-
@
|
91
|
+
def inheritable_attributes
|
92
|
+
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
|
79
93
|
end
|
80
94
|
|
81
|
-
def
|
82
|
-
|
95
|
+
def write_inheritable_attribute(key, value)
|
96
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
97
|
+
@inheritable_attributes = {}
|
98
|
+
end
|
99
|
+
inheritable_attributes[key] = value
|
100
|
+
end
|
101
|
+
|
102
|
+
def write_inheritable_array(key, elements)
|
103
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
104
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
83
105
|
end
|
84
106
|
|
85
|
-
def
|
86
|
-
|
107
|
+
def write_inheritable_hash(key, hash)
|
108
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
109
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
110
|
+
end
|
111
|
+
|
112
|
+
def read_inheritable_attribute(key)
|
113
|
+
inheritable_attributes[key]
|
87
114
|
end
|
88
115
|
|
89
|
-
def
|
90
|
-
|
116
|
+
def reset_inheritable_attributes
|
117
|
+
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
91
118
|
end
|
92
119
|
|
93
|
-
private
|
94
|
-
|
95
|
-
|
120
|
+
private
|
121
|
+
# Prevent this constant from being created multiple times
|
122
|
+
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
|
123
|
+
|
124
|
+
def inherited_with_inheritable_attributes(child)
|
125
|
+
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
|
96
126
|
|
97
|
-
|
98
|
-
|
127
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
128
|
+
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
129
|
+
else
|
130
|
+
new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
|
131
|
+
memo.update(key => (value.dup rescue value))
|
132
|
+
end
|
99
133
|
end
|
100
134
|
|
101
|
-
child.instance_variable_set('@
|
135
|
+
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
|
102
136
|
end
|
103
137
|
|
104
|
-
alias
|
105
|
-
alias inherited
|
106
|
-
end
|
138
|
+
alias inherited_without_inheritable_attributes inherited
|
139
|
+
alias inherited inherited_with_inheritable_attributes
|
140
|
+
end
|