hobo 0.9.0 → 0.9.100
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/CHANGES.txt +132 -0
- data/Rakefile +18 -22
- data/bin/hobo +14 -13
- data/doctest/scopes.rdoctest +1 -0
- data/dryml_generators/rapid/forms.dryml.erb +1 -1
- data/dryml_generators/rapid/pages.dryml.erb +24 -24
- data/lib/hobo.rb +3 -3
- data/lib/hobo/accessible_associations.rb +10 -3
- data/lib/hobo/controller.rb +2 -1
- data/lib/hobo/dryml.rb +7 -2
- data/lib/hobo/dryml/dryml_builder.rb +1 -4
- data/lib/hobo/dryml/dryml_generator.rb +5 -3
- data/lib/hobo/dryml/taglib.rb +3 -8
- data/lib/hobo/dryml/template.rb +3 -10
- data/lib/hobo/dryml/template_handler.rb +3 -2
- data/lib/hobo/hobo_helper.rb +5 -83
- data/lib/hobo/lifecycles.rb +9 -5
- data/lib/hobo/lifecycles/actions.rb +5 -5
- data/lib/hobo/lifecycles/lifecycle.rb +6 -2
- data/lib/hobo/model.rb +14 -36
- data/lib/hobo/model_controller.rb +28 -15
- data/lib/hobo/model_router.rb +1 -1
- data/lib/hobo/permissions.rb +55 -5
- data/lib/hobo/permissions/associations.rb +13 -5
- data/lib/hobo/rapid_helper.rb +4 -10
- data/lib/hobo/scopes/automatic_scopes.rb +9 -1
- data/lib/hobo/translations.rb +88 -0
- data/lib/hobo/user.rb +1 -2
- data/lib/hobo/user_controller.rb +31 -9
- data/lib/hobo/view_hints.rb +38 -6
- data/rails_generators/hobo_model/templates/hints.rb +4 -1
- data/rails_generators/hobo_model_controller/hobo_model_controller_generator.rb +1 -1
- data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +2 -1
- data/rails_generators/hobo_rapid/templates/hobo-rapid.js +9 -0
- data/rails_generators/hobo_rapid/templates/ie7-recalc.js +166 -2
- data/rails_generators/hobo_user_model/templates/model.rb +1 -3
- data/taglibs/rapid.dryml +2 -1
- data/taglibs/rapid_core.dryml +7 -5
- data/taglibs/rapid_editing.dryml +14 -10
- data/taglibs/rapid_forms.dryml +7 -5
- data/taglibs/rapid_generics.dryml +1 -1
- data/taglibs/rapid_lifecycles.dryml +3 -2
- data/taglibs/rapid_pages.dryml +1 -1
- data/taglibs/rapid_support.dryml +1 -1
- data/tasks/hobo_tasks.rake +10 -0
- metadata +7 -7
- data/lib/hobo/bundle.rb +0 -330
data/taglibs/rapid_editing.dryml
CHANGED
@@ -28,7 +28,8 @@ This area of Hobo has had less attention that the non-ajax forms of late, so it'
|
|
28
28
|
|
29
29
|
<!-- Not implemented - you just get links to the items in the collection -->
|
30
30
|
<def tag="has-many-editor">
|
31
|
-
<% #TODO: Implement
|
31
|
+
<% #TODO: Implement
|
32
|
+
%>
|
32
33
|
<a merge-attrs/>
|
33
34
|
</def>
|
34
35
|
|
@@ -36,30 +37,33 @@ This area of Hobo has had less attention that the non-ajax forms of late, so it'
|
|
36
37
|
<def tag="belongs-to-editor" polymorphic><%= select_one_editor(attributes) %></def>
|
37
38
|
|
38
39
|
<!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
|
39
|
-
<def tag="editor" for="string"><%= in_place_editor
|
40
|
+
<def tag="editor" for="string"><%= in_place_editor(attributes, this) %></def>
|
40
41
|
|
41
42
|
<!-- Provides a simple Scriptaculous in-place-editor that uses a `<textarea>` -->
|
42
|
-
<def tag="editor" for="text"><%= in_place_editor
|
43
|
+
<def tag="editor" for="text"><%= in_place_editor(attributes, this) %></def>
|
43
44
|
|
44
45
|
<!-- Provides a simple Scriptaculous in-place-editor that uses a `<textarea>`.
|
45
46
|
A JavaScript hook is available in order to replace the simple textarea with a rich-text editor.
|
46
|
-
For an example, see the [hoboyui](http://github.com/tablatom/hoboyui) plugin
|
47
|
-
|
47
|
+
For an example, see the [hoboyui](http://github.com/tablatom/hoboyui) plugin
|
48
|
+
|
49
|
+
This tag does not sanitize HTML; this is provided by HtmlString before saving to the database.
|
50
|
+
-->
|
51
|
+
<def tag="editor" for="html"><%= in_place_editor(attributes, this) %></def>
|
48
52
|
|
49
53
|
<!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
|
50
|
-
<def tag="editor" for="datetime"><%= in_place_editor
|
54
|
+
<def tag="editor" for="datetime"><%= in_place_editor(attributes, this) %></def>
|
51
55
|
|
52
56
|
<!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
|
53
|
-
<def tag="editor" for="date"><%= in_place_editor
|
57
|
+
<def tag="editor" for="date"><%= in_place_editor(attributes, this) %></def>
|
54
58
|
|
55
59
|
<!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='integer'>` -->
|
56
|
-
<def tag="editor" for="integer"><%= in_place_editor
|
60
|
+
<def tag="editor" for="integer"><%= in_place_editor(attributes, this) %></def>
|
57
61
|
|
58
62
|
<!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='BigDecimal'>` -->
|
59
|
-
<def tag="editor" for="BigDecimal"><%= in_place_editor
|
63
|
+
<def tag="editor" for="BigDecimal"><%= in_place_editor(attributes, this) %></def>
|
60
64
|
|
61
65
|
<!-- Provides a simple Scriptaculous in-place-editor that uses an `<input type='text'>` -->
|
62
|
-
<def tag="editor" for="float"><%= in_place_editor
|
66
|
+
<def tag="editor" for="float"><%= in_place_editor(attributes, this) %></def>
|
63
67
|
|
64
68
|
<!-- Raises an error - passwords cannot be edited in place -->
|
65
69
|
<def tag="editor" for="password"><% raise HoboError, "passwords cannot be edited in place" %></def>
|
data/taglibs/rapid_forms.dryml
CHANGED
@@ -417,8 +417,9 @@ The menus default to the current time if the current value is nil.
|
|
417
417
|
-->
|
418
418
|
<def tag="input" for="HoboFields::EnumString" attrs="labels, titleize, first-option, first-value"><%
|
419
419
|
labels ||= {}
|
420
|
+
labels = HashWithIndifferentAccess.new(labels)
|
420
421
|
titleize = true if titleize.nil?
|
421
|
-
options = this_type.values.map {|v| [labels.fetch(v
|
422
|
+
options = this_type.values.map {|v| [labels.fetch(v, titleize ? this_type.translated_values[v].titleize : this_type.translated_values[v]), v] }
|
422
423
|
%>
|
423
424
|
<select name="#{param_name_for_this}" merge-attrs>
|
424
425
|
<option value="#{first_value}" unless="&first_option.nil?"><first-option/></option>
|
@@ -680,7 +681,7 @@ Here's a more complex example. This used to be a part of [agility](http://cookb
|
|
680
681
|
end
|
681
682
|
end
|
682
683
|
|
683
|
-
Note that this was added to the projects controller, rather than the users controller as in the first example. You can read this as: create an auto-complete action called
|
684
|
+
Note that this was added to the projects controller, rather than the users controller as in the first example. You can read this as: create an auto-complete action called `new_member_name` that finds users that are not already members of the project, and not the owner of the project, and completes the :name field.
|
684
685
|
|
685
686
|
<name-one:user complete-target="&@project" completer="new_member_name"/>
|
686
687
|
|
@@ -853,7 +854,8 @@ Use the `uri` option to specify a redirect location:
|
|
853
854
|
end
|
854
855
|
-%>
|
855
856
|
<ul class="check-many" param="default" merge-attrs>
|
856
|
-
<input type="hidden" name="#{param_name}[]" value=""/><% # ensure all items are removed when nothing checked
|
857
|
+
<input type="hidden" name="#{param_name}[]" value=""/><% # ensure all items are removed when nothing checked
|
858
|
+
%>
|
857
859
|
<li repeat="&options" param>
|
858
860
|
<input type="checkbox" name="#{param_name}[]" value="@#{this.id}" checked="&this.in?(collection)" disabled="&disabled"/>
|
859
861
|
<name param/>
|
@@ -891,12 +893,13 @@ The body of the tag will be repeated for each of the current records in the coll
|
|
891
893
|
-->
|
892
894
|
<def tag="input-many" attrs="fields,skip" polymorphic>
|
893
895
|
<set empty="&this.empty?"/>
|
896
|
+
<% skip ||= this.proxy_reflection.klass.reflect_on_all_associations.detect {|p| p.primary_key_name==this.proxy_reflection.primary_key_name}.try.name.to_s if this.respond_to? :proxy_reflection %>
|
894
897
|
<ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this}">
|
895
898
|
<li repeat class="#{'record-with-errors' unless this.errors.empty?}">
|
896
899
|
<error-messages without-heading class="sub-record"/>
|
897
900
|
<hidden-id-field/>
|
898
901
|
<div class="input-many-item" param="default">
|
899
|
-
<field-list merge-attrs="fields" />
|
902
|
+
<field-list merge-attrs="fields" skip="&skip"/>
|
900
903
|
</div>
|
901
904
|
<div class="buttons">
|
902
905
|
<button type="button" class="remove-item" merge-attrs="disabled" param="remove-item">-</button>
|
@@ -904,7 +907,6 @@ The body of the tag will be repeated for each of the current records in the coll
|
|
904
907
|
</div>
|
905
908
|
</li>
|
906
909
|
<li if="&empty">
|
907
|
-
<% skip ||= this.proxy_reflection.klass.reflect_on_all_associations.detect {|p| p.primary_key_name==this.proxy_reflection.primary_key_name}.try.name.to_s %>
|
908
910
|
<fake-field-context fake-field="0" context="&this.try.new_candidate || this.member_class.new">
|
909
911
|
<div class="input-many-item" param="default">
|
910
912
|
<field-list merge-attrs="fields" skip="&skip"/>
|
@@ -19,7 +19,7 @@
|
|
19
19
|
-->
|
20
20
|
<def tag="empty-collection-message">
|
21
21
|
<div class="empty-collection-message" style="#{'display:none' if !this.empty?}" param="default">
|
22
|
-
<ht key="#{
|
22
|
+
<ht key="#{this.member_class.name.tableize}.collection.empty_message">
|
23
23
|
No <collection-name lowercase/> to display
|
24
24
|
</ht>
|
25
25
|
</div>
|
@@ -20,11 +20,12 @@ All of the [standard ajax attributes](/api_taglibs/rapid_forms) are also support
|
|
20
20
|
|
21
21
|
html_attributes[:method] ||= has_params ? :get : :put
|
22
22
|
add_classes!(html_attributes, "transition-button #{transition_name}-button")
|
23
|
-
label
|
23
|
+
label = ht("#{this.class.name.tableize}.actions.#{transition_name}", :default => (label || transition_name.to_s.titleize))
|
24
24
|
url = object_url(this, transition_name, :method => html_attributes[:method])
|
25
25
|
|
26
26
|
if (update || !ajax_attributes.empty?) && !has_params
|
27
27
|
ajax_attributes[:message] ||= label
|
28
|
+
ajax_attributes[:method] = html_attributes[:method]
|
28
29
|
func = ajax_updater(url, update, ajax_attributes)
|
29
30
|
html_attributes.update(:onclick => "var e = this; " + func, :type =>'button', :value => label)
|
30
31
|
element(:input, html_attributes, nil, true, true)
|
@@ -41,7 +42,7 @@ For example, you could use this on a `Friendship` card: the person invited to ha
|
|
41
42
|
-->
|
42
43
|
<def tag="transition-buttons">
|
43
44
|
<div merge-attrs class="transitions">
|
44
|
-
<% this.lifecycle.
|
45
|
+
<% this.lifecycle.publishable_transitions_for(current_user).each do |t| %>
|
45
46
|
<transition-button transition="&t"/>
|
46
47
|
<% end %>
|
47
48
|
</div>
|
data/taglibs/rapid_pages.dryml
CHANGED
@@ -245,7 +245,7 @@ The flash is output in a `<div class="flash notice">`, where `notice` is the `ty
|
|
245
245
|
<def tag="flash-messages" attrs="names"><%=
|
246
246
|
scope.flash_rendered = true
|
247
247
|
names = names.nil? ? flash.keys : comma_split(names)
|
248
|
-
names.map { |name| flash_message :type => name }
|
248
|
+
names.map { |name| flash_message :type => name }.join
|
249
249
|
%></def>
|
250
250
|
|
251
251
|
<!-- Renders `<div id="ajax-progress"><div><span id="ajax-progress-text"></span></div></div>`. The theme will style this as an ajax progress 'spinner' -->
|
data/taglibs/rapid_support.dryml
CHANGED
data/tasks/hobo_tasks.rake
CHANGED
@@ -44,5 +44,15 @@ namespace :hobo do
|
|
44
44
|
END
|
45
45
|
end
|
46
46
|
|
47
|
+
desc "Run the standard generators that the hobo command runs with the --invite-only option."
|
48
|
+
task :run_invite_only_generators do
|
49
|
+
exec <<-END
|
50
|
+
ruby script/generate hobo --add-routes && \
|
51
|
+
ruby script/generate hobo_rapid --import-tags --invite-only && \
|
52
|
+
ruby script/generate hobo_user_model user --invite-only && \
|
53
|
+
ruby script/generate hobo_user_controller user --invite-only && \
|
54
|
+
ruby script/generate hobo_front_controller front --delete-index --add-routes --invite-only
|
55
|
+
END
|
56
|
+
end
|
47
57
|
end
|
48
58
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hobo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.100
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Locke
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-01 00:00:00 +00:00
|
13
13
|
default_executable: hobo
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -23,14 +23,14 @@ dependencies:
|
|
23
23
|
version: 2.2.2
|
24
24
|
version:
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
|
-
name:
|
26
|
+
name: will_paginate
|
27
27
|
type: :runtime
|
28
28
|
version_requirement:
|
29
29
|
version_requirements: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.
|
33
|
+
version: 2.3.11
|
34
34
|
version:
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
36
|
name: hobosupport
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
requirements:
|
41
41
|
- - "="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.9.
|
43
|
+
version: 0.9.100
|
44
44
|
version:
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
46
|
name: hobofields
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - "="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.9.
|
53
|
+
version: 0.9.100
|
54
54
|
version:
|
55
55
|
description:
|
56
56
|
email: tom@tomlocke.com
|
@@ -82,7 +82,6 @@ files:
|
|
82
82
|
- lib/hobo.rb
|
83
83
|
- lib/hobo/accessible_associations.rb
|
84
84
|
- lib/hobo/authentication_support.rb
|
85
|
-
- lib/hobo/bundle.rb
|
86
85
|
- lib/hobo/controller.rb
|
87
86
|
- lib/hobo/dev_controller.rb
|
88
87
|
- lib/hobo/dryml.rb
|
@@ -131,6 +130,7 @@ files:
|
|
131
130
|
- lib/hobo/scopes/named_scope_extensions.rb
|
132
131
|
- lib/hobo/static_tags
|
133
132
|
- lib/hobo/tasks/rails.rb
|
133
|
+
- lib/hobo/translations.rb
|
134
134
|
- lib/hobo/undefined.rb
|
135
135
|
- lib/hobo/undefined_access_error.rb
|
136
136
|
- lib/hobo/user.rb
|
data/lib/hobo/bundle.rb
DELETED
@@ -1,330 +0,0 @@
|
|
1
|
-
module ::Hobo
|
2
|
-
|
3
|
-
class Bundle
|
4
|
-
|
5
|
-
@bundles = HashWithIndifferentAccess.new
|
6
|
-
|
7
|
-
class << self
|
8
|
-
|
9
|
-
# Hobo::Bundle.bundles is a hash of all instantiated bundles by name
|
10
|
-
attr_accessor :bundles
|
11
|
-
|
12
|
-
# Used by subclasses, e.g MyBundle.plugin is the name of the
|
13
|
-
# plugin the bundle came from
|
14
|
-
attr_reader :plugin
|
15
|
-
|
16
|
-
attr_reader :model_declarations, :controller_declarations
|
17
|
-
|
18
|
-
attr_accessor :dirname
|
19
|
-
|
20
|
-
def inherited(base)
|
21
|
-
filename = caller[0].match(/^(.*):\d+/)[1]
|
22
|
-
base.dirname = filename.match(%r(^.*/plugins/[^/]+))[0]
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
def load_models_and_controllers
|
27
|
-
return if models_and_controllers_loaded?
|
28
|
-
|
29
|
-
@plugin = File.basename(dirname)
|
30
|
-
|
31
|
-
@model_declarations = []
|
32
|
-
@controller_declarations = []
|
33
|
-
|
34
|
-
class_eval do
|
35
|
-
eval_ruby_files("#{dirname}/models", @models)
|
36
|
-
eval_ruby_files("#{dirname}/controllers", @controllers)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def [](bundle_name)
|
41
|
-
bundles[bundle_name]
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def bundle_model(name, &block)
|
48
|
-
@model_declarations << [name, block]
|
49
|
-
end
|
50
|
-
|
51
|
-
|
52
|
-
def bundle_model_controller(model_name, &block)
|
53
|
-
@controller_declarations << [model_name, block]
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
def models_and_controllers_loaded?
|
58
|
-
@model_declarations
|
59
|
-
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
def eval_ruby_files(dir, filenames)
|
64
|
-
files = if filenames == [:none]
|
65
|
-
[]
|
66
|
-
elsif filenames.blank? || filenames == [:all]
|
67
|
-
Dir["#{dir}/*.rb"]
|
68
|
-
else
|
69
|
-
filenames.map { |f| "#{dir}/#{f}.rb" }
|
70
|
-
end
|
71
|
-
|
72
|
-
files.each { |f| instance_eval(File.read(f), f, 1) }
|
73
|
-
end
|
74
|
-
|
75
|
-
|
76
|
-
# Declarations
|
77
|
-
|
78
|
-
def models(*models)
|
79
|
-
@models = models
|
80
|
-
end
|
81
|
-
|
82
|
-
def controllers(*controllers)
|
83
|
-
@controllers = controllers.map {|c| case c.to_s
|
84
|
-
when /controller$/, "all", "none" then c
|
85
|
-
else "#{c.to_s.pluralize}_controller"
|
86
|
-
end }
|
87
|
-
end
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
def initialize(*args)
|
92
|
-
caller_options = args.extract_options!
|
93
|
-
|
94
|
-
self.class.load_models_and_controllers
|
95
|
-
self.name = args.first || self.class.name.match(/[^:]+$/)[0].underscore
|
96
|
-
Bundle.bundles[name] = self
|
97
|
-
|
98
|
-
options = defaults(caller_options).with_indifferent_access
|
99
|
-
options.recursive_update(caller_options)
|
100
|
-
|
101
|
-
@renames, @options = separate_renames(options)
|
102
|
-
|
103
|
-
includes
|
104
|
-
|
105
|
-
create_models
|
106
|
-
create_controllers
|
107
|
-
|
108
|
-
init
|
109
|
-
end
|
110
|
-
|
111
|
-
attr_accessor :renames, :options, :name
|
112
|
-
|
113
|
-
# optionally overridden by the bundle subclass
|
114
|
-
def includes; end
|
115
|
-
def init; end
|
116
|
-
def defaults(options); {}; end
|
117
|
-
|
118
|
-
|
119
|
-
def plugin
|
120
|
-
self.class.plugin
|
121
|
-
end
|
122
|
-
|
123
|
-
|
124
|
-
def create_models
|
125
|
-
self.class.model_declarations.each do |name, block|
|
126
|
-
klass = make_class(new_name_for(name), ActiveRecord::Base)
|
127
|
-
|
128
|
-
klass.meta_def :belongs_to_with_optional_polymorphism do |*args|
|
129
|
-
opts = args.extract_options!
|
130
|
-
|
131
|
-
if opts[:polymorphic] == :optional
|
132
|
-
if bundle.options["polymorphic_#{name}"]
|
133
|
-
opts[:polymorphic] = true
|
134
|
-
opts.delete(:class_name)
|
135
|
-
else
|
136
|
-
opts.delete(:polymorphic)
|
137
|
-
end
|
138
|
-
end
|
139
|
-
belongs_to_without_optional_polymorphism(name, opts)
|
140
|
-
end
|
141
|
-
klass.meta_eval { alias_method_chain :belongs_to, :optional_polymorphism }
|
142
|
-
|
143
|
-
klass.class_eval { hobo_model }
|
144
|
-
|
145
|
-
# FIXME this extension breaks passing a block to belongs_to
|
146
|
-
klass.meta_def :belongs_to_with_alias do |*args|
|
147
|
-
opts = args.extract_options!
|
148
|
-
name = args.first.to_sym
|
149
|
-
|
150
|
-
alias_name = opts.delete(:alias)
|
151
|
-
|
152
|
-
belongs_to_without_alias(name, opts)
|
153
|
-
|
154
|
-
if alias_name && name != alias_name
|
155
|
-
klass.send(:alias_method, alias_name, name)
|
156
|
-
# make the aliased name available in the classes metadata
|
157
|
-
klass.reflections[alias_name] = klass.reflections[name]
|
158
|
-
end
|
159
|
-
|
160
|
-
end
|
161
|
-
klass.meta_eval { alias_method_chain :belongs_to, :alias }
|
162
|
-
|
163
|
-
klass.class_eval(&block)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
|
168
|
-
def create_controllers
|
169
|
-
bundle = self
|
170
|
-
self.class.controller_declarations.each do |model_name, block|
|
171
|
-
klass = make_class("#{new_name_for(model_name).to_s.pluralize}Controller", ::ApplicationController) do
|
172
|
-
hobo_model_controller
|
173
|
-
end
|
174
|
-
klass.class_eval(&block)
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
|
179
|
-
def make_class(name, base_class, &b)
|
180
|
-
bundle = self
|
181
|
-
klass = Class.new(base_class) do
|
182
|
-
# Nasty hack because blocks can't take blocks
|
183
|
-
# Roll on Ruby 1.9
|
184
|
-
def self.feature(name, &block)
|
185
|
-
_feature(name, block)
|
186
|
-
end
|
187
|
-
|
188
|
-
def method_missing(name, *args)
|
189
|
-
if name.to_s =~ /^_.*_$/
|
190
|
-
self.class.bundle.magic_option(name)
|
191
|
-
else
|
192
|
-
super
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
klass.meta_def(:bundle) do
|
198
|
-
bundle
|
199
|
-
end
|
200
|
-
|
201
|
-
klass.meta_def(:_feature) do |feature, block|
|
202
|
-
has_feature = bundle.options[feature]
|
203
|
-
if has_feature
|
204
|
-
define_method("features_#{feature}?") { true }
|
205
|
-
meta_def("features_#{feature}?") { true }
|
206
|
-
block.call if block
|
207
|
-
else
|
208
|
-
define_method("features_#{feature}?") { false }
|
209
|
-
meta_def("features_#{feature}?") { false }
|
210
|
-
end
|
211
|
-
end
|
212
|
-
silence_warnings { Object.const_set(name, klass) }
|
213
|
-
|
214
|
-
klass.meta_def :method_missing do |name, *args|
|
215
|
-
if name.to_s =~ /^_.*_$/
|
216
|
-
bundle.magic_option(name)
|
217
|
-
else
|
218
|
-
super
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
klass.class_eval(&b) if b
|
223
|
-
|
224
|
-
klass
|
225
|
-
end
|
226
|
-
|
227
|
-
|
228
|
-
def new_name_for(name)
|
229
|
-
name = name.to_s
|
230
|
-
underscore = name =~ /^[a-z]/
|
231
|
-
name = name.camelize if underscore
|
232
|
-
|
233
|
-
plural = !renames.has_key?(name) && (sing = name.singularize) && renames.has_key?(sing)
|
234
|
-
name = sing if plural
|
235
|
-
|
236
|
-
# Keep a track of names we've seen to avoid cycles
|
237
|
-
seen = [ name ]
|
238
|
-
|
239
|
-
name = name.gsub(/_.*?_/) { |s| new_name_for(s[1..-2]) }
|
240
|
-
while (newname = renames[name])
|
241
|
-
name = newname
|
242
|
-
name = name.gsub(/_.*?_/) { |s| new_name_for(s[1..-2]) }
|
243
|
-
|
244
|
-
break if name.in?(seen)
|
245
|
-
seen << name
|
246
|
-
end
|
247
|
-
|
248
|
-
name = name.underscore if underscore
|
249
|
-
name = name.pluralize if plural
|
250
|
-
name = name.to_sym if underscore || plural
|
251
|
-
name
|
252
|
-
end
|
253
|
-
|
254
|
-
|
255
|
-
def separate_renames(options)
|
256
|
-
simple_options, renames = HashWithIndifferentAccess.new, HashWithIndifferentAccess.new
|
257
|
-
options.each do |k, v|
|
258
|
-
if k.to_s =~ /^[A-Z]/
|
259
|
-
renames[k] = v.to_s
|
260
|
-
else
|
261
|
-
simple_options[k] = v
|
262
|
-
end
|
263
|
-
end
|
264
|
-
[renames, simple_options]
|
265
|
-
end
|
266
|
-
|
267
|
-
|
268
|
-
def customize(name, &block)
|
269
|
-
new_name_for(name).to_s.constantize.class_eval(&block)
|
270
|
-
end
|
271
|
-
|
272
|
-
|
273
|
-
def method_missing(name, *args)
|
274
|
-
if name.to_s =~ /^_.*_$/
|
275
|
-
magic_option(name)
|
276
|
-
else
|
277
|
-
super
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
|
282
|
-
# Returns the option value or renamed class name from a 'magic'
|
283
|
-
# name like _foo_ or _MyFoo_
|
284
|
-
def magic_option(name)
|
285
|
-
option_name = name.to_s[1..-2]
|
286
|
-
if option_name == "bundle"
|
287
|
-
self.name
|
288
|
-
elsif options.has_key?(option_name)
|
289
|
-
options[option_name]
|
290
|
-
else
|
291
|
-
new_name_for(option_name)
|
292
|
-
end
|
293
|
-
end
|
294
|
-
|
295
|
-
|
296
|
-
def optional_bundle(*args)
|
297
|
-
local_options = args.extract_options!
|
298
|
-
class_name, option_name = args
|
299
|
-
option_name ||= class_name.to_s.underscore
|
300
|
-
_include_bundle(class_name, option_name, local_options) if self.options[option_name]
|
301
|
-
end
|
302
|
-
|
303
|
-
|
304
|
-
def include_bundle(*args)
|
305
|
-
local_options = args.extract_options!
|
306
|
-
class_name, option_name = args
|
307
|
-
option_name ||= class_name.to_s.underscore
|
308
|
-
_include_bundle(class_name, option_name, local_options)
|
309
|
-
end
|
310
|
-
|
311
|
-
|
312
|
-
def _include_bundle(class_name, option_name, local_options)
|
313
|
-
external_options = self.options[option_name]
|
314
|
-
external_options = {} if external_options.nil? || external_options == true
|
315
|
-
name = "#{self.name}_#{option_name}"
|
316
|
-
|
317
|
-
sub_bundle_options = external_options.merge(local_options).merge(renames)
|
318
|
-
sub_bundle = class_name.to_s.constantize.new(name, sub_bundle_options)
|
319
|
-
|
320
|
-
conflicting_renames = (renames.keys & sub_bundle.renames.keys).select { |k| renames[k] != sub_bundle.renames[k] }
|
321
|
-
unless conflicting_renames.empty?
|
322
|
-
raise ArgumentError, "Conflicting renames in included bundle '#{name}' of '#{self.name}': #{conflicting_renames * ', '}"
|
323
|
-
end
|
324
|
-
renames.update(sub_bundle.renames)
|
325
|
-
self.options["#{option_name}_bundle"] = name
|
326
|
-
end
|
327
|
-
|
328
|
-
end
|
329
|
-
|
330
|
-
end
|