tanuki 0.1.3 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +6 -3
- data/app/tanuki/attribute/attribute.rb +19 -0
- data/app/tanuki/base/base.rb +3 -0
- data/app/tanuki/controller/controller.rb +1 -1
- data/app/tanuki/controller/link.thtml +6 -6
- data/app/tanuki/meta_model/manager.ttxt +2 -0
- data/app/tanuki/meta_model/manager_base.ttxt +2 -0
- data/app/tanuki/meta_model/meta_model.rb +3 -0
- data/app/tanuki/meta_model/model.ttxt +2 -0
- data/app/tanuki/meta_model/model_base.ttxt +12 -0
- data/app/tanuki/model/model.rb +3 -0
- data/app/tanuki/page/missing/default.thtml +61 -2
- data/app/user/page/index/default.thtml +120 -120
- data/app/user/page/index/index.rb +1 -1
- data/bin/tanuki +185 -53
- data/config/common.rb +7 -0
- data/config/common_application.rb +31 -0
- data/config/development_application.rb +8 -0
- data/config/production_application.rb +2 -0
- data/lib/tanuki/application.rb +15 -19
- data/lib/tanuki/argument/base.rb +2 -2
- data/lib/tanuki/argument/integer.rb +2 -2
- data/lib/tanuki/argument/integer_range.rb +3 -3
- data/lib/tanuki/argument/string.rb +2 -2
- data/lib/tanuki/argument.rb +3 -3
- data/lib/tanuki/{controller_behavior.rb → behavior/controller_behavior.rb} +22 -21
- data/lib/tanuki/behavior/meta_model_behavior.rb +43 -0
- data/lib/tanuki/behavior/model_behavior.rb +112 -0
- data/lib/tanuki/{object_behavior.rb → behavior/object_behavior.rb} +6 -6
- data/lib/tanuki/configurator.rb +39 -76
- data/lib/tanuki/context.rb +31 -19
- data/lib/tanuki/{module_extensions.rb → extensions/module.rb} +3 -3
- data/lib/tanuki/extensions/object.rb +12 -0
- data/lib/tanuki/extensions/rack/static_dir.rb +18 -0
- data/lib/tanuki/i18n.rb +3 -1
- data/lib/tanuki/launcher.rb +3 -2
- data/lib/tanuki/loader.rb +37 -46
- data/lib/tanuki/template_compiler.rb +8 -7
- data/lib/tanuki/version.rb +1 -1
- data/lib/tanuki.rb +14 -8
- data/schema/tanuki/models/controller.yml +2 -3
- data/schema/tanuki/models/page.yml +7 -8
- metadata +35 -27
- data/app/tanuki/object/object.rb +0 -3
@@ -0,0 +1,31 @@
|
|
1
|
+
load_config :common
|
2
|
+
|
3
|
+
# Rack middleware
|
4
|
+
use Rack::Head
|
5
|
+
use Rack::StaticDir, 'public'
|
6
|
+
|
7
|
+
# Server
|
8
|
+
set :server, [:thin, :mongrel, :webrick]
|
9
|
+
set :host, '0.0.0.0'
|
10
|
+
set :port, 3000
|
11
|
+
|
12
|
+
# Default controllers
|
13
|
+
set :root_page, ::User_Page_Index
|
14
|
+
set :missing_page, ::Tanuki_Page_Missing
|
15
|
+
|
16
|
+
# Internationalization
|
17
|
+
set :i18n, false
|
18
|
+
set :language, nil
|
19
|
+
set :language_fallback, {}
|
20
|
+
set :languages, proc { language_fallback.keys }
|
21
|
+
set :best_language, proc {|lngs| language_fallback[language].each {|lng| return lng if lngs.include? lng }; nil }
|
22
|
+
set :best_translation, proc {|trn| language_fallback[language].each {|lng| return trn[lng] if trn.include? lng }; nil }
|
23
|
+
|
24
|
+
# Visitors
|
25
|
+
visitor :string do s = ''; proc {|out| s << out.to_s } end
|
26
|
+
|
27
|
+
# Argument associations
|
28
|
+
argument Fixnum, Argument::Integer
|
29
|
+
argument Bignum, Argument::Integer
|
30
|
+
argument Range, Argument::IntegerRange
|
31
|
+
argument String, Argument::String
|
data/lib/tanuki/application.rb
CHANGED
@@ -4,37 +4,33 @@ module Tanuki
|
|
4
4
|
# It contains core application functionality like configuration, request handling and view management.
|
5
5
|
class Application
|
6
6
|
|
7
|
-
@context = Loader.context = Context.
|
8
|
-
@rack_middleware =
|
7
|
+
@context = (Loader.context = Context).child
|
8
|
+
@rack_middleware = []
|
9
9
|
|
10
10
|
class << self
|
11
11
|
|
12
|
-
# Removes a given middleware from the Rack middleware pipeline
|
12
|
+
# Removes all occurences of a given +middleware+ from the Rack middleware pipeline.
|
13
13
|
def discard(middleware)
|
14
|
-
@rack_middleware.
|
14
|
+
@rack_middleware.delete_if {|item| item[0] == middleware }
|
15
15
|
end
|
16
16
|
|
17
|
-
# Runs the application with current settings
|
17
|
+
# Runs the application with current settings.
|
18
18
|
def run
|
19
19
|
rack_builder = Rack::Builder.new
|
20
|
-
@rack_middleware.each {|
|
20
|
+
@rack_middleware.each {|item| rack_builder.use(item[0], *item[1], &item[2]) }
|
21
21
|
rack_builder.run(rack_app)
|
22
22
|
srv = available_server
|
23
|
-
puts "A wild Tanuki appears!
|
23
|
+
puts "A wild Tanuki appears! Press Ctrl-C to set it free.",
|
24
|
+
"You used #{srv.name.gsub(/.*::/, '')} at #{@context.host}:#{@context.port}."
|
24
25
|
srv.run rack_builder.to_app, :Host => @context.host, :Port => @context.port
|
25
26
|
end
|
26
27
|
|
27
|
-
#
|
28
|
-
def set(option, value)
|
29
|
-
@context.send("#{option}=".to_sym, value)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Adds a given middleware to the Rack middleware pipeline
|
28
|
+
# Adds a given +middleware+ with optional +args+ and +block+ to the Rack middleware pipeline.
|
33
29
|
def use(middleware, *args, &block)
|
34
|
-
@rack_middleware[middleware
|
30
|
+
@rack_middleware << [middleware, args, block]
|
35
31
|
end
|
36
32
|
|
37
|
-
# Adds a template visitor block
|
33
|
+
# Adds a template visitor +block+. This +block+ must return a +Proc+.
|
38
34
|
#
|
39
35
|
# visitor :escape do
|
40
36
|
# s = ''
|
@@ -50,7 +46,7 @@ module Tanuki
|
|
50
46
|
# <%_escape escaped_view %>
|
51
47
|
# <%_printf('<div>%s</div>') formatted_view %>
|
52
48
|
def visitor(sym, &block)
|
53
|
-
|
49
|
+
BaseBehavior.instance_eval { define_method "#{sym}_visitor".to_sym, &block }
|
54
50
|
end
|
55
51
|
|
56
52
|
private
|
@@ -67,7 +63,7 @@ module Tanuki
|
|
67
63
|
raise "servers #{@context.server.join(', ')} not found"
|
68
64
|
end
|
69
65
|
|
70
|
-
# Returns an array of template outputs for controller ctrl in context
|
66
|
+
# Returns an array of template outputs for controller +ctrl+ in context +request_ctx+.
|
71
67
|
def build_body(ctrl, request_ctx)
|
72
68
|
arr = []
|
73
69
|
Launcher.new(ctrl, request_ctx).each &proc {|out| arr << out.to_s }
|
@@ -84,14 +80,14 @@ module Tanuki
|
|
84
80
|
proc do |env|
|
85
81
|
request_ctx = ctx.child
|
86
82
|
request_ctx.templates = {}
|
87
|
-
if match = env['
|
83
|
+
if match = env['PATH_INFO'].match(/^(.+)(?<!\$)\/$/)
|
88
84
|
loc = match[1]
|
89
85
|
loc << "?#{env['QUERY_STRING']}" unless env['QUERY_STRING'].empty?
|
90
86
|
[301, {'Location' => loc, 'Content-Type' => 'text/html; charset=utf-8'}, []]
|
91
87
|
else
|
92
88
|
request_ctx.env = env
|
93
89
|
result = ::Tanuki::ControllerBehavior.dispatch(request_ctx, ctx.i18n ? ::Tanuki::I18n : ctx.root_page,
|
94
|
-
Rack::Utils.unescape(env['
|
90
|
+
Rack::Utils.unescape(env['PATH_INFO']).force_encoding('UTF-8'))
|
95
91
|
case result[:type]
|
96
92
|
when :redirect then
|
97
93
|
[302, {'Location' => result[:location], 'Content-Type' => 'text/html; charset=utf-8'}, []]
|
data/lib/tanuki/argument/base.rb
CHANGED
@@ -6,7 +6,7 @@ module Tanuki
|
|
6
6
|
|
7
7
|
attr_reader :default, :value
|
8
8
|
|
9
|
-
# Initializes the argument with a default value.
|
9
|
+
# Initializes the argument with a +default+ value.
|
10
10
|
def initialize(default)
|
11
11
|
@value = @default = default
|
12
12
|
end
|
@@ -16,7 +16,7 @@ module Tanuki
|
|
16
16
|
@value.to_s
|
17
17
|
end
|
18
18
|
|
19
|
-
# Sets the value with required rules.
|
19
|
+
# Sets the value to +obj+ with required rules.
|
20
20
|
def value=(obj)
|
21
21
|
@value = to_value(obj)
|
22
22
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module Tanuki
|
2
2
|
module Argument
|
3
3
|
|
4
|
-
# Tanuki::Argument::Integer is a class for Integer arguments.
|
4
|
+
# Tanuki::Argument::Integer is a class for +Integer+ arguments.
|
5
5
|
class Integer < Base
|
6
6
|
|
7
|
-
# Returns argument value from
|
7
|
+
# Returns argument value from an object +obj+.
|
8
8
|
def to_value(obj)
|
9
9
|
begin Kernel::Integer obj rescue @default end
|
10
10
|
end
|
@@ -1,16 +1,16 @@
|
|
1
1
|
module Tanuki
|
2
2
|
module Argument
|
3
3
|
|
4
|
-
# Tanuki::Argument::IntegerRange is a class for Integer arguments with a certain value range.
|
4
|
+
# Tanuki::Argument::IntegerRange is a class for +Integer+ arguments with a certain value range.
|
5
5
|
class IntegerRange < Integer
|
6
6
|
|
7
|
-
# Initializes the argument with a default value and allowed value range
|
7
|
+
# Initializes the argument with a +default+ value and allowed value +range+.
|
8
8
|
def initialize(range, default=nil)
|
9
9
|
super(default ? default : range.first)
|
10
10
|
@range = range
|
11
11
|
end
|
12
12
|
|
13
|
-
# Returns argument value from
|
13
|
+
# Returns argument value from an object +obj+.
|
14
14
|
def to_value(obj)
|
15
15
|
i = super(obj)
|
16
16
|
@range.include?(i) ? i : @default
|
@@ -1,10 +1,10 @@
|
|
1
1
|
module Tanuki
|
2
2
|
module Argument
|
3
3
|
|
4
|
-
# Tanuki::Argument::String is a class for String arguments.
|
4
|
+
# Tanuki::Argument::String is a class for +String+ arguments.
|
5
5
|
class String < Base
|
6
6
|
|
7
|
-
# Returns argument value from
|
7
|
+
# Returns argument value from an object +obj+.
|
8
8
|
def to_value(obj)
|
9
9
|
obj.to_s
|
10
10
|
end
|
data/lib/tanuki/argument.rb
CHANGED
@@ -12,12 +12,12 @@ module Tanuki
|
|
12
12
|
|
13
13
|
class << self
|
14
14
|
|
15
|
-
#
|
15
|
+
# Removes argument association for a given type class +klass+.
|
16
16
|
def delete(klass)
|
17
17
|
@assoc.delete(klass)
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
20
|
+
# Associates a given type class +klass+ with an argument class +arg_class+.
|
21
21
|
def store(klass, arg_class)
|
22
22
|
warn "Tanuki::Argument::Base is not an ancestor of `#{arg_class}'" unless arg_class.ancestors.include? Argument::Base
|
23
23
|
@assoc[klass] = arg_class
|
@@ -25,7 +25,7 @@ module Tanuki
|
|
25
25
|
|
26
26
|
alias_method :[], :store
|
27
27
|
|
28
|
-
#
|
28
|
+
# Converts a given type object +obj+ to an argument object with optional +args+.
|
29
29
|
def to_argument(obj, *args)
|
30
30
|
if @assoc.include?(klass = obj.class)
|
31
31
|
@assoc[klass].new(obj, *args)
|
@@ -9,7 +9,7 @@ module Tanuki
|
|
9
9
|
internal_attr_reader :model, :logical_parent, :link
|
10
10
|
internal_attr_accessor :logical_child, :visual_child
|
11
11
|
|
12
|
-
#
|
12
|
+
# Creates new controller with context +ctx+, +logical_parent+ controller, +route_part+ definitions and a +model+.
|
13
13
|
def initialize(ctx, logical_parent, route_part, model=nil)
|
14
14
|
@_configured = false
|
15
15
|
@_ctx = ctx
|
@@ -29,7 +29,7 @@ module Tanuki
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
# Invoked with route args when current route is initialized.
|
32
|
+
# Invoked with route +args+ when current route is initialized.
|
33
33
|
def initialize_route(*args)
|
34
34
|
end
|
35
35
|
|
@@ -38,7 +38,7 @@ module Tanuki
|
|
38
38
|
@_ctx
|
39
39
|
end
|
40
40
|
|
41
|
-
# Initializes and retrieves child route
|
41
|
+
# Initializes and retrieves child controller on +route+. Searches static, dynamic, and ghost routes (in that order).
|
42
42
|
def [](route, *args)
|
43
43
|
byname = (args.length == 1 and args[0].is_a? Hash)
|
44
44
|
ensure_configured!
|
@@ -77,12 +77,12 @@ module Tanuki
|
|
77
77
|
@_cache[key] = child # Thread safe (possible overwrite, but within consistent state)
|
78
78
|
end
|
79
79
|
|
80
|
-
#
|
80
|
+
# Returns true, if controller is active.
|
81
81
|
def active?
|
82
82
|
@_active
|
83
83
|
end
|
84
84
|
|
85
|
-
# Retrieves child
|
85
|
+
# Retrieves child controller class on +route+. Searches static, dynamic, and ghost routes (in that order).
|
86
86
|
def child_class(route)
|
87
87
|
ensure_configured!
|
88
88
|
args = []
|
@@ -108,11 +108,11 @@ module Tanuki
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
-
# Invoked when controller
|
111
|
+
# Invoked when controller needs to be configured.
|
112
112
|
def configure
|
113
113
|
end
|
114
114
|
|
115
|
-
#
|
115
|
+
# Returns true, if controller is current.
|
116
116
|
def current?
|
117
117
|
@_current
|
118
118
|
end
|
@@ -122,6 +122,7 @@ module Tanuki
|
|
122
122
|
nil
|
123
123
|
end
|
124
124
|
|
125
|
+
# Calls +block+ once for each visible child controller on static or dynamic routes, passing it as a parameter.
|
125
126
|
def each(&block)
|
126
127
|
return Enumerator.new(self) unless block_given?
|
127
128
|
ensure_configured!
|
@@ -157,15 +158,15 @@ module Tanuki
|
|
157
158
|
nil
|
158
159
|
end
|
159
160
|
|
160
|
-
# Returns the link to the current controller, switching the active controller on the respective path level to self
|
161
|
+
# Returns the link to the current controller, switching the active controller on the respective path level to +self+.
|
161
162
|
def forward_link
|
162
|
-
uri_parts = @_ctx.env['
|
163
|
+
uri_parts = @_ctx.env['PATH_INFO'].split(/(?<!\$)\//)
|
163
164
|
link_parts = link.split(/(?<!\$)\//)
|
164
165
|
link_parts.each_index {|i| uri_parts[i] = link_parts[i] }
|
165
166
|
uri_parts.join('/') << ((qs = @_ctx.env['QUERY_STRING']).empty? ? '' : "?#{qs}")
|
166
167
|
end
|
167
168
|
|
168
|
-
# Returns the number of
|
169
|
+
# Returns the number of visible child controllers on static and dynamic routes.
|
169
170
|
def length
|
170
171
|
if @_child_collection_defs.length > 0
|
171
172
|
if @_length_is_valid
|
@@ -179,7 +180,7 @@ module Tanuki
|
|
179
180
|
end
|
180
181
|
end
|
181
182
|
|
182
|
-
#
|
183
|
+
# Invoked when child controller context needs to be processed before initializing.
|
183
184
|
def process_child_context(ctx, route)
|
184
185
|
ctx
|
185
186
|
end
|
@@ -198,21 +199,21 @@ module Tanuki
|
|
198
199
|
|
199
200
|
private
|
200
201
|
|
201
|
-
# Defines a child of class klass on route with model
|
202
|
+
# Defines a child of class +klass+ on +route+ with +model+, optionally +hidden+.
|
202
203
|
def has_child(klass, route, model=nil, hidden=false)
|
203
204
|
@_child_defs[route] = {:class => klass, :model => model, :hidden => hidden}
|
204
205
|
@_length += 1 unless hidden
|
205
206
|
self
|
206
207
|
end
|
207
208
|
|
208
|
-
# Defines a child collection of type parse_regexp
|
209
|
+
# Defines a child collection of type +parse_regexp+, formatted back by +format_string+.
|
209
210
|
def has_child_collection(parse_regexp, format_string, child_def_fetcher)
|
210
211
|
@_child_defs[parse_regexp] = @_child_collection_defs.size
|
211
212
|
@_child_collection_defs << {:parse => parse_regexp, :format => format_string, :fetcher => child_def_fetcher}
|
212
213
|
@_length_is_valid = false
|
213
214
|
end
|
214
215
|
|
215
|
-
# Invoked for route with args when a route is missing.
|
216
|
+
# Invoked for +route+ with +args+ when a route is missing. This hook can be used to make ghost routes.
|
216
217
|
def missing_route(route, *args)
|
217
218
|
@_ctx.missing_page.new(@_ctx, self, {:route => route, :args => []})
|
218
219
|
end
|
@@ -225,12 +226,12 @@ module Tanuki
|
|
225
226
|
@_arg_defs ||= superclass.arg_defs.dup
|
226
227
|
end
|
227
228
|
|
228
|
-
# Escapes a given string for use in links.
|
229
|
+
# Escapes characters +chrs+ and encodes a given string +s+ for use in links.
|
229
230
|
def escape(s, chrs)
|
230
231
|
s ? Rack::Utils.escape(s.to_s.gsub(/[\$#{chrs}]/, '$\0')) : nil
|
231
232
|
end
|
232
233
|
|
233
|
-
# Extracts arguments, initializing default values beforehand. Searches md hash for default value overrides.
|
234
|
+
# Extracts arguments, initializing default values beforehand. Searches +md+ hash for default value overrides.
|
234
235
|
def extract_args(md)
|
235
236
|
res = []
|
236
237
|
arg_defs.each_pair do |name, arg|
|
@@ -239,7 +240,7 @@ module Tanuki
|
|
239
240
|
res
|
240
241
|
end
|
241
242
|
|
242
|
-
# Builds link from
|
243
|
+
# Builds link from controller +ctrl+ to a given route.
|
243
244
|
def grow_link(ctrl, route_part, arg_defs)
|
244
245
|
own_link = escape(route_part[:route], '\/:') << route_part[:args].map do |k, v|
|
245
246
|
arg_defs[k][:arg].default == v ? '' : ":#{escape(k, '\/:-')}-#{escape(v, '\/:')}"
|
@@ -247,7 +248,7 @@ module Tanuki
|
|
247
248
|
"#{ctrl.link == '/' ? '' : ctrl.link}/#{own_link}"
|
248
249
|
end
|
249
250
|
|
250
|
-
# Defines an argument with name
|
251
|
+
# Defines an argument with a +name+, derived from type +obj+ with additional +args+.
|
251
252
|
def has_arg(name, obj, *args)
|
252
253
|
# TODO Ensure thread safety
|
253
254
|
arg_defs[name] = {:arg => Argument.to_argument(obj, *args), :index => @_arg_defs.size}
|
@@ -264,7 +265,7 @@ module Tanuki
|
|
264
265
|
|
265
266
|
class << self
|
266
267
|
|
267
|
-
# Dispathes route chain in context ctx on request_path
|
268
|
+
# Dispathes route chain in context +ctx+ on +request_path+, starting with controller +klass+.
|
268
269
|
def dispatch(ctx, klass, request_path)
|
269
270
|
parts = parse_path(request_path)
|
270
271
|
curr = root_ctrl = klass.new(ctx, nil, nil, true)
|
@@ -297,7 +298,7 @@ module Tanuki
|
|
297
298
|
|
298
299
|
private
|
299
300
|
|
300
|
-
# Parses path to return route name and arguments.
|
301
|
+
# Parses +path+ to return route name and arguments.
|
301
302
|
def parse_path(path)
|
302
303
|
path[1..-1].split(/(?<!\$)\//).map do |s|
|
303
304
|
arr = s.gsub('$/', '/').split(/(?<!\$):/)
|
@@ -312,7 +313,7 @@ module Tanuki
|
|
312
313
|
end
|
313
314
|
end
|
314
315
|
|
315
|
-
#
|
316
|
+
# Unescapes a given link part for internal use.
|
316
317
|
def unescape(s)
|
317
318
|
s ? s.gsub(/\$([\/\$:-])/, '\1') : nil
|
318
319
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Tanuki
|
2
|
+
|
3
|
+
module MetaModelBehavior
|
4
|
+
|
5
|
+
def initialize(namespace, name, data)
|
6
|
+
@namespace = namespace
|
7
|
+
@name = name
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def class_name_for(class_type)
|
12
|
+
case class_type
|
13
|
+
when :model, :model_base then "#{@namespace}_Model_#{@name}"
|
14
|
+
when :manager, :manager_base then "#{@namespace}_Manager_#{@name}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def key
|
19
|
+
if @data['key'].nil?
|
20
|
+
[]
|
21
|
+
elsif @data['key'].is_a? Array
|
22
|
+
@data['key'].map {|item| qualified_name(item) }
|
23
|
+
elsif @data['key'].is_a? String
|
24
|
+
[qualified_name(@data['key'])]
|
25
|
+
else
|
26
|
+
raise "key for model #{@namespace}.#{@name} is invalid"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def qualified_name(field_name)
|
31
|
+
parts = field_name.split('.')
|
32
|
+
if parts.length == 1
|
33
|
+
":#{field_name}"
|
34
|
+
elsif parts.length == 2
|
35
|
+
":#{parts[1]}.qualify(:#{parts[0]}"
|
36
|
+
else
|
37
|
+
raise "field name for model #{@namespace}.#{@name} is invalid"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end # end MetaModelBehavior
|
42
|
+
|
43
|
+
end # end Tanuki
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Tanuki
|
2
|
+
|
3
|
+
module ModelBehavior
|
4
|
+
|
5
|
+
def initialize(data = {}, lazy = false)
|
6
|
+
@_data = data
|
7
|
+
@_loaded = !lazy
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](attribute)
|
11
|
+
ensure_loaded!
|
12
|
+
self.class[attribute].get(@_data)
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(attribute, value)
|
16
|
+
@_errors ||= {}
|
17
|
+
@_original ||= {}
|
18
|
+
begin
|
19
|
+
@_original[attribute] = self[attribute] unless @_original.include? attribute
|
20
|
+
self.class[attribute].set(@_data, value)
|
21
|
+
@_errors.delete(attribute)
|
22
|
+
rescue
|
23
|
+
@_errors[attribute] = {:value => value, :error => $!}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def internals_get(attribute)
|
28
|
+
self.class[attribute].internals_get(@_data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def internals_set(attribute, internal_value)
|
32
|
+
@_errors ||= {}
|
33
|
+
internals_set(self.class[attribute], @_data)
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_updates
|
37
|
+
@_original ||= {}
|
38
|
+
original_data = {}
|
39
|
+
self.class.attributes.each_pair do |name, attrib|
|
40
|
+
attrib.set(original_data, @_original[name])
|
41
|
+
end
|
42
|
+
updates = {}
|
43
|
+
original_data.each_pair do |field, value|
|
44
|
+
updates[field] = data[field] if data[field] != original_data[field]
|
45
|
+
end
|
46
|
+
updates
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_error(attribute)
|
50
|
+
@_errors ||= {}
|
51
|
+
@_errors[attribute]
|
52
|
+
end
|
53
|
+
|
54
|
+
def invalid?(attribute)
|
55
|
+
@_errors.include? attribute
|
56
|
+
end
|
57
|
+
|
58
|
+
def has_errors?
|
59
|
+
@_errors ||= {}
|
60
|
+
@_errors == {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def errors
|
64
|
+
@_errors ||= {}
|
65
|
+
@_errors
|
66
|
+
end
|
67
|
+
|
68
|
+
module ClassMethods
|
69
|
+
|
70
|
+
def create(data, ctx, lazy = false) # IDENTITY TRACKING AND LAZY LOADING
|
71
|
+
entity_key = extract_key(data)
|
72
|
+
key = [self, entity_key] #extract_key is generated ad hoc by model compiler!
|
73
|
+
if cached = ctx.entity_cache[key]
|
74
|
+
cached
|
75
|
+
else
|
76
|
+
ctx.entity_cache[key] = get(*entity_key) # get is generated Ad Hoc by model compiler
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def has_attribute(attribute, attr_def)
|
81
|
+
@_attributes ||= superclass.instance_variable_get(:@_attributes).dup
|
82
|
+
@_attributes[attribute] = attr_def
|
83
|
+
end
|
84
|
+
|
85
|
+
def [](attribute)
|
86
|
+
@_attributes[attribute]
|
87
|
+
end
|
88
|
+
|
89
|
+
def has_reference(attribute, reference_def)
|
90
|
+
@_references ||= superclass.instance_variable_get(:@_references).dup
|
91
|
+
@_references[attribute] = reference_def
|
92
|
+
end
|
93
|
+
|
94
|
+
# Prepares the extended module.
|
95
|
+
def self.extended(mod)
|
96
|
+
mod.instance_variable_set(:@_attributes, {})
|
97
|
+
mod.instance_variable_set(:@_references, {})
|
98
|
+
end
|
99
|
+
|
100
|
+
end # end ClassMethods
|
101
|
+
|
102
|
+
class << self
|
103
|
+
|
104
|
+
def included(mod)
|
105
|
+
mod.extend ClassMethods
|
106
|
+
end
|
107
|
+
|
108
|
+
end # end class << self
|
109
|
+
|
110
|
+
end # end ModelBehaviour
|
111
|
+
|
112
|
+
end # end Tanuki
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module Tanuki
|
2
2
|
|
3
|
-
# Tanuki::
|
3
|
+
# Tanuki::BaseBehavior contains basic methods for a templatable object.
|
4
4
|
# In is included in the base framework object class.
|
5
|
-
module
|
5
|
+
module BaseBehavior
|
6
6
|
|
7
|
-
# Shortcut to Tanuki::Loader
|
7
|
+
# Shortcut to Tanuki::Loader::has_template?. Used internally by templates.
|
8
8
|
def _has_tpl(ctx, klass, sym)
|
9
9
|
Tanuki::Loader.has_template?(ctx.templates, klass, sym)
|
10
10
|
end
|
11
11
|
|
12
|
-
# Shortcut to Tanuki::Loader
|
12
|
+
# Shortcut to Tanuki::Loader::run_template. Used internally by templates.
|
13
13
|
def _run_tpl(ctx, obj, sym, *args, &block)
|
14
14
|
Tanuki::Loader.run_template(ctx.templates, obj, sym, *args, &block)
|
15
15
|
end
|
@@ -19,7 +19,7 @@ module Tanuki
|
|
19
19
|
ctx
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
22
|
+
# Allows to return template blocks. E. g. returns +foobar+ template block when +foobar_view+ method is called.
|
23
23
|
def method_missing(sym, *args, &block)
|
24
24
|
if matches = sym.to_s.match(/^(.*)_view$/)
|
25
25
|
return Tanuki::Loader.run_template({}, self, matches[1].to_sym, *args, &block)
|
@@ -27,6 +27,6 @@ module Tanuki
|
|
27
27
|
super
|
28
28
|
end
|
29
29
|
|
30
|
-
end # end
|
30
|
+
end # end BaseBehavior
|
31
31
|
|
32
32
|
end # end Tanuki
|