tanuki 0.3.1 → 0.4.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.rdoc +5 -4
- data/app/tanuki/controller/{link.thtml → controller.link.thtml} +0 -0
- data/app/tanuki/controller/controller.page.thtml +14 -0
- data/app/tanuki/controller/controller.rb +1 -2
- data/app/tanuki/controller/controller.title.ttxt +1 -0
- data/app/tanuki/controller/controller.view.thtml +3 -0
- data/app/tanuki/fetcher/sequel/sequel.rb +34 -0
- data/app/tanuki/manager/controller/controller.rb +1 -1
- data/app/tanuki/manager/page/page.rb +1 -1
- data/app/tanuki/meta_model/{manager.ttxt → meta_model.manager.ttxt} +0 -0
- data/app/tanuki/meta_model/{manager_base.ttxt → meta_model.manager_base.ttxt} +0 -0
- data/app/tanuki/meta_model/{model.ttxt → meta_model.model.ttxt} +0 -0
- data/app/tanuki/meta_model/{model_base.ttxt → meta_model.model_base.ttxt} +0 -0
- data/app/tanuki/meta_model/meta_model.rb +1 -2
- data/app/tanuki/model/controller/controller.rb +1 -1
- data/app/tanuki/model/page/page.rb +1 -1
- data/app/tanuki/page/missing/{default.thtml → missing.page.thtml} +1 -1
- data/app/tanuki/page/missing/missing.rb +3 -2
- data/app/user/page/home/home.rb +2 -0
- data/app/user/page/home/home.title.thtml +1 -0
- data/app/user/page/home/home.view.css +88 -0
- data/app/user/page/home/home.view.thtml +22 -0
- data/bin/tanuki +2 -1
- data/config/common.rb +1 -0
- data/config/common_application.rb +3 -6
- data/config/development_application.rb +0 -3
- data/lib/tanuki.rb +8 -7
- data/lib/tanuki/application.rb +108 -81
- data/lib/tanuki/argument.rb +10 -5
- data/lib/tanuki/argument/integer_range.rb +4 -2
- data/lib/tanuki/{behavior/object_behavior.rb → base_behavior.rb} +21 -4
- data/lib/tanuki/configurator.rb +20 -8
- data/lib/tanuki/const.rb +32 -0
- data/lib/tanuki/context.rb +18 -7
- data/lib/tanuki/controller.rb +517 -0
- data/lib/tanuki/css_compressor.rb +50 -0
- data/lib/tanuki/extensions/module.rb +21 -5
- data/lib/tanuki/extensions/rack/frozen_route.rb +35 -0
- data/lib/tanuki/extensions/rack/static_dir.rb +1 -1
- data/lib/tanuki/extensions/sequel/model.rb +7 -0
- data/lib/tanuki/i18n.rb +8 -6
- data/lib/tanuki/loader.rb +166 -33
- data/lib/tanuki/meta_model.rb +176 -0
- data/lib/tanuki/{behavior/model_behavior.rb → model_behavior.rb} +7 -3
- data/lib/tanuki/model_generator.rb +49 -29
- data/lib/tanuki/template_compiler.rb +72 -41
- data/lib/tanuki/utility.rb +11 -4
- data/lib/tanuki/utility/create.rb +52 -11
- data/lib/tanuki/utility/generate.rb +16 -10
- data/lib/tanuki/utility/version.rb +1 -1
- data/lib/tanuki/version.rb +7 -2
- metadata +50 -66
- data/app/tanuki/controller/default.thtml +0 -5
- data/app/tanuki/controller/index.thtml +0 -1
- data/app/user/page/index/default.thtml +0 -121
- data/app/user/page/index/index.rb +0 -2
- data/config/test_application.rb +0 -2
- data/lib/tanuki/behavior/controller_behavior.rb +0 -366
- data/lib/tanuki/behavior/meta_model_behavior.rb +0 -160
- data/lib/tanuki/extensions/rack/builder.rb +0 -26
- data/lib/tanuki/extensions/rack/server.rb +0 -18
- data/lib/tanuki/launcher.rb +0 -21
- data/lib/tanuki/utility/server.rb +0 -23
data/config/test_application.rb
DELETED
@@ -1,366 +0,0 @@
|
|
1
|
-
module Tanuki
|
2
|
-
|
3
|
-
# Tanuki::ControllerBehavior contains basic methods for a framework controller.
|
4
|
-
# It is included in the base controller class.
|
5
|
-
module ControllerBehavior
|
6
|
-
|
7
|
-
include Enumerable
|
8
|
-
|
9
|
-
internal_attr_reader :model, :logical_parent, :link
|
10
|
-
internal_attr_accessor :logical_child, :visual_child
|
11
|
-
|
12
|
-
# Creates new controller with context +ctx+, +logical_parent+ controller, +route_part+ definitions and a +model+.
|
13
|
-
def initialize(ctx, logical_parent, route_part, model=nil)
|
14
|
-
@_configured = false
|
15
|
-
@_ctx = ctx
|
16
|
-
@_model = model
|
17
|
-
@_args = {}
|
18
|
-
if @_logical_parent = logical_parent
|
19
|
-
|
20
|
-
# Register controller arguments, as declared with Tanuki::ControllerBehavior#has_arg.
|
21
|
-
@_route = route_part[:route]
|
22
|
-
self.class.arg_defs.each_pair do |arg_name, arg_def|
|
23
|
-
route_part[:args][arg_def[:index]] = @_args[arg_name] = arg_def[:arg].to_value(route_part[:args][arg_def[:index]])
|
24
|
-
end
|
25
|
-
|
26
|
-
@_link = self.class.grow_link(@_logical_parent, {:route => @_route, :args => @_args}, self.class.arg_defs)
|
27
|
-
initialize_route(*route_part[:args])
|
28
|
-
else
|
29
|
-
@_link = '/'
|
30
|
-
@_route = nil
|
31
|
-
initialize_route
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Invoked with route +args+ when current route is initialized.
|
36
|
-
def initialize_route(*args)
|
37
|
-
end
|
38
|
-
|
39
|
-
# Returns controller context. Used internally by templates.
|
40
|
-
def _ctx(ctx)
|
41
|
-
@_ctx
|
42
|
-
end
|
43
|
-
|
44
|
-
# Initializes and retrieves child controller on +route+. Searches static, dynamic, and ghost routes (in that order).
|
45
|
-
def [](route, *args)
|
46
|
-
byname = (args.length == 1 and args[0].is_a? Hash)
|
47
|
-
ensure_configured!
|
48
|
-
key = [route, args.dup]
|
49
|
-
if cached = @_cache[key]
|
50
|
-
|
51
|
-
# Return form cache
|
52
|
-
return cached
|
53
|
-
|
54
|
-
elsif child_def = @_child_defs[route]
|
55
|
-
|
56
|
-
# Search static routes
|
57
|
-
klass = child_def[:class]
|
58
|
-
args = klass.extract_args(args[0]) if byname
|
59
|
-
child = klass.new(process_child_context(@_ctx, route), self, {:route => route, :args => args}, child_def[:model])
|
60
|
-
|
61
|
-
else
|
62
|
-
|
63
|
-
# Search dynamic routes
|
64
|
-
found = false
|
65
|
-
s = route.to_s
|
66
|
-
@_child_collection_defs.each do |collection_def|
|
67
|
-
if md = collection_def[:parse].match(s)
|
68
|
-
a_route = md['route'].to_sym
|
69
|
-
child_def = collection_def[:fetcher].fetch(a_route, collection_def[:format])
|
70
|
-
if child_def
|
71
|
-
klass = child_def[:class]
|
72
|
-
args = klass.extract_args(args[0]) if byname
|
73
|
-
embedded_args = klass.extract_args(md)
|
74
|
-
args.each_index {|i| embedded_args[i] = args[i] if args[i] }
|
75
|
-
child = klass.new(process_child_context(@_ctx, a_route), self,
|
76
|
-
{:route => a_route, :args => embedded_args}, child_def[:model])
|
77
|
-
found = true
|
78
|
-
break child
|
79
|
-
end # if
|
80
|
-
end # each
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
# If still not found, search ghost routes
|
85
|
-
child = missing_route(route, *args) unless found
|
86
|
-
|
87
|
-
end
|
88
|
-
@_cache[key] = child # Thread safe (possible overwrite, but within consistent state)
|
89
|
-
end
|
90
|
-
|
91
|
-
# Returns +true+, if controller is active.
|
92
|
-
def active?
|
93
|
-
@_active
|
94
|
-
end
|
95
|
-
|
96
|
-
# Retrieves child controller class on +route+. Searches static, dynamic, and ghost routes (in that order).
|
97
|
-
def child_class(route)
|
98
|
-
ensure_configured!
|
99
|
-
args = []
|
100
|
-
key = [route, args]
|
101
|
-
if cached = @_cache[key]
|
102
|
-
|
103
|
-
# Return from cache
|
104
|
-
return cached.class
|
105
|
-
|
106
|
-
elsif child_def = @_child_defs[route]
|
107
|
-
|
108
|
-
# Return from static routes
|
109
|
-
return child_def[:class]
|
110
|
-
|
111
|
-
else
|
112
|
-
|
113
|
-
# Search dynamic routes
|
114
|
-
s = route.to_s
|
115
|
-
@_child_collection_defs.each do |collection_def|
|
116
|
-
if md = collection_def[:parse].match(s)
|
117
|
-
a_route = md['route'].to_sym
|
118
|
-
child_def = collection_def[:fetcher].fetch(a_route, collection_def[:format])
|
119
|
-
return child_def[:class] if child_def
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
# If still not found, search ghost routes
|
124
|
-
return (@_cache[key] = missing_route(route, *args)).class
|
125
|
-
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# Invoked when controller needs to be configured.
|
130
|
-
def configure
|
131
|
-
end
|
132
|
-
|
133
|
-
# Returns +true+, if controller is current.
|
134
|
-
def current?
|
135
|
-
@_current
|
136
|
-
end
|
137
|
-
|
138
|
-
# If set, controller navigates to a given child route by default.
|
139
|
-
# Returned object should be either +nil+ (don't navigate), or a +Hash+ with keys:
|
140
|
-
# * +:route+ is the +Symbol+ for the route
|
141
|
-
# * +:args+ contain route arguments +Hash+
|
142
|
-
# * +:redirect+ makes a 302 redirect to this route, if true (optional)
|
143
|
-
def default_route
|
144
|
-
nil
|
145
|
-
end
|
146
|
-
|
147
|
-
# Calls +block+ once for each visible child controller on static or dynamic routes, passing it as a parameter.
|
148
|
-
def each(&block)
|
149
|
-
return Enumerator.new(self) unless block_given?
|
150
|
-
ensure_configured!
|
151
|
-
@_child_defs.each_pair do |route, child|
|
152
|
-
if route.is_a? Regexp
|
153
|
-
cd = @_child_collection_defs[child]
|
154
|
-
cd[:fetcher].fetch_all(cd[:format]) do |child_def|
|
155
|
-
key = [child_def[:route], []]
|
156
|
-
unless child = @_cache[key]
|
157
|
-
child = child_def[:class].new(process_child_context(@_ctx, route), self,
|
158
|
-
{:route => child_def[:route], :args => {}}, child_def[:model])
|
159
|
-
@_cache[key] = child
|
160
|
-
end
|
161
|
-
block.call child
|
162
|
-
end
|
163
|
-
else
|
164
|
-
yield self[route] unless child[:hidden]
|
165
|
-
end
|
166
|
-
end
|
167
|
-
self
|
168
|
-
end
|
169
|
-
|
170
|
-
# Invoked when controller configuration needs to be ensured.
|
171
|
-
def ensure_configured!
|
172
|
-
unless @_configured
|
173
|
-
@_child_defs = {}
|
174
|
-
@_child_collection_defs = []
|
175
|
-
@_cache={}
|
176
|
-
@_length = 0
|
177
|
-
configure
|
178
|
-
@_configured = true
|
179
|
-
end
|
180
|
-
nil
|
181
|
-
end
|
182
|
-
|
183
|
-
# Returns the link to the current controller, switching the active controller on the respective path level to +self+.
|
184
|
-
def forward_link
|
185
|
-
uri_parts = @_ctx.env['PATH_INFO'].split(/(?<!\$)\//)
|
186
|
-
link_parts = link.split(/(?<!\$)\//)
|
187
|
-
link_parts.each_index {|i| uri_parts[i] = link_parts[i] }
|
188
|
-
uri_parts.join('/') << ((qs = @_ctx.env['QUERY_STRING']).empty? ? '' : "?#{qs}")
|
189
|
-
end
|
190
|
-
|
191
|
-
# Returns the number of visible child controllers on static and dynamic routes.
|
192
|
-
def length
|
193
|
-
if @_child_collection_defs.length > 0
|
194
|
-
if @_length_is_valid
|
195
|
-
@_length
|
196
|
-
else
|
197
|
-
@_child_collection_defs.each {|cd| @_length += cd[:fetcher].length }
|
198
|
-
@_length_is_valid = true
|
199
|
-
end
|
200
|
-
else
|
201
|
-
@_length
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
# Invoked when child controller context needs to be processed before initializing.
|
206
|
-
def process_child_context(ctx, route)
|
207
|
-
ctx
|
208
|
-
end
|
209
|
-
|
210
|
-
alias_method :size, :length
|
211
|
-
|
212
|
-
# Returns controller string representation. Defaults to route name.
|
213
|
-
def to_s
|
214
|
-
@_route.to_s
|
215
|
-
end
|
216
|
-
|
217
|
-
# Invoked when visual parent needs to be determined. Defaults to logical parent.
|
218
|
-
def visual_parent
|
219
|
-
@_logical_parent
|
220
|
-
end
|
221
|
-
|
222
|
-
private
|
223
|
-
|
224
|
-
# Defines a child of class +klass+ on +route+ with +model+, optionally +hidden+.
|
225
|
-
def has_child(klass, route, model=nil, hidden=false)
|
226
|
-
@_child_defs[route] = {:class => klass, :model => model, :hidden => hidden}
|
227
|
-
@_length += 1 unless hidden
|
228
|
-
self
|
229
|
-
end
|
230
|
-
|
231
|
-
# Defines a child collection of type +parse_regexp+, formatted back by +format_string+.
|
232
|
-
def has_child_collection(parse_regexp, format_string, child_def_fetcher)
|
233
|
-
@_child_defs[parse_regexp] = @_child_collection_defs.size
|
234
|
-
@_child_collection_defs << {:parse => parse_regexp, :format => format_string, :fetcher => child_def_fetcher}
|
235
|
-
@_length_is_valid = false
|
236
|
-
end
|
237
|
-
|
238
|
-
# Invoked for +route+ with +args+ when a route is missing. This hook can be used to make ghost routes.
|
239
|
-
def missing_route(route, *args)
|
240
|
-
@_ctx.missing_page.new(@_ctx, self, {:route => route, :args => []})
|
241
|
-
end
|
242
|
-
|
243
|
-
# Tanuki::ControllerBehavior mixed-in class methods.
|
244
|
-
module ClassMethods
|
245
|
-
|
246
|
-
# Returns own or superclass argument definitions.
|
247
|
-
def arg_defs
|
248
|
-
@_arg_defs ||= superclass.arg_defs.dup
|
249
|
-
end
|
250
|
-
|
251
|
-
# Escapes characters +chrs+ and encodes a given string +s+ for use in links.
|
252
|
-
def escape(s, chrs)
|
253
|
-
s ? Rack::Utils.escape(s.to_s.gsub(/[\$#{chrs}]/, '$\0')) : nil
|
254
|
-
end
|
255
|
-
|
256
|
-
# Extracts arguments, initializing default values beforehand. Searches +md+ hash for default value overrides.
|
257
|
-
def extract_args(md)
|
258
|
-
res = []
|
259
|
-
arg_defs.each_pair do |name, arg|
|
260
|
-
res[arg[:index]] = md[name]
|
261
|
-
end
|
262
|
-
res
|
263
|
-
end
|
264
|
-
|
265
|
-
# Builds link from controller +ctrl+ to a given route.
|
266
|
-
def grow_link(ctrl, route_part, arg_defs)
|
267
|
-
own_link = escape(route_part[:route], '\/:') << route_part[:args].map do |k, v|
|
268
|
-
arg_defs[k][:arg].default == v ? '' : ":#{escape(k, '\/:-')}-#{escape(v, '\/:')}"
|
269
|
-
end.join
|
270
|
-
"#{ctrl.link == '/' ? '' : ctrl.link}/#{own_link}"
|
271
|
-
end
|
272
|
-
|
273
|
-
# Defines an argument with a +name+, derived from type +obj+ with additional +args+.
|
274
|
-
def has_arg(name, obj, *args)
|
275
|
-
# TODO Ensure thread safety
|
276
|
-
arg_defs[name] = {:arg => Argument.to_argument(obj, *args), :index => @_arg_defs.size}
|
277
|
-
end
|
278
|
-
|
279
|
-
# Prepares the extended module.
|
280
|
-
def self.extended(mod)
|
281
|
-
mod.instance_variable_set(:@_arg_defs, {})
|
282
|
-
end
|
283
|
-
|
284
|
-
end # ClassMethods
|
285
|
-
|
286
|
-
extend ClassMethods
|
287
|
-
|
288
|
-
class << self
|
289
|
-
|
290
|
-
# Dispathes route chain in context +ctx+ on +request_path+, starting with controller +klass+.
|
291
|
-
def dispatch(ctx, klass, request_path)
|
292
|
-
route_parts = parse_path(request_path)
|
293
|
-
|
294
|
-
# Set logical children for active controllers
|
295
|
-
curr = root_ctrl = klass.new(ctx, nil, nil, true)
|
296
|
-
route_parts.each do |route_part|
|
297
|
-
curr.instance_variable_set :@_active, true
|
298
|
-
nxt = curr[route_part[:route], *route_part[:args]]
|
299
|
-
curr.logical_child = nxt
|
300
|
-
curr = nxt
|
301
|
-
end
|
302
|
-
|
303
|
-
# Set links for active controllers and default routes
|
304
|
-
while route_part = curr.default_route
|
305
|
-
|
306
|
-
# Do a redirect, if some controller in the chain asks for it
|
307
|
-
if route_part[:redirect]
|
308
|
-
klass = curr.child_class(route_part)
|
309
|
-
return {:type => :redirect, :location => grow_link(curr, route_part, klass.arg_defs)}
|
310
|
-
end
|
311
|
-
|
312
|
-
# Add default route as logical child
|
313
|
-
curr.instance_variable_set :@_active, true
|
314
|
-
nxt = curr[route_part[:route], *route_part[:args]]
|
315
|
-
curr.logical_child = nxt
|
316
|
-
curr = nxt
|
317
|
-
|
318
|
-
end
|
319
|
-
|
320
|
-
# Find out dispatch result type from current controller
|
321
|
-
curr.instance_variable_set :@_active, true
|
322
|
-
curr.instance_variable_set :@_current, true
|
323
|
-
type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
|
324
|
-
|
325
|
-
# Set visual children for active controllers
|
326
|
-
prev = curr
|
327
|
-
while curr = prev.visual_parent
|
328
|
-
curr.visual_child = prev
|
329
|
-
prev = curr
|
330
|
-
end
|
331
|
-
|
332
|
-
{:type => type, :controller => prev}
|
333
|
-
end
|
334
|
-
|
335
|
-
# Extends the including module with Tanuki::ControllerBehavior::ClassMethods.
|
336
|
-
def included(mod)
|
337
|
-
mod.extend ClassMethods
|
338
|
-
end
|
339
|
-
|
340
|
-
private
|
341
|
-
|
342
|
-
# Parses +path+ to return route name and arguments.
|
343
|
-
def parse_path(path)
|
344
|
-
path[1..-1].split(/(?<!\$)\//).map do |s|
|
345
|
-
arr = s.gsub('$/', '/').split(/(?<!\$):/)
|
346
|
-
route_part = {:route => unescape(arr[0]).to_sym}
|
347
|
-
args = {}
|
348
|
-
arr[1..-1].each do |argval|
|
349
|
-
varr = argval.split(/(?<!\$)-/)
|
350
|
-
args[unescape(varr[0])] = unescape(varr[1..-1].join) # TODO Predict argument
|
351
|
-
end
|
352
|
-
route_part[:args] = extract_args(args)
|
353
|
-
route_part
|
354
|
-
end # do
|
355
|
-
end
|
356
|
-
|
357
|
-
# Unescapes a given link part for internal use.
|
358
|
-
def unescape(s)
|
359
|
-
s ? s.gsub(/\$([\/\$:-])/, '\1') : nil
|
360
|
-
end
|
361
|
-
|
362
|
-
end # class << self
|
363
|
-
|
364
|
-
end # ControllerBehavior
|
365
|
-
|
366
|
-
end # Tanuki
|
@@ -1,160 +0,0 @@
|
|
1
|
-
module Tanuki
|
2
|
-
|
3
|
-
# Tanuki::MetaModelBehavior contains all methods for the meta-model.
|
4
|
-
# In is included in the meta-model class.
|
5
|
-
module MetaModelBehavior
|
6
|
-
|
7
|
-
# Creates new meta-model +name+ in +namespace+.
|
8
|
-
# Model schema is passed as +data+.
|
9
|
-
# Stucture +models+ contains all models being generated.
|
10
|
-
def initialize(namespace, name, data, models)
|
11
|
-
@namespace = namespace
|
12
|
-
@name = name
|
13
|
-
@data = data
|
14
|
-
@models = models
|
15
|
-
end
|
16
|
-
|
17
|
-
# Returns class name for a given class type.
|
18
|
-
def class_name_for(class_type)
|
19
|
-
case class_type
|
20
|
-
when :model, :model_base then "#{@namespace}_Model_#{@name}"
|
21
|
-
when :manager, :manager_base then "#{@namespace}_Manager_#{@name}"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
# Returns an array of code for alias-column name pair.
|
26
|
-
def key
|
27
|
-
@key.inspect
|
28
|
-
end
|
29
|
-
|
30
|
-
# Prepares data for template generation.
|
31
|
-
# Processes own keys, fields, etc.
|
32
|
-
def process!
|
33
|
-
process_source!
|
34
|
-
process_key!
|
35
|
-
process_joins!
|
36
|
-
process_filter!
|
37
|
-
process_order!
|
38
|
-
end
|
39
|
-
|
40
|
-
# Prepares data for building a Sequel +where+ clause.
|
41
|
-
def process_filter!
|
42
|
-
# TODO: ...
|
43
|
-
end
|
44
|
-
|
45
|
-
def process_key!
|
46
|
-
if @source.include? 'key' && @source['key'].nil?
|
47
|
-
@key = []
|
48
|
-
else
|
49
|
-
@key = @source['key'] || 'id'
|
50
|
-
end
|
51
|
-
@key = [@key] if @key.is_a? String
|
52
|
-
raise 'invalid key' unless @key.is_a? Array
|
53
|
-
@key.map! do |k|
|
54
|
-
parts = k.split('.').map {|p| p.to_sym }
|
55
|
-
raise "invalid key field #{k}" if parts.count > 2
|
56
|
-
if parts.count == 2
|
57
|
-
raise 'all key fields should belong to the first-source' if parts[0] != @first_source.to_s
|
58
|
-
parts
|
59
|
-
else
|
60
|
-
[@first_source, parts[0]]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
# Prepares data for building a Sequel +order+ clause.
|
66
|
-
def process_order!
|
67
|
-
# TODO: ...
|
68
|
-
end
|
69
|
-
|
70
|
-
# Extracts the model firts-source information form the YAML @data
|
71
|
-
# and performs
|
72
|
-
def process_source!
|
73
|
-
guess_table = @name.pluralize
|
74
|
-
@data ||= {}
|
75
|
-
@source = @data['source'] || guess_table
|
76
|
-
@source = {'table' => @source} if @source.is_a? String
|
77
|
-
@first_source = (@source['table'] || guess_table).downcase.to_sym
|
78
|
-
end
|
79
|
-
|
80
|
-
def process_joins!
|
81
|
-
@joins = {}
|
82
|
-
@joins[@first_source] = nil
|
83
|
-
joins = @source['joins'] || {}
|
84
|
-
joins = [joins] if joins.is_a? String
|
85
|
-
joins = Hash[*joins.collect {|v| [v, nil] }.flatten] if joins.is_a? Array
|
86
|
-
if joins.is_a? Hash
|
87
|
-
joins.each_pair do |table_alias, join|
|
88
|
-
table_alias = table_alias.to_sym
|
89
|
-
raise "#{table_alias} is already in use" if @joins.include? table_alias
|
90
|
-
if join && (join['on'].is_a? Hash)
|
91
|
-
table_name = join['table'] || table_alias
|
92
|
-
on = join['on']
|
93
|
-
join_type = (join['type'] || 'inner').to_sym
|
94
|
-
else
|
95
|
-
on = join
|
96
|
-
table_name = table_alias
|
97
|
-
join_type = :inner
|
98
|
-
end
|
99
|
-
if on
|
100
|
-
on = Hash[*on.map do |lhs, rhs|
|
101
|
-
[[lhs,table_alias],[rhs,@first_source]].map do |side,table_alias|
|
102
|
-
if side.is_a? String
|
103
|
-
if m = side.match(/^\(('|")(.*)\1\)$/)
|
104
|
-
m[2]
|
105
|
-
else
|
106
|
-
parts = side.split('.').map {|x| x.to_sym }
|
107
|
-
case parts.count
|
108
|
-
when 1
|
109
|
-
[table_alias, parts[0]]
|
110
|
-
when 2
|
111
|
-
raise "Unknown alias #{parts[0]}" unless @joins.include? parts[0]
|
112
|
-
parts
|
113
|
-
else
|
114
|
-
raise "Invalid column specification #{lhs}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
else
|
118
|
-
side
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end.flatten]
|
122
|
-
else
|
123
|
-
on = {}
|
124
|
-
@key.each do |k|
|
125
|
-
on[[table_alias, (@first_source.to_s.singularize << '_' << k[1].to_s).to_sym]] = k
|
126
|
-
end
|
127
|
-
end
|
128
|
-
@joins[table_alias] = {
|
129
|
-
:type => join_type,
|
130
|
-
:table => table_name,
|
131
|
-
:alias => table_alias,
|
132
|
-
:on => on
|
133
|
-
}
|
134
|
-
end
|
135
|
-
else
|
136
|
-
raise "`joins' should be either nil or string or array or hash"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
# Prepares data for template generation.
|
141
|
-
# Processes foreign keys, fields, etc.
|
142
|
-
def process_relations!
|
143
|
-
|
144
|
-
end
|
145
|
-
|
146
|
-
# Returns code for alias-column name pair for field +field_name+.
|
147
|
-
def qualified_name(field_name)
|
148
|
-
parts = field_name.split('.')
|
149
|
-
if parts.length == 1
|
150
|
-
"%w{:#{@first_source} :#{field_name}}"
|
151
|
-
elsif parts.length == 2
|
152
|
-
"%w{:#{parts[0]} :#{parts[1]}}"
|
153
|
-
else
|
154
|
-
raise "field name for model #{@namespace}.#{@name} is invalid"
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
end # MetaModelBehavior
|
159
|
-
|
160
|
-
end # Tanuki
|