tanuki 0.3.0 → 0.3.1

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.
Files changed (65) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +10 -2
  3. data/app/tanuki/attribute/attribute.rb +2 -2
  4. data/app/tanuki/base/base.rb +2 -2
  5. data/app/tanuki/controller/controller.rb +2 -2
  6. data/app/tanuki/controller/default.thtml +1 -1
  7. data/app/tanuki/controller/index.thtml +1 -1
  8. data/app/tanuki/controller/link.thtml +1 -1
  9. data/app/tanuki/manager/controller/controller.rb +2 -0
  10. data/app/tanuki/manager/page/page.rb +2 -0
  11. data/app/tanuki/meta_model/manager.ttxt +1 -1
  12. data/app/tanuki/meta_model/manager_base.ttxt +2 -2
  13. data/app/tanuki/meta_model/meta_model.rb +2 -2
  14. data/app/tanuki/meta_model/model.ttxt +1 -1
  15. data/app/tanuki/meta_model/model_base.ttxt +2 -2
  16. data/app/tanuki/model/controller/controller.rb +2 -0
  17. data/app/tanuki/model/model.rb +2 -2
  18. data/app/tanuki/model/page/page.rb +2 -0
  19. data/app/tanuki/page/missing/default.thtml +1 -1
  20. data/app/tanuki/page/missing/missing.rb +2 -2
  21. data/app/user/page/index/default.thtml +1 -1
  22. data/app/user/page/index/index.rb +2 -2
  23. data/bin/tanuki +3 -201
  24. data/config/common.rb +1 -1
  25. data/config/common_application.rb +3 -3
  26. data/config/development_application.rb +1 -1
  27. data/config/production_application.rb +1 -1
  28. data/config/test_application.rb +2 -0
  29. data/lib/tanuki/application.rb +27 -7
  30. data/lib/tanuki/argument/base.rb +3 -3
  31. data/lib/tanuki/argument/integer.rb +3 -3
  32. data/lib/tanuki/argument/integer_range.rb +3 -3
  33. data/lib/tanuki/argument/string.rb +3 -3
  34. data/lib/tanuki/argument.rb +3 -3
  35. data/lib/tanuki/behavior/controller_behavior.rb +40 -8
  36. data/lib/tanuki/behavior/meta_model_behavior.rb +62 -20
  37. data/lib/tanuki/behavior/model_behavior.rb +4 -4
  38. data/lib/tanuki/behavior/object_behavior.rb +2 -2
  39. data/lib/tanuki/configurator.rb +2 -2
  40. data/lib/tanuki/context.rb +8 -3
  41. data/lib/tanuki/extensions/module.rb +16 -1
  42. data/lib/tanuki/extensions/rack/builder.rb +2 -2
  43. data/lib/tanuki/extensions/rack/server.rb +2 -2
  44. data/lib/tanuki/extensions/rack/static_dir.rb +2 -2
  45. data/lib/tanuki/i18n.rb +2 -2
  46. data/lib/tanuki/launcher.rb +2 -2
  47. data/lib/tanuki/loader.rb +24 -4
  48. data/lib/tanuki/model_generator.rb +114 -0
  49. data/lib/tanuki/template_compiler.rb +89 -13
  50. data/lib/tanuki/utility/create.rb +38 -0
  51. data/lib/tanuki/utility/generate.rb +52 -0
  52. data/lib/tanuki/utility/help.rb +16 -0
  53. data/lib/tanuki/utility/server.rb +23 -0
  54. data/lib/tanuki/utility/version.rb +15 -0
  55. data/lib/tanuki/utility.rb +65 -0
  56. data/lib/tanuki/version.rb +2 -2
  57. data/lib/tanuki.rb +1 -2
  58. data/schema/tanuki/l10n/en/controller.yml +1 -1
  59. data/schema/tanuki/l10n/en/page.yml +1 -1
  60. data/schema/tanuki/l10n/ru/controller.yml +1 -1
  61. data/schema/tanuki/l10n/ru/page.yml +1 -1
  62. data/schema/tanuki/models/controller.yml +1 -1
  63. data/schema/tanuki/models/page.yml +1 -1
  64. metadata +16 -5
  65. data/lib/tanuki/extensions/object.rb +0 -12
@@ -1,7 +1,7 @@
1
1
  module Tanuki
2
2
 
3
3
  # Tanuki::ControllerBehavior contains basic methods for a framework controller.
4
- # In is included in the base controller class.
4
+ # It is included in the base controller class.
5
5
  module ControllerBehavior
6
6
 
7
7
  include Enumerable
@@ -16,10 +16,13 @@ module Tanuki
16
16
  @_model = model
17
17
  @_args = {}
18
18
  if @_logical_parent = logical_parent
19
+
20
+ # Register controller arguments, as declared with Tanuki::ControllerBehavior#has_arg.
19
21
  @_route = route_part[:route]
20
22
  self.class.arg_defs.each_pair do |arg_name, arg_def|
21
23
  route_part[:args][arg_def[:index]] = @_args[arg_name] = arg_def[:arg].to_value(route_part[:args][arg_def[:index]])
22
24
  end
25
+
23
26
  @_link = self.class.grow_link(@_logical_parent, {:route => @_route, :args => @_args}, self.class.arg_defs)
24
27
  initialize_route(*route_part[:args])
25
28
  else
@@ -44,14 +47,19 @@ module Tanuki
44
47
  ensure_configured!
45
48
  key = [route, args.dup]
46
49
  if cached = @_cache[key]
50
+
47
51
  # Return form cache
48
52
  return cached
53
+
49
54
  elsif child_def = @_child_defs[route]
55
+
50
56
  # Search static routes
51
57
  klass = child_def[:class]
52
58
  args = klass.extract_args(args[0]) if byname
53
59
  child = klass.new(process_child_context(@_ctx, route), self, {:route => route, :args => args}, child_def[:model])
60
+
54
61
  else
62
+
55
63
  # Search dynamic routes
56
64
  found = false
57
65
  s = route.to_s
@@ -68,11 +76,14 @@ module Tanuki
68
76
  {:route => a_route, :args => embedded_args}, child_def[:model])
69
77
  found = true
70
78
  break child
71
- end
72
- end
79
+ end # if
80
+ end # each
81
+
73
82
  end
83
+
74
84
  # If still not found, search ghost routes
75
85
  child = missing_route(route, *args) unless found
86
+
76
87
  end
77
88
  @_cache[key] = child # Thread safe (possible overwrite, but within consistent state)
78
89
  end
@@ -88,12 +99,17 @@ module Tanuki
88
99
  args = []
89
100
  key = [route, args]
90
101
  if cached = @_cache[key]
102
+
91
103
  # Return from cache
92
104
  return cached.class
105
+
93
106
  elsif child_def = @_child_defs[route]
107
+
94
108
  # Return from static routes
95
109
  return child_def[:class]
110
+
96
111
  else
112
+
97
113
  # Search dynamic routes
98
114
  s = route.to_s
99
115
  @_child_collection_defs.each do |collection_def|
@@ -103,8 +119,10 @@ module Tanuki
103
119
  return child_def[:class] if child_def
104
120
  end
105
121
  end
122
+
106
123
  # If still not found, search ghost routes
107
124
  return (@_cache[key] = missing_route(route, *args)).class
125
+
108
126
  end
109
127
  end
110
128
 
@@ -263,7 +281,7 @@ module Tanuki
263
281
  mod.instance_variable_set(:@_arg_defs, {})
264
282
  end
265
283
 
266
- end # end ClassMethods
284
+ end # ClassMethods
267
285
 
268
286
  extend ClassMethods
269
287
 
@@ -272,6 +290,8 @@ module Tanuki
272
290
  # Dispathes route chain in context +ctx+ on +request_path+, starting with controller +klass+.
273
291
  def dispatch(ctx, klass, request_path)
274
292
  route_parts = parse_path(request_path)
293
+
294
+ # Set logical children for active controllers
275
295
  curr = root_ctrl = klass.new(ctx, nil, nil, true)
276
296
  route_parts.each do |route_part|
277
297
  curr.instance_variable_set :@_active, true
@@ -279,24 +299,36 @@ module Tanuki
279
299
  curr.logical_child = nxt
280
300
  curr = nxt
281
301
  end
302
+
303
+ # Set links for active controllers and default routes
282
304
  while route_part = curr.default_route
305
+
306
+ # Do a redirect, if some controller in the chain asks for it
283
307
  if route_part[:redirect]
284
308
  klass = curr.child_class(route_part)
285
309
  return {:type => :redirect, :location => grow_link(curr, route_part, klass.arg_defs)}
286
310
  end
311
+
312
+ # Add default route as logical child
287
313
  curr.instance_variable_set :@_active, true
288
314
  nxt = curr[route_part[:route], *route_part[:args]]
289
315
  curr.logical_child = nxt
290
316
  curr = nxt
317
+
291
318
  end
319
+
320
+ # Find out dispatch result type from current controller
292
321
  curr.instance_variable_set :@_active, true
293
322
  curr.instance_variable_set :@_current, true
294
323
  type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
324
+
325
+ # Set visual children for active controllers
295
326
  prev = curr
296
327
  while curr = prev.visual_parent
297
328
  curr.visual_child = prev
298
329
  prev = curr
299
330
  end
331
+
300
332
  {:type => type, :controller => prev}
301
333
  end
302
334
 
@@ -319,7 +351,7 @@ module Tanuki
319
351
  end
320
352
  route_part[:args] = extract_args(args)
321
353
  route_part
322
- end
354
+ end # do
323
355
  end
324
356
 
325
357
  # Unescapes a given link part for internal use.
@@ -327,8 +359,8 @@ module Tanuki
327
359
  s ? s.gsub(/\$([\/\$:-])/, '\1') : nil
328
360
  end
329
361
 
330
- end # end class << self
362
+ end # class << self
331
363
 
332
- end # end ControllerBehavior
364
+ end # ControllerBehavior
333
365
 
334
- end # end Tanuki
366
+ end # Tanuki
@@ -32,10 +32,22 @@ module Tanuki
32
32
  def process!
33
33
  process_source!
34
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: ...
35
43
  end
36
44
 
37
45
  def process_key!
38
- @key = @source['key'] || 'id'
46
+ if @source.include? 'key' && @source['key'].nil?
47
+ @key = []
48
+ else
49
+ @key = @source['key'] || 'id'
50
+ end
39
51
  @key = [@key] if @key.is_a? String
40
52
  raise 'invalid key' unless @key.is_a? Array
41
53
  @key.map! do |k|
@@ -50,6 +62,11 @@ module Tanuki
50
62
  end
51
63
  end
52
64
 
65
+ # Prepares data for building a Sequel +order+ clause.
66
+ def process_order!
67
+ # TODO: ...
68
+ end
69
+
53
70
  # Extracts the model firts-source information form the YAML @data
54
71
  # and performs
55
72
  def process_source!
@@ -63,11 +80,6 @@ module Tanuki
63
80
  def process_joins!
64
81
  @joins = {}
65
82
  @joins[@first_source] = nil
66
- end
67
-
68
- # Prepares data for template generation.
69
- # Processes foreign keys, fields, etc.
70
- def process_relations!
71
83
  joins = @source['joins'] || {}
72
84
  joins = [joins] if joins.is_a? String
73
85
  joins = Hash[*joins.collect {|v| [v, nil] }.flatten] if joins.is_a? Array
@@ -75,32 +87,62 @@ module Tanuki
75
87
  joins.each_pair do |table_alias, join|
76
88
  table_alias = table_alias.to_sym
77
89
  raise "#{table_alias} is already in use" if @joins.include? table_alias
78
- if join
79
- if join['on'].is_a Hash
80
- table_name = join['table'] || table_alias
81
- on = join['on']
82
- else
83
- on = join
84
- table_name = table_alias
85
- end
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
86
94
  else
87
- on = nil
88
- table_name = table_alias
95
+ on = join
96
+ table_name = table_alias
97
+ join_type = :inner
89
98
  end
90
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]
91
122
  else
92
123
  on = {}
93
124
  @key.each do |k|
94
- on[[table_alias, @first_source.to_s.singularize.to_sym]] = []
95
- # TODO choose a right priciple
125
+ on[[table_alias, (@first_source.to_s.singularize << '_' << k[1].to_s).to_sym]] = k
96
126
  end
97
127
  end
128
+ @joins[table_alias] = {
129
+ :type => join_type,
130
+ :table => table_name,
131
+ :alias => table_alias,
132
+ :on => on
133
+ }
98
134
  end
99
135
  else
100
136
  raise "`joins' should be either nil or string or array or hash"
101
137
  end
102
138
  end
103
139
 
140
+ # Prepares data for template generation.
141
+ # Processes foreign keys, fields, etc.
142
+ def process_relations!
143
+
144
+ end
145
+
104
146
  # Returns code for alias-column name pair for field +field_name+.
105
147
  def qualified_name(field_name)
106
148
  parts = field_name.split('.')
@@ -113,6 +155,6 @@ module Tanuki
113
155
  end
114
156
  end
115
157
 
116
- end # end MetaModelBehavior
158
+ end # MetaModelBehavior
117
159
 
118
- end # end Tanuki
160
+ end # Tanuki
@@ -105,7 +105,7 @@ module Tanuki
105
105
  mod.instance_variable_set(:@_relations, {})
106
106
  end
107
107
 
108
- end # end ClassMethods
108
+ end # ClassMethods
109
109
 
110
110
  class << self
111
111
 
@@ -114,8 +114,8 @@ module Tanuki
114
114
  mod.extend ClassMethods
115
115
  end
116
116
 
117
- end # end class << self
117
+ end # class << self
118
118
 
119
- end # end ModelBehaviour
119
+ end # ModelBehaviour
120
120
 
121
- end # end Tanuki
121
+ end # Tanuki
@@ -27,6 +27,6 @@ module Tanuki
27
27
  super
28
28
  end
29
29
 
30
- end # end BaseBehavior
30
+ end # BaseBehavior
31
31
 
32
- end # end Tanuki
32
+ end # Tanuki
@@ -49,6 +49,6 @@ module Tanuki
49
49
  Application.visitor(sym, &block)
50
50
  end
51
51
 
52
- end # end Configurator
52
+ end # Configurator
53
53
 
54
- end # end Tanuki
54
+ end # Tanuki
@@ -46,16 +46,21 @@ module Tanuki
46
46
  defined = @_defined
47
47
  class << self; self; end.instance_eval do
48
48
  method_sym = match[1].to_sym
49
+
50
+ # Search cache, if method exists; add as necessary
49
51
  if defined.include? method_sym
50
52
  undef_method method_sym
51
53
  else
52
54
  defined[method_sym] = nil
53
55
  end
56
+
57
+ # Register value in context
54
58
  if arg.is_a? Proc
55
59
  define_method(method_sym, &arg)
56
60
  else
57
61
  define_method(method_sym) { arg }
58
62
  end
63
+
59
64
  return arg
60
65
  end if match[2]
61
66
  super
@@ -66,8 +71,8 @@ module Tanuki
66
71
  raise "contexts cannot be instantiated"
67
72
  end
68
73
 
69
- end # end class << self
74
+ end # class << self
70
75
 
71
- end # end Context
76
+ end # Context
72
77
 
73
- end # end Tanuki
78
+ end # Tanuki
@@ -1,5 +1,20 @@
1
1
  class Module
2
2
 
3
+ # Runs Tanuki::Loader for every missing constant in any namespace.
4
+ def const_missing(sym)
5
+ klass = "#{name + '::' if name != 'Object'}#{sym}"
6
+ paths = Dir.glob(Tanuki::Loader.combined_class_path(klass))
7
+ if paths.empty?
8
+ unless Dir.glob(Tanuki::Loader.combined_class_dir(klass)).empty?
9
+ return const_set(sym, Class.new)
10
+ end
11
+ else
12
+ paths.reverse_each {|path| require path }
13
+ return const_get(sym) if const_defined?(sym)
14
+ end
15
+ raise NameError, "uninitialized constant #{name}::#{sym}"
16
+ end
17
+
3
18
  # Creates a reader +sym+ and a writer +sym=+ for the instance variable @_sym.
4
19
  def internal_attr_accessor(*syms)
5
20
  internal_attr_reader(*syms)
@@ -24,4 +39,4 @@ class Module
24
39
  end
25
40
  end
26
41
 
27
- end # end Module
42
+ end # Module
@@ -22,5 +22,5 @@ module Rack
22
22
  end
23
23
  end
24
24
 
25
- end # end
26
- end # end Rack
25
+ end #
26
+ end # Rack
@@ -14,5 +14,5 @@ module Rack
14
14
 
15
15
  alias_method_chain :options, :tanuki
16
16
 
17
- end # end
18
- end # end Rack
17
+ end #
18
+ end # Rack
@@ -14,5 +14,5 @@ module Rack
14
14
  @app.call(env)
15
15
  end
16
16
 
17
- end # end
18
- end # end Rack
17
+ end #
18
+ end # Rack
data/lib/tanuki/i18n.rb CHANGED
@@ -30,6 +30,6 @@ module Tanuki
30
30
  ctx
31
31
  end
32
32
 
33
- end # end I18n
33
+ end # I18n
34
34
 
35
- end # end Tanuki
35
+ end # Tanuki
@@ -16,6 +16,6 @@ module Tanuki
16
16
  @ctrl.default_view.call(proc {|out| block.call(out.to_s) }, @ctx)
17
17
  end
18
18
 
19
- end # end Launcher
19
+ end # Launcher
20
20
 
21
- end # end Tanuki
21
+ end # Tanuki
data/lib/tanuki/loader.rb CHANGED
@@ -17,6 +17,12 @@ module Tanuki
17
17
  class_path(klass, @app_root ||= combined_app_root)
18
18
  end
19
19
 
20
+ # Returns the path to a directory containing class +klass+.
21
+ # Seatches across all common roots.
22
+ def combined_class_dir(klass)
23
+ const_to_path(klass, @app_root ||= combined_app_root)
24
+ end
25
+
20
26
  # Returns a glob pattern of all common roots.
21
27
  def combined_app_root
22
28
  local_app_root = File.expand_path(File.join('..', '..', '..', 'app'), __FILE__)
@@ -48,11 +54,15 @@ module Tanuki
48
54
  ct_file_exists = File.file?(ct_path)
49
55
  ct_file_mtime = ct_file_exists ? File.mtime(ct_path) : nil
50
56
  st_file = File.new(st_path, 'r:UTF-8')
57
+
58
+ # Find out if template refresh is required
51
59
  if !ct_file_exists || st_file.mtime > ct_file_mtime || File.mtime(COMPILER_PATH) > ct_file_mtime
52
60
  no_refresh = compile_template(st_file, ct_path, ct_file_mtime, owner, sym)
53
61
  else
54
62
  no_refresh = true
55
63
  end
64
+
65
+ # Load template
56
66
  method_name = "#{sym}_view".to_sym
57
67
  owner.instance_eval do
58
68
  unless (method_exists = instance_methods(false).include? method_name) && no_refresh
@@ -60,8 +70,11 @@ module Tanuki
60
70
  load ct_path
61
71
  end
62
72
  end
73
+
74
+ # Register template in cache
63
75
  templates["#{owner}##{sym}"] = nil
64
76
  templates["#{obj.class}##{sym}"] = nil
77
+
65
78
  obj.send(method_name, *args, &block)
66
79
  else
67
80
  raise "undefined template `#{sym}' for #{obj.class}"
@@ -81,7 +94,11 @@ module Tanuki
81
94
  # (is equal to +ct_file_mtime+) since file locking was initiated.
82
95
  def compile_template(st_file, ct_path, ct_file_mtime, owner, sym)
83
96
  no_refresh = true
97
+
98
+ # Lock template source to avoid race condition
84
99
  st_file.flock(File::LOCK_EX)
100
+
101
+ # Compile, if template still needs compiling on lock release
85
102
  if !File.file?(ct_path) || File.mtime(ct_path) == ct_file_mtime
86
103
  no_refresh = false
87
104
  ct_dir = File.dirname(ct_path)
@@ -91,7 +108,10 @@ module Tanuki
91
108
  end
92
109
  FileUtils.mv(tmp_ct_path, ct_path)
93
110
  end
111
+
112
+ # Release lock
94
113
  st_file.flock(File::LOCK_UN)
114
+
95
115
  no_refresh
96
116
  end
97
117
 
@@ -102,7 +122,7 @@ module Tanuki
102
122
 
103
123
  # Transforms a given constant +klass+ to a path with a given +root+.
104
124
  def const_to_path(klass, root)
105
- File.join(root, klass.to_s.split('_').map {|item| item.gsub(/(?!^)([A-Z])/, '_\1') }.join(File::SEPARATOR).downcase)
125
+ File.join(root, klass.to_s.split('::').map {|item| item.gsub(/(?!^)([A-Z])/, '_\1') }.join(File::SEPARATOR).downcase)
106
126
  end
107
127
 
108
128
  # Finds the direct template +method_name+ owner among ancestors of class +klass+.
@@ -115,8 +135,8 @@ module Tanuki
115
135
  [nil, nil]
116
136
  end
117
137
 
118
- end # end class << self
138
+ end # class << self
119
139
 
120
- end # end Path
140
+ end # Path
121
141
 
122
- end # end Tanuki
142
+ end # Tanuki
@@ -0,0 +1,114 @@
1
+ module Tanuki
2
+
3
+ # Tanuki::ModelGenerator is used for, well, generating models
4
+ class ModelGenerator
5
+
6
+ # A collection of all model definitions represented as hash of hashes where
7
+ # the first nesting level represents namespaces
8
+ # and the second one contains named model bodies +Hash+.
9
+ attr_reader :models
10
+
11
+ # +Hash+ for collecting the template render-time info for models.
12
+ # Keys in this hash are full qualified names of models (+namespace_model_name+).
13
+ # Values are hashes in the form of +{:generated => [], :failed => [], :skipped => []}+
14
+ # where all subentries correspond to template generation statuses.
15
+ attr_reader :tried
16
+
17
+ # Creates a model generator configured by given context +ctx+.
18
+ def initialize(ctx)
19
+ @ctx = ctx
20
+ @tried = {}
21
+ @models = {}
22
+ end
23
+
24
+ # Loads all models into memory from a given +schema_root+ and prepares
25
+ # their own properties to be rendered in class templates.
26
+ def read_models(schema_root)
27
+ Dir.entries(schema_root).reject {|path| path =~ /\A\.{1,2}\Z/ }.each do |namespace_path|
28
+ namespace_name = namespace_path.split('_').map {|s| s.capitalize }.join
29
+ namespace = @models[namespace_name] = {}
30
+ Dir.glob(File.join(schema_root, namespace_path, 'models', '*.yml')) do |file_path|
31
+ model_name = File.basename(file_path, '.yml').split('_').map {|s| s.capitalize }.join
32
+ meta_model = namespace[model_name] = Tanuki::MetaModel.new(
33
+ namespace_name,
34
+ model_name,
35
+ YAML.load_file(file_path),
36
+ @models
37
+ )
38
+ meta_model.process!
39
+ if @tried.include? namespace_model_name = "#{namespace_name}.#{model_name}"
40
+ next
41
+ else
42
+ @tried[namespace_model_name] = {:generated => [], :failed => [], :skipped => []}
43
+ @tried["#{namespace_model_name} (base)"] = {:generated => [], :failed => [], :skipped => []}
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # Generates all models that were read from a given +schema_root+.
50
+ # Generation paths are determined by context given in the constructor.
51
+ def generate(schema_root)
52
+ read_models schema_root
53
+ process_models
54
+ generate_classes
55
+ end
56
+
57
+ # Renders a model class by applying +class_type+ template
58
+ # to a given +meta_model+. Classes are splitted in two parts:
59
+ # * user-extendable part (+base=false+), which resides in +ctx.app_root+,
60
+ # * other part with framework-specific code (+base=true+), which resides in +ctx.gen_root+.
61
+ # +namespace_model_name+ is used as a label for error report collection
62
+ # in Tanuki::ModelGenerator#tried hash.
63
+ def generate_class(meta_model, namespace_model_name, class_type, base)
64
+ class_name = meta_model.class_name_for class_type
65
+ namespace_model_name += ' (base)' if base
66
+ path = Tanuki::Loader.class_path(class_name, base ? @ctx.gen_root : @ctx.app_root)
67
+ if base || !(File.exists? path)
68
+ begin
69
+ dirname = File.dirname(path)
70
+ FileUtils.mkdir_p dirname unless File.directory? dirname
71
+ File.open path, 'w' do |file|
72
+ writer = proc {|out| file.print out.to_s }
73
+ Loader.run_template({}, meta_model, class_type).call(writer, @ctx)
74
+ end
75
+ @tried[namespace_model_name][:generated] << class_name
76
+ rescue
77
+ @tried[namespace_model_name][:failed] << class_name
78
+ end
79
+ else
80
+ @tried[namespace_model_name][:skipped] << class_name
81
+ end
82
+ end
83
+
84
+ # Iterates over all loaded model definitions and renders
85
+ # all available class templates for them.
86
+ def generate_classes
87
+ @models.each do |namespace_name, namespace |
88
+ namespace.each do |model_name, meta_model|
89
+ namespace_model_name = "#{namespace_name}.#{model_name}"
90
+ {
91
+ :model => false,
92
+ :model_base => true,
93
+ :manager => false,
94
+ :manager_base => true
95
+ }.each do |class_type, base|
96
+ generate_class meta_model, namespace_model_name, class_type, base
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ # Iterates over all loaded model definitions, giving them a chance to meet each other
103
+ # and make any cross-model assumptions (like names for foreign keys).
104
+ def process_models
105
+ @models.each do |namespace_name, namespace |
106
+ namespace.each do |model_name, meta_model|
107
+ meta_model.process_relations!
108
+ end
109
+ end
110
+ end
111
+
112
+ end # ModelGenerator
113
+
114
+ end # Tanuki