tanuki 0.1.3 → 0.2.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.
- 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
|