tanuki 0.2.1 → 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/app/tanuki/meta_model/manager.ttxt +1 -1
- data/app/tanuki/meta_model/model.ttxt +1 -1
- data/app/tanuki/meta_model/model_base.ttxt +2 -2
- data/bin/tanuki +34 -19
- data/config/common_application.rb +2 -0
- data/lib/tanuki/application.rb +41 -6
- data/lib/tanuki/behavior/controller_behavior.rb +24 -15
- data/lib/tanuki/behavior/meta_model_behavior.rb +85 -10
- data/lib/tanuki/behavior/model_behavior.rb +35 -26
- data/lib/tanuki/configurator.rb +0 -2
- data/lib/tanuki/context.rb +59 -34
- data/lib/tanuki/extensions/rack/builder.rb +26 -0
- data/lib/tanuki/extensions/rack/server.rb +18 -0
- data/lib/tanuki/extensions/rack/static_dir.rb +1 -1
- data/lib/tanuki/i18n.rb +1 -1
- data/lib/tanuki/version.rb +1 -1
- data/lib/tanuki.rb +3 -0
- metadata +44 -17
@@ -1,2 +1,2 @@
|
|
1
|
-
class <%= class_name_for :manager %>
|
1
|
+
class <%= class_name_for :manager %>
|
2
2
|
end
|
@@ -1,2 +1,2 @@
|
|
1
|
-
class <%= class_name_for :model %>
|
1
|
+
class <%= class_name_for :model %>
|
2
2
|
end
|
@@ -3,10 +3,10 @@ class <%= class_name_for :model_base %> < Tanuki_Base
|
|
3
3
|
|
4
4
|
class << self
|
5
5
|
def extract_key(data)
|
6
|
-
[<% key.each do |
|
6
|
+
[<% @key.each do |qualified| %>data[<%= qualified[0].inspect %>][<%= qualified[1].inspect %>], <% end %>]
|
7
7
|
end
|
8
8
|
|
9
|
-
def get(ctx
|
9
|
+
def get(ctx, *args)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
data/bin/tanuki
CHANGED
@@ -35,8 +35,9 @@ module Tanuki
|
|
35
35
|
|
36
36
|
def generate(cwd)
|
37
37
|
version unless @in_repl
|
38
|
-
cwd = File.expand_path(cwd
|
38
|
+
cwd = cwd ? File.expand_path(cwd) : Dir.pwd
|
39
39
|
puts "Working directory is: #{cwd}\nTo specify another: `generate <path>'"
|
40
|
+
require 'active_support/inflector'
|
40
41
|
require 'yaml'
|
41
42
|
require 'fileutils'
|
42
43
|
require 'tanuki/extensions/object'
|
@@ -48,12 +49,17 @@ module Tanuki
|
|
48
49
|
require 'tanuki/loader'
|
49
50
|
require 'tanuki/template_compiler'
|
50
51
|
Loader.context = ctx = Context
|
51
|
-
|
52
|
-
cfg.
|
52
|
+
default_root = File.expand_path(File.join('..', '..'), __FILE__)
|
53
|
+
cfg = Configurator.new(Context, cwd)
|
54
|
+
if cwd != default_root
|
55
|
+
cfg.config_root = File.join(default_root, 'config')
|
56
|
+
cfg.load_config :common
|
57
|
+
end
|
53
58
|
cfg.config_root = File.join(cwd, 'config')
|
54
|
-
cfg.load_config :common,
|
59
|
+
cfg.load_config :common, cwd != default_root
|
55
60
|
puts "\n looking for models"
|
56
61
|
@tried = {}
|
62
|
+
@models = {}
|
57
63
|
local_schema_root = File.expand_path(File.join('..', '..', 'schema'), __FILE__)
|
58
64
|
generate_in(ctx.schema_root, ctx)
|
59
65
|
generate_in(local_schema_root, ctx) if ctx.schema_root != local_schema_root
|
@@ -65,15 +71,34 @@ module Tanuki
|
|
65
71
|
|
66
72
|
def generate_in(schema_root, ctx)
|
67
73
|
Dir.entries(schema_root)[2..-1].each do |namespace_path|
|
68
|
-
|
74
|
+
namespace_name = namespace_path.split('_').map {|s| s.capitalize }.join
|
75
|
+
namespace = @models[namespace_name] = {}
|
69
76
|
Dir.glob(File.join(schema_root, namespace_path, 'models', '*.yml')) do |file_path|
|
70
77
|
model_name = File.basename(file_path, '.yml').split('_').map {|s| s.capitalize }.join
|
71
|
-
|
78
|
+
meta_model = namespace[model_name] = Tanuki_MetaModel.new(
|
79
|
+
namespace_name,
|
80
|
+
model_name,
|
81
|
+
YAML.load_file(file_path),
|
82
|
+
@models
|
83
|
+
)
|
84
|
+
meta_model.process!
|
85
|
+
if @tried.include? namespace_model_name = "#{namespace_name}.#{model_name}"
|
72
86
|
next
|
73
87
|
else
|
74
88
|
@tried[namespace_model_name] = {:generated => [], :failed => [], :skipped => []}
|
75
89
|
end
|
76
|
-
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@models.each do |namespace_name, namespace |
|
94
|
+
namespace.each do |model_name, meta_model|
|
95
|
+
meta_model.process_relations!
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@models.each do |namespace_name, namespace |
|
100
|
+
namespace.each do |model_name, meta_model|
|
101
|
+
namespace_model_name = "#{namespace_name}.#{model_name}"
|
77
102
|
{
|
78
103
|
:model => false,
|
79
104
|
:model_base => true,
|
@@ -136,19 +161,9 @@ module Tanuki
|
|
136
161
|
|
137
162
|
def server(env=nil)
|
138
163
|
env = env ? env.to_sym : :development
|
139
|
-
puts %{Calling for a Tanuki in "#{
|
164
|
+
puts %{Calling for a Tanuki in "#{Dir.pwd}"}
|
140
165
|
version unless @in_repl
|
141
166
|
require 'tanuki'
|
142
|
-
allow_run = false
|
143
|
-
begin
|
144
|
-
@cfg = Configurator.new(Context, pwd, File.expand_path(File.join('..', '..', 'config'), __FILE__))
|
145
|
-
@cfg.load_config(([:development, :production].include? env) ? :"#{env}_application" : :common_application)
|
146
|
-
@cfg.config_root = File.join(pwd, 'config')
|
147
|
-
@cfg.load_config :"#{env}_application", true
|
148
|
-
allow_run = true
|
149
|
-
rescue NameError => e
|
150
|
-
puts "Tanuki wanted `#{e.name}', but you didn't have any."
|
151
|
-
end
|
152
167
|
begin
|
153
168
|
Application.run
|
154
169
|
false
|
@@ -158,7 +173,7 @@ module Tanuki
|
|
158
173
|
rescue SystemCallError
|
159
174
|
puts 'Tanuki ran away! Someone else is playing here.'
|
160
175
|
true
|
161
|
-
end if
|
176
|
+
end if Application.configure(env)
|
162
177
|
end
|
163
178
|
|
164
179
|
def start_repl
|
@@ -8,6 +8,7 @@ use Rack::StaticDir, 'public'
|
|
8
8
|
set :server, [:thin, :mongrel, :webrick]
|
9
9
|
set :host, '0.0.0.0'
|
10
10
|
set :port, 3000
|
11
|
+
set :development, false
|
11
12
|
|
12
13
|
# Default controllers
|
13
14
|
set :root_page, ::User_Page_Index
|
@@ -15,6 +16,7 @@ set :missing_page, ::Tanuki_Page_Missing
|
|
15
16
|
|
16
17
|
# Internationalization
|
17
18
|
set :i18n, false
|
19
|
+
set :i18n_redirect, false
|
18
20
|
set :language, nil
|
19
21
|
set :language_fallback, {}
|
20
22
|
set :languages, proc { language_fallback.keys }
|
data/lib/tanuki/application.rb
CHANGED
@@ -9,20 +9,55 @@ module Tanuki
|
|
9
9
|
|
10
10
|
class << self
|
11
11
|
|
12
|
+
# Initializes application settings using configuration for environment +env+.
|
13
|
+
# These include settings for server, context, and middleware.
|
14
|
+
# Returns true, if configuration is successful.
|
15
|
+
def configure(env)
|
16
|
+
@environment = env
|
17
|
+
begin
|
18
|
+
default_root = File.expand_path(File.join('..', '..', '..'), __FILE__)
|
19
|
+
@cfg = Configurator.new(Context, pwd = Dir.pwd)
|
20
|
+
if pwd != default_root
|
21
|
+
@cfg.config_root = File.join(default_root, 'config')
|
22
|
+
@cfg.load_config(([:development, :production].include? env) ? :"#{env}_application" : :common_application)
|
23
|
+
end
|
24
|
+
@cfg.config_root = File.join(pwd, 'config')
|
25
|
+
@cfg.load_config :"#{env}_application", pwd != default_root
|
26
|
+
return true
|
27
|
+
rescue NameError => e
|
28
|
+
if e.name =~ /\AA-Z/
|
29
|
+
raise NameError, "missing class or module for constant `#{e.name}'", e.backtrace
|
30
|
+
else
|
31
|
+
raise e
|
32
|
+
end
|
33
|
+
end
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# Add utilized middleware to a given Rack::Builder instance +rack_builder+.
|
38
|
+
def configure_middleware(rack_builder)
|
39
|
+
@rack_middleware.each {|item| rack_builder.use(item[0], *item[1], &item[2]) }
|
40
|
+
end
|
41
|
+
|
12
42
|
# Removes all occurences of a given +middleware+ from the Rack middleware pipeline.
|
13
43
|
def discard(middleware)
|
14
44
|
@rack_middleware.delete_if {|item| item[0] == middleware }
|
15
45
|
end
|
16
46
|
|
47
|
+
# Returns current environment +Symbol+, if application is configured.
|
48
|
+
# Returns +nil+ otherwise.
|
49
|
+
def environment
|
50
|
+
@environment ||= nil
|
51
|
+
end
|
52
|
+
|
17
53
|
# Runs the application with current settings.
|
18
54
|
def run
|
19
|
-
rack_builder = Rack::Builder.new
|
20
|
-
@rack_middleware.each {|item| rack_builder.use(item[0], *item[1], &item[2]) }
|
55
|
+
configure_middleware(rack_builder = Rack::Builder.new)
|
21
56
|
rack_builder.run(rack_app)
|
22
|
-
|
23
|
-
puts "A
|
24
|
-
"You used #{
|
25
|
-
|
57
|
+
@context.running_server = available_server
|
58
|
+
puts "A#{'n' if @environment =~ /\A[aeiou]/} #{@environment} Tanuki appears! Press Ctrl-C to set it free.",
|
59
|
+
"You used #{@context.running_server.name.gsub(/.*::/, '')} at #{@context.host}:#{@context.port}."
|
60
|
+
@context.running_server.run rack_builder.to_app, :Host => @context.host, :Port => @context.port
|
26
61
|
end
|
27
62
|
|
28
63
|
# Adds a given +middleware+ with optional +args+ and +block+ to the Rack middleware pipeline.
|
@@ -77,7 +77,7 @@ module Tanuki
|
|
77
77
|
@_cache[key] = child # Thread safe (possible overwrite, but within consistent state)
|
78
78
|
end
|
79
79
|
|
80
|
-
# Returns true
|
80
|
+
# Returns +true+, if controller is active.
|
81
81
|
def active?
|
82
82
|
@_active
|
83
83
|
end
|
@@ -112,12 +112,16 @@ module Tanuki
|
|
112
112
|
def configure
|
113
113
|
end
|
114
114
|
|
115
|
-
# Returns true
|
115
|
+
# Returns +true+, if controller is current.
|
116
116
|
def current?
|
117
117
|
@_current
|
118
118
|
end
|
119
119
|
|
120
120
|
# If set, controller navigates to a given child route by default.
|
121
|
+
# Returned object should be either +nil+ (don't navigate), or a +Hash+ with keys:
|
122
|
+
# * +:route+ is the +Symbol+ for the route
|
123
|
+
# * +:args+ contain route arguments +Hash+
|
124
|
+
# * +:redirect+ makes a 302 redirect to this route, if true (optional)
|
121
125
|
def default_route
|
122
126
|
nil
|
123
127
|
end
|
@@ -267,28 +271,33 @@ module Tanuki
|
|
267
271
|
|
268
272
|
# Dispathes route chain in context +ctx+ on +request_path+, starting with controller +klass+.
|
269
273
|
def dispatch(ctx, klass, request_path)
|
270
|
-
|
274
|
+
route_parts = parse_path(request_path)
|
271
275
|
curr = root_ctrl = klass.new(ctx, nil, nil, true)
|
272
|
-
|
276
|
+
route_parts.each do |route_part|
|
273
277
|
curr.instance_variable_set :@_active, true
|
274
|
-
nxt = curr[
|
278
|
+
nxt = curr[route_part[:route], *route_part[:args]]
|
279
|
+
curr.logical_child = nxt
|
280
|
+
curr = nxt
|
281
|
+
end
|
282
|
+
while route_part = curr.default_route
|
283
|
+
if route_part[:redirect]
|
284
|
+
klass = curr.child_class(route_part)
|
285
|
+
return {:type => :redirect, :location => grow_link(curr, route_part, klass.arg_defs)}
|
286
|
+
end
|
287
|
+
curr.instance_variable_set :@_active, true
|
288
|
+
nxt = curr[route_part[:route], *route_part[:args]]
|
275
289
|
curr.logical_child = nxt
|
276
290
|
curr = nxt
|
277
291
|
end
|
278
292
|
curr.instance_variable_set :@_active, true
|
279
293
|
curr.instance_variable_set :@_current, true
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
|
294
|
+
type = (curr.is_a? ctx.missing_page) ? :missing_page : :page
|
295
|
+
prev = curr
|
296
|
+
while curr = prev.visual_parent
|
297
|
+
curr.visual_child = prev
|
285
298
|
prev = curr
|
286
|
-
while curr = prev.visual_parent
|
287
|
-
curr.visual_child = prev
|
288
|
-
prev = curr
|
289
|
-
end
|
290
|
-
{:type => type, :controller => prev}
|
291
299
|
end
|
300
|
+
{:type => type, :controller => prev}
|
292
301
|
end
|
293
302
|
|
294
303
|
# Extends the including module with Tanuki::ControllerBehavior::ClassMethods.
|
@@ -1,13 +1,20 @@
|
|
1
1
|
module Tanuki
|
2
2
|
|
3
|
+
# Tanuki::MetaModelBehavior contains all methods for the meta-model.
|
4
|
+
# In is included in the meta-model class.
|
3
5
|
module MetaModelBehavior
|
4
6
|
|
5
|
-
|
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)
|
6
11
|
@namespace = namespace
|
7
12
|
@name = name
|
8
13
|
@data = data
|
14
|
+
@models = models
|
9
15
|
end
|
10
16
|
|
17
|
+
# Returns class name for a given class type.
|
11
18
|
def class_name_for(class_type)
|
12
19
|
case class_type
|
13
20
|
when :model, :model_base then "#{@namespace}_Model_#{@name}"
|
@@ -15,24 +22,92 @@ module Tanuki
|
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
25
|
+
# Returns an array of code for alias-column name pair.
|
18
26
|
def key
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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
|
+
end
|
36
|
+
|
37
|
+
def process_key!
|
38
|
+
@key = @source['key'] || 'id'
|
39
|
+
@key = [@key] if @key.is_a? String
|
40
|
+
raise 'invalid key' unless @key.is_a? Array
|
41
|
+
@key.map! do |k|
|
42
|
+
parts = k.split('.').map {|p| p.to_sym }
|
43
|
+
raise "invalid key field #{k}" if parts.count > 2
|
44
|
+
if parts.count == 2
|
45
|
+
raise 'all key fields should belong to the first-source' if parts[0] != @first_source.to_s
|
46
|
+
parts
|
47
|
+
else
|
48
|
+
[@first_source, parts[0]]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Extracts the model firts-source information form the YAML @data
|
54
|
+
# and performs
|
55
|
+
def process_source!
|
56
|
+
guess_table = @name.pluralize
|
57
|
+
@data ||= {}
|
58
|
+
@source = @data['source'] || guess_table
|
59
|
+
@source = {'table' => @source} if @source.is_a? String
|
60
|
+
@first_source = (@source['table'] || guess_table).downcase.to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
def process_joins!
|
64
|
+
@joins = {}
|
65
|
+
@joins[@first_source] = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Prepares data for template generation.
|
69
|
+
# Processes foreign keys, fields, etc.
|
70
|
+
def process_relations!
|
71
|
+
joins = @source['joins'] || {}
|
72
|
+
joins = [joins] if joins.is_a? String
|
73
|
+
joins = Hash[*joins.collect {|v| [v, nil] }.flatten] if joins.is_a? Array
|
74
|
+
if joins.is_a? Hash
|
75
|
+
joins.each_pair do |table_alias, join|
|
76
|
+
table_alias = table_alias.to_sym
|
77
|
+
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
|
86
|
+
else
|
87
|
+
on = nil
|
88
|
+
table_name = table_alias
|
89
|
+
end
|
90
|
+
if on
|
91
|
+
else
|
92
|
+
on = {}
|
93
|
+
@key.each do |k|
|
94
|
+
on[[table_alias, @first_source.to_s.singularize.to_sym]] = []
|
95
|
+
# TODO choose a right priciple
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
25
99
|
else
|
26
|
-
raise "
|
100
|
+
raise "`joins' should be either nil or string or array or hash"
|
27
101
|
end
|
28
102
|
end
|
29
103
|
|
104
|
+
# Returns code for alias-column name pair for field +field_name+.
|
30
105
|
def qualified_name(field_name)
|
31
106
|
parts = field_name.split('.')
|
32
107
|
if parts.length == 1
|
33
|
-
":#{field_name}"
|
108
|
+
"%w{:#{@first_source} :#{field_name}}"
|
34
109
|
elsif parts.length == 2
|
35
|
-
":#{parts[
|
110
|
+
"%w{:#{parts[0]} :#{parts[1]}}"
|
36
111
|
else
|
37
112
|
raise "field name for model #{@namespace}.#{@name} is invalid"
|
38
113
|
end
|
@@ -1,17 +1,23 @@
|
|
1
1
|
module Tanuki
|
2
2
|
|
3
|
+
# Tanuki::ModelBehavior contains basic methods for a framework model.
|
4
|
+
# In is included in the base model class.
|
3
5
|
module ModelBehavior
|
4
6
|
|
5
|
-
|
7
|
+
# Creates new model with dataset row +data+.
|
8
|
+
# If the model is +lazy+, +data+ should contain only row keys.
|
9
|
+
def initialize(data={}, lazy=false)
|
6
10
|
@_data = data
|
7
11
|
@_loaded = !lazy
|
8
12
|
end
|
9
13
|
|
14
|
+
# Returns the value of a given attribute, loading the model on demand.
|
10
15
|
def [](attribute)
|
11
|
-
ensure_loaded!
|
16
|
+
ensure_loaded! unless self.class[attribute].present_in(@_data)
|
12
17
|
self.class[attribute].get(@_data)
|
13
18
|
end
|
14
19
|
|
20
|
+
# Sets the value of a given attribute.
|
15
21
|
def []=(attribute, value)
|
16
22
|
@_errors ||= {}
|
17
23
|
@_original ||= {}
|
@@ -24,16 +30,27 @@ module Tanuki
|
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
33
|
+
# Returns the modification errors hash.
|
34
|
+
def errors
|
35
|
+
@_errors ||= {}
|
36
|
+
@_errors
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns transport representation of data.
|
27
40
|
def internals_get(attribute)
|
28
41
|
self.class[attribute].internals_get(@_data)
|
29
42
|
end
|
30
43
|
|
44
|
+
# Sets transport representation of data.
|
31
45
|
def internals_set(attribute, internal_value)
|
32
46
|
@_errors ||= {}
|
33
47
|
internals_set(self.class[attribute], @_data)
|
34
48
|
end
|
35
49
|
|
50
|
+
# Returns model updates hash.
|
51
|
+
# This method is used internally to generate a data source update.
|
36
52
|
def get_updates
|
53
|
+
# TODO Rewrite this properly
|
37
54
|
@_original ||= {}
|
38
55
|
original_data = {}
|
39
56
|
self.class.attributes.each_pair do |name, attrib|
|
@@ -46,30 +63,23 @@ module Tanuki
|
|
46
63
|
updates
|
47
64
|
end
|
48
65
|
|
49
|
-
|
50
|
-
@_errors ||= {}
|
51
|
-
@_errors[attribute]
|
52
|
-
end
|
53
|
-
|
54
|
-
def invalid?(attribute)
|
55
|
-
@_errors.include? attribute
|
56
|
-
end
|
57
|
-
|
66
|
+
# Returns +true+ if there are any modification errors.
|
58
67
|
def has_errors?
|
59
68
|
@_errors ||= {}
|
60
69
|
@_errors == {}
|
61
70
|
end
|
62
71
|
|
63
|
-
def errors
|
64
|
-
@_errors ||= {}
|
65
|
-
@_errors
|
66
|
-
end
|
67
|
-
|
68
72
|
module ClassMethods
|
69
73
|
|
70
|
-
|
74
|
+
# Returns meta-information for a given attribute.
|
75
|
+
def [](attribute)
|
76
|
+
@_attributes[attribute]
|
77
|
+
end
|
78
|
+
|
79
|
+
# Creates new model, or returns existing one.
|
80
|
+
def get(data, ctx, lazy=false) # IDENTITY TRACKING AND LAZY LOADING
|
71
81
|
entity_key = extract_key(data)
|
72
|
-
key = [self, entity_key] #extract_key is generated ad hoc by model compiler!
|
82
|
+
key = [self, entity_key] # extract_key is generated ad hoc by model compiler!
|
73
83
|
if cached = ctx.entity_cache[key]
|
74
84
|
cached
|
75
85
|
else
|
@@ -77,30 +87,29 @@ module Tanuki
|
|
77
87
|
end
|
78
88
|
end
|
79
89
|
|
90
|
+
# Assigns +attribute+ with definition +attr_def+ to model.
|
80
91
|
def has_attribute(attribute, attr_def)
|
81
92
|
@_attributes ||= superclass.instance_variable_get(:@_attributes).dup
|
82
93
|
@_attributes[attribute] = attr_def
|
83
94
|
end
|
84
95
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
def has_reference(attribute, reference_def)
|
90
|
-
@_references ||= superclass.instance_variable_get(:@_references).dup
|
91
|
-
@_references[attribute] = reference_def
|
96
|
+
# Adds a relation +name+ with definition +relation_def+ to model.
|
97
|
+
def has_relation(name, relation_def)
|
98
|
+
@_relations ||= superclass.instance_variable_get(:@_relations).dup
|
99
|
+
@_relations[name] = relation_def
|
92
100
|
end
|
93
101
|
|
94
102
|
# Prepares the extended module.
|
95
103
|
def self.extended(mod)
|
96
104
|
mod.instance_variable_set(:@_attributes, {})
|
97
|
-
mod.instance_variable_set(:@
|
105
|
+
mod.instance_variable_set(:@_relations, {})
|
98
106
|
end
|
99
107
|
|
100
108
|
end # end ClassMethods
|
101
109
|
|
102
110
|
class << self
|
103
111
|
|
112
|
+
# Extends the including module with Tanuki::ModelBehavior::ClassMethods.
|
104
113
|
def included(mod)
|
105
114
|
mod.extend ClassMethods
|
106
115
|
end
|
data/lib/tanuki/configurator.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
module Tanuki
|
2
2
|
|
3
3
|
# Tanuki::Configurator is a scope for evaluating a Tanuki application configuration block.
|
4
|
-
# Use Tanuki::development_application and Tanuki::production_application to create such a block.
|
5
4
|
class Configurator
|
6
5
|
|
7
6
|
# Configuration root.
|
@@ -12,7 +11,6 @@ module Tanuki
|
|
12
11
|
def initialize(ctx, root, config_root=nil)
|
13
12
|
@context = ctx
|
14
13
|
set :root, root ? root : Dir.pwd
|
15
|
-
@config_root = config_root ? config_root : File.join(@context.root, 'config')
|
16
14
|
end
|
17
15
|
|
18
16
|
# Loads and executes a given configuraion file with symbolic name +config+.
|
data/lib/tanuki/context.rb
CHANGED
@@ -7,41 +7,66 @@ module Tanuki
|
|
7
7
|
|
8
8
|
@_defined = {}
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
child
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
defined
|
24
|
-
|
25
|
-
|
26
|
-
if
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Creates and returns child context object.
|
13
|
+
# This object's superclass is going to be current context class.
|
14
|
+
def child
|
15
|
+
child = Class.new(self)
|
16
|
+
child.instance_variable_set(:@_defined, {})
|
17
|
+
child
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns a printable version of Tanuki::Context, represented as a +Hash+.
|
21
|
+
# Can be used during development for inspection purposes.
|
22
|
+
#--
|
23
|
+
# When changing this method, remember to update `#{__LINE__ + 12}' to `defined.inspect` line number.
|
24
|
+
# This is required to avoid infinite recursion.
|
25
|
+
def inspect
|
26
|
+
return to_s if caller.any? {|entry_point| entry_point =~ /\A#{__FILE__}:#{__LINE__ + 12}/}
|
27
|
+
defined = {}
|
28
|
+
ancestors.each do |ancestor|
|
29
|
+
ancestor.instance_variable_get(:@_defined).each_key do |key|
|
30
|
+
begin
|
31
|
+
defined[key] ||= send(key)
|
32
|
+
rescue ArgumentError
|
33
|
+
defined[key] ||= method(key)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
break if ancestor.equal? Context
|
35
37
|
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
defined.inspect
|
39
|
+
end
|
40
|
+
|
41
|
+
# Allowes arbitary values to be assigned to context with a +key=+ method.
|
42
|
+
# A reader in context object class is created for each assigned value.
|
43
|
+
def method_missing(sym, arg=nil)
|
44
|
+
match = sym.to_s.match(/\A(?!(?:child|inspect|method_missing)=\Z)([^=]+)(=)?\Z/)
|
45
|
+
raise "`#{sym}' method cannot be called for Context and its descendants" unless match
|
46
|
+
defined = @_defined
|
47
|
+
class << self; self; end.instance_eval do
|
48
|
+
method_sym = match[1].to_sym
|
49
|
+
if defined.include? method_sym
|
50
|
+
undef_method method_sym
|
51
|
+
else
|
52
|
+
defined[method_sym] = nil
|
53
|
+
end
|
54
|
+
if arg.is_a? Proc
|
55
|
+
define_method(method_sym, &arg)
|
56
|
+
else
|
57
|
+
define_method(method_sym) { arg }
|
58
|
+
end
|
59
|
+
return arg
|
60
|
+
end if match[2]
|
61
|
+
super
|
62
|
+
end
|
63
|
+
|
64
|
+
# Disallow context instantiation
|
65
|
+
def new
|
66
|
+
raise "contexts cannot be instantiated"
|
67
|
+
end
|
68
|
+
|
69
|
+
end # end class << self
|
45
70
|
|
46
71
|
end # end Context
|
47
72
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Rack
|
2
|
+
class Builder
|
3
|
+
|
4
|
+
# Initializes application settings using configuration for environment +env+ and +rackup+ arguments.
|
5
|
+
# Application is configured for development, if no environment is specified.
|
6
|
+
# Returns Tanuki::Application::rack_app.
|
7
|
+
#
|
8
|
+
# This should be invoked from Rackup configuration files (e.g. +config.ru+):
|
9
|
+
#
|
10
|
+
# #\ -p 3000
|
11
|
+
# require 'tanuki'
|
12
|
+
# run tanuki
|
13
|
+
def tanuki(env=nil)
|
14
|
+
puts %{Calling for a Tanuki in "#{Dir.pwd}"}
|
15
|
+
at_exit { puts 'Tanuki ran away!' }
|
16
|
+
builder = self
|
17
|
+
Tanuki::Application.instance_eval do
|
18
|
+
configure(env = env ? env.to_sym : :development)
|
19
|
+
configure_middleware(builder)
|
20
|
+
puts "A racked #{env} Tanuki appears!"
|
21
|
+
rack_app
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end # end
|
26
|
+
end # end Rack
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rack
|
2
|
+
class Server
|
3
|
+
|
4
|
+
# Wraps around Rack::Server#options to update application configuration accordingly.
|
5
|
+
def options_with_tanuki(*args, &block)
|
6
|
+
rack_server = self
|
7
|
+
rackup_options = options_without_tanuki(*args, &block)
|
8
|
+
Tanuki::Application.instance_eval do
|
9
|
+
rackup_options.each_pair {|k, v| @context.send :"#{k.downcase}=", v }
|
10
|
+
@context.running_server = rack_server.server
|
11
|
+
end
|
12
|
+
rackup_options
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method_chain :options, :tanuki
|
16
|
+
|
17
|
+
end # end
|
18
|
+
end # end Rack
|
data/lib/tanuki/i18n.rb
CHANGED
@@ -15,7 +15,7 @@ module Tanuki
|
|
15
15
|
# Returns default route according to default language.
|
16
16
|
def default_route
|
17
17
|
raise 'default language is not configured' unless @_ctx.language
|
18
|
-
{:route => @_ctx.language
|
18
|
+
{:route => @_ctx.language, :args => {}, :redirect => @_ctx.i18n_redirect}
|
19
19
|
end
|
20
20
|
|
21
21
|
# Calls default view of visual child.
|
data/lib/tanuki/version.rb
CHANGED
data/lib/tanuki.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
libdir = File.dirname(__FILE__)
|
2
2
|
$:.unshift(libdir) unless $:.include?(libdir)
|
3
3
|
|
4
|
+
require 'active_support/all'
|
4
5
|
require 'rack'
|
5
6
|
require 'fileutils'
|
6
7
|
require 'sequel'
|
@@ -10,6 +11,8 @@ require 'escape_utils/url/rack'
|
|
10
11
|
require 'tanuki/version'
|
11
12
|
require 'tanuki/extensions/module'
|
12
13
|
require 'tanuki/extensions/object'
|
14
|
+
require 'tanuki/extensions/rack/builder'
|
15
|
+
require 'tanuki/extensions/rack/server'
|
13
16
|
require 'tanuki/extensions/rack/static_dir'
|
14
17
|
require 'tanuki/behavior/controller_behavior'
|
15
18
|
require 'tanuki/behavior/meta_model_behavior'
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Anatoly Ressin
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-09-10 00:00:00 +03:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -24,7 +24,7 @@ dependencies:
|
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
segments:
|
30
30
|
- 1
|
@@ -38,13 +38,12 @@ dependencies:
|
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
|
-
- -
|
41
|
+
- - ~>
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
segments:
|
44
44
|
- 3
|
45
45
|
- 14
|
46
|
-
|
47
|
-
version: 3.14.0
|
46
|
+
version: "3.14"
|
48
47
|
type: :runtime
|
49
48
|
version_requirements: *id002
|
50
49
|
- !ruby/object:Gem::Dependency
|
@@ -53,30 +52,56 @@ dependencies:
|
|
53
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
54
53
|
none: false
|
55
54
|
requirements:
|
56
|
-
- -
|
55
|
+
- - ~>
|
57
56
|
- !ruby/object:Gem::Version
|
58
57
|
segments:
|
59
58
|
- 0
|
60
59
|
- 1
|
61
|
-
|
62
|
-
version: 0.1.5
|
60
|
+
version: "0.1"
|
63
61
|
type: :runtime
|
64
62
|
version_requirements: *id003
|
65
63
|
- !ruby/object:Gem::Dependency
|
66
|
-
name:
|
64
|
+
name: activesupport
|
67
65
|
prerelease: false
|
68
66
|
requirement: &id004 !ruby/object:Gem::Requirement
|
69
67
|
none: false
|
70
68
|
requirements:
|
71
|
-
- -
|
69
|
+
- - ~>
|
72
70
|
- !ruby/object:Gem::Version
|
73
71
|
segments:
|
74
|
-
- 1
|
75
72
|
- 3
|
76
73
|
- 0
|
77
|
-
version:
|
78
|
-
type: :
|
74
|
+
version: "3.0"
|
75
|
+
type: :runtime
|
79
76
|
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: i18n
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
- 4
|
88
|
+
version: "0.4"
|
89
|
+
type: :runtime
|
90
|
+
version_requirements: *id005
|
91
|
+
- !ruby/object:Gem::Dependency
|
92
|
+
name: rspec
|
93
|
+
prerelease: false
|
94
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ~>
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
segments:
|
100
|
+
- 1
|
101
|
+
- 3
|
102
|
+
version: "1.3"
|
103
|
+
type: :development
|
104
|
+
version_requirements: *id006
|
80
105
|
description: Tanuki is an MVVM-inspired web framework that fancies idiomatic Ruby, DRY and extensibility by its design.
|
81
106
|
email: tanuki@withballs.org
|
82
107
|
executables:
|
@@ -121,6 +146,8 @@ files:
|
|
121
146
|
- lib/tanuki/context.rb
|
122
147
|
- lib/tanuki/extensions/module.rb
|
123
148
|
- lib/tanuki/extensions/object.rb
|
149
|
+
- lib/tanuki/extensions/rack/builder.rb
|
150
|
+
- lib/tanuki/extensions/rack/server.rb
|
124
151
|
- lib/tanuki/extensions/rack/static_dir.rb
|
125
152
|
- lib/tanuki/i18n.rb
|
126
153
|
- lib/tanuki/launcher.rb
|
@@ -148,7 +175,7 @@ require_paths:
|
|
148
175
|
required_ruby_version: !ruby/object:Gem::Requirement
|
149
176
|
none: false
|
150
177
|
requirements:
|
151
|
-
- -
|
178
|
+
- - ~>
|
152
179
|
- !ruby/object:Gem::Version
|
153
180
|
segments:
|
154
181
|
- 1
|