hobo 0.9.0 → 0.9.100
Sign up to get free protection for your applications and to get access to all the features.
- 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
|