nemo 0.1.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/INSTALL +5 -0
- data/README +25 -0
- data/lib/nemo.rb +47 -0
- data/lib/nemo/components.rb +4 -0
- data/lib/nemo/components/calendar.rb +191 -0
- data/lib/nemo/components/confirm.rb +30 -0
- data/lib/nemo/components/multiattr.rb +93 -0
- data/lib/nemo/examples.rb +5 -0
- data/lib/nemo/examples/date_picker.rb +74 -0
- data/lib/nemo/examples/person_editor.rb +293 -0
- data/lib/nemo/metaobject.rb +5 -0
- data/lib/nemo/metaobject/acollector.rb +79 -0
- data/lib/nemo/metaobject/attributes.rb +282 -0
- data/lib/nemo/metaobject/metaobject.rb +86 -0
- data/lib/nemo/metaobject/validation.rb +32 -0
- data/lib/nemo/util.rb +31 -0
- data/lib/nemo/util/accessors.rb +85 -0
- data/lib/nemo/util/blankslate.rb +6 -0
- data/lib/nemo/util/collector.rb +54 -0
- data/lib/nemo/util/date.rb +41 -0
- data/lib/nemo/visitors.rb +4 -0
- data/lib/nemo/visitors/editor.rb +186 -0
- data/lib/nemo/visitors/viewer.rb +63 -0
- data/lib/nemo/visitors/visitor.rb +14 -0
- data/nemo.gemspec +23 -0
- data/nemo_rdoc.bat +1 -0
- data/test/date_picker.rb +14 -0
- data/test/nemo/calendar.css +26 -0
- data/test/nemo/calendar.gif +0 -0
- data/test/nemo/person_editor.css +40 -0
- data/test/person_editor.rb +14 -0
- data/test/unit_test.rb +128 -0
- metadata +86 -0
@@ -0,0 +1,293 @@
|
|
1
|
+
# Web interface for maintaining a list of Person objects
|
2
|
+
#
|
3
|
+
module Nemo::Examples::PersonEditor; end
|
4
|
+
|
5
|
+
# Wrap standard editor in a page
|
6
|
+
#
|
7
|
+
class Nemo::Examples::PersonEditor::Editor < Nemo::Visitors::Editor
|
8
|
+
|
9
|
+
def render
|
10
|
+
r.page do
|
11
|
+
r.head do
|
12
|
+
r.title('Person Editor')
|
13
|
+
r.link.type('text/css').rel('stylesheet').href('/nemo/person_editor.css')
|
14
|
+
r.style('body { text-align: center }')
|
15
|
+
end
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
# Used by render to display model as a single table
|
23
|
+
# with one row per record, versus one table per record.
|
24
|
+
#
|
25
|
+
class Nemo::Examples::PersonEditor::ListItemViewer < Nemo::Visitors::Viewer
|
26
|
+
|
27
|
+
def render
|
28
|
+
render_fields
|
29
|
+
end
|
30
|
+
|
31
|
+
def render_label(a, &block)
|
32
|
+
r.table_data { block ? block.call : r.space }
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
# Maintain a list of Person objects
|
38
|
+
#
|
39
|
+
class Nemo::Examples::PersonEditor::DomainModel
|
40
|
+
|
41
|
+
attr_accessor :persons
|
42
|
+
|
43
|
+
def initialize
|
44
|
+
@persons = Array.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_person(person)
|
48
|
+
@persons << person
|
49
|
+
end
|
50
|
+
|
51
|
+
def remove_person(person)
|
52
|
+
@persons.delete(person)
|
53
|
+
end
|
54
|
+
|
55
|
+
def male_persons
|
56
|
+
@persons.find_all { |p| p.sex == true }
|
57
|
+
end
|
58
|
+
|
59
|
+
def female_persons
|
60
|
+
@persons.find_all { |p| p.sex == false }
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
class Nemo::Examples::PersonEditor::Session < Wee::Session
|
66
|
+
|
67
|
+
# Initialize the Wee session
|
68
|
+
#
|
69
|
+
def initialize
|
70
|
+
super do
|
71
|
+
self.root_component = Nemo::Examples::PersonEditor::Root.new
|
72
|
+
self.page_store = Wee::Utils::LRUCache.new(25) # backtrack up to 25 pages
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create a new Person
|
77
|
+
#
|
78
|
+
# person(:name=>'Joe', birthday=Date.new(1950,1,1))
|
79
|
+
#
|
80
|
+
def person(hash=Hash.new)
|
81
|
+
obj = Nemo::Examples::PersonEditor::Person.new
|
82
|
+
hash.each { |k,v| obj.send(k.to_s+'=', v) }
|
83
|
+
obj
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create a new PhoneNumber
|
87
|
+
#
|
88
|
+
# phone_number(:type=>'home', :number=>'281-328-1328')
|
89
|
+
#
|
90
|
+
def phone_number(hash=Hash.new)
|
91
|
+
obj = Nemo::Examples::PersonEditor::PhoneNumber.new
|
92
|
+
hash.each { |k,v| obj.send(k.to_s+'=', v) }
|
93
|
+
obj
|
94
|
+
end
|
95
|
+
|
96
|
+
# Create a new DomainModel and populate with data.
|
97
|
+
# Define and store in a constant on creation, return existing if already defined.
|
98
|
+
#
|
99
|
+
def domainmodel
|
100
|
+
unless Nemo::Examples::PersonEditor.const_defined?(:Model)
|
101
|
+
Nemo::Examples::PersonEditor.const_set(:Model, Nemo::Examples::PersonEditor::DomainModel.new)
|
102
|
+
father = person(:name=>'Joe', :sex=>true, :birthday=>Date.new(1950,1,1))
|
103
|
+
mother = person(:name=>'Jane', :sex=>false, :birthday=>Date.new(1960,1,1))
|
104
|
+
child1 = person(:name=>'Lisa', :sex=>false, :birthday=>Date.new(1982,1,1), :father=>father, :mother=>mother)
|
105
|
+
child2 = person(:name=>'Liza', :sex=>false, :birthday=>Date.new(1978,1,1), :father=>father, :mother=>mother)
|
106
|
+
father.phone_numbers << phone_number(:number=>'281-328-1328', :type=>'home')
|
107
|
+
child1.phone_numbers << phone_number(:number=>'132-813-2813', :type=>'work')
|
108
|
+
[father, mother, child1, child2].each { |p| Nemo::Examples::PersonEditor::Model.add_person(p) }
|
109
|
+
end
|
110
|
+
Nemo::Examples::PersonEditor::Model
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
# Display a list of persons for editing or deletion
|
116
|
+
#
|
117
|
+
class Nemo::Examples::PersonEditor::Root < Wee::Component
|
118
|
+
|
119
|
+
attr_accessor :views
|
120
|
+
|
121
|
+
# Create a ListItemViewer for each record
|
122
|
+
#
|
123
|
+
def initialize
|
124
|
+
super
|
125
|
+
@views = Wee::Session.current.domainmodel.persons.collect { |p| viewer_for(p) }
|
126
|
+
@views.each { |view| @children << view }
|
127
|
+
end
|
128
|
+
|
129
|
+
# Enable Wee backtracking for views
|
130
|
+
#
|
131
|
+
def backtrack_state(snapshot)
|
132
|
+
super
|
133
|
+
snapshot.add(self.children)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Create a new ListItemViewer for a given metaobject
|
137
|
+
#
|
138
|
+
def viewer_for(obj)
|
139
|
+
Nemo::Examples::PersonEditor::ListItemViewer.new(obj.metaobject.hide!(:password, :interests))
|
140
|
+
end
|
141
|
+
|
142
|
+
# Render the page
|
143
|
+
#
|
144
|
+
def render
|
145
|
+
r.html do
|
146
|
+
r.head { r.title('Person Editor') }
|
147
|
+
r.link.type('text/css').rel('stylesheet').href('/nemo/person_editor.css')
|
148
|
+
r.body do
|
149
|
+
r.h1('Person Editor')
|
150
|
+
r.table.width('100%').with do
|
151
|
+
if @views.empty?
|
152
|
+
r.table_row { r.table_header('No persons found.') }
|
153
|
+
else
|
154
|
+
labels = @views.first.metaobject.visible_attributes.collect { |a| a.label }
|
155
|
+
r.table_row { labels.each { |label| r.table_header(label) }; r.table_header { r.space } }
|
156
|
+
@views.each do |view|
|
157
|
+
r.table_row do
|
158
|
+
r.render(view)
|
159
|
+
r.table_header do
|
160
|
+
r.anchor.callback{ edit(view.metaobject.baseobject) }.with('edit')
|
161
|
+
r.space; r.text('|'); r.space
|
162
|
+
r.anchor.callback{ delete(view.metaobject.baseobject) }.with('delete')
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
r.break
|
169
|
+
r.anchor.callback{ add_new }.with('create new person')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Add a new Person
|
175
|
+
#
|
176
|
+
def add_new
|
177
|
+
if obj = call( Nemo::Examples::PersonEditor::Editor.new(Nemo::Examples::PersonEditor::Person.new.metaobject) )
|
178
|
+
session.domainmodel.add_person(obj)
|
179
|
+
@views << viewer_for(obj)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Edit a Person
|
184
|
+
#
|
185
|
+
def edit(obj)
|
186
|
+
call( Nemo::Examples::PersonEditor::Editor.new(obj.metaobject) ) # will auto-commit
|
187
|
+
end
|
188
|
+
|
189
|
+
# Delete a Person.
|
190
|
+
# Requires confirmation: Are you sure you want to delete? [OK] [Cancel]
|
191
|
+
#
|
192
|
+
def delete(obj)
|
193
|
+
if call( Nemo::Components::ConfirmDialog.new("Are you sure you want to delete #{obj.name}?") )
|
194
|
+
session.domainmodel.remove_person(obj)
|
195
|
+
session.domainmodel.persons.each do |person|
|
196
|
+
person.father = nil if person.father == obj
|
197
|
+
person.mother = nil if person.mother == obj
|
198
|
+
end
|
199
|
+
@views.delete_if { |view| view.metaobject.baseobject == obj }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
class Nemo::Examples::PersonEditor::PhoneNumber
|
206
|
+
|
207
|
+
attr_accessor :number, :type
|
208
|
+
|
209
|
+
def metaobject
|
210
|
+
Nemo.metaobject_for(self) {
|
211
|
+
text :type
|
212
|
+
label 'Type'
|
213
|
+
maxlength 10
|
214
|
+
required
|
215
|
+
|
216
|
+
text :number
|
217
|
+
label 'Number'
|
218
|
+
maxlength 20
|
219
|
+
required
|
220
|
+
}
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
224
|
+
|
225
|
+
class Nemo::Examples::PersonEditor::Person
|
226
|
+
|
227
|
+
INTERESTS = ['Art', 'Music', 'Coffee', 'Ruby']
|
228
|
+
|
229
|
+
attr_accessor :birthday, :father, :interests, :name, :mother, :password, :phone_numbers, :sex
|
230
|
+
|
231
|
+
def initialize
|
232
|
+
@interests = ['Art', 'Music']
|
233
|
+
@password = 'abc123'
|
234
|
+
@phone_numbers = Array.new
|
235
|
+
end
|
236
|
+
|
237
|
+
def age
|
238
|
+
date = (@birthday || Date.today-1)
|
239
|
+
(Date.today-date).to_i/365
|
240
|
+
end
|
241
|
+
|
242
|
+
def metaobject
|
243
|
+
Nemo.metaobject_for(self) {
|
244
|
+
text :name
|
245
|
+
label 'Full Name'
|
246
|
+
maxlength 30
|
247
|
+
required
|
248
|
+
|
249
|
+
password :password
|
250
|
+
label 'Password'
|
251
|
+
required
|
252
|
+
|
253
|
+
date :birthday
|
254
|
+
label 'Birthday'
|
255
|
+
format_with { |date| date.strftime('%b %d, %Y') }
|
256
|
+
required
|
257
|
+
|
258
|
+
text :age
|
259
|
+
label 'Age'
|
260
|
+
read_only
|
261
|
+
|
262
|
+
boolean :sex
|
263
|
+
label 'Sex'
|
264
|
+
true_item_string 'male'
|
265
|
+
false_item_string 'female'
|
266
|
+
|
267
|
+
single_relationship :father
|
268
|
+
label 'Father'
|
269
|
+
relationship_to { Wee::Session.current.domainmodel.male_persons }
|
270
|
+
format_with { |each| sprintf('%s (%s)', each.name, each.metaobject[:sex].formatted_value) }
|
271
|
+
nil_item_string 'unknown'
|
272
|
+
|
273
|
+
single_relationship :mother
|
274
|
+
label 'Mother'
|
275
|
+
relationship_to { Wee::Session.current.domainmodel.female_persons }
|
276
|
+
format_with { |each| sprintf('%s (%s)', each.name, each.metaobject[:sex].formatted_value) }
|
277
|
+
nil_item_string 'unknown'
|
278
|
+
link_action { |item| puts "==== Action was taken ====" }
|
279
|
+
|
280
|
+
multiple_relationship :interests
|
281
|
+
label 'Interests'
|
282
|
+
relationship_to { INTERESTS }
|
283
|
+
add_validation_rule { |value| value.size > 1 }.
|
284
|
+
error_string('Select at least two items of interest')
|
285
|
+
|
286
|
+
multiple :phone_numbers
|
287
|
+
label 'Phone#s'
|
288
|
+
base_class Nemo::Examples::PersonEditor::PhoneNumber
|
289
|
+
format_with { |each| sprintf('%s: %s', each.type, each.number) }
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Collect a list of Attribute objects using minimal syntax,
|
2
|
+
# Use the attribute methods (#text, #date, #boolean, etc) to add new attributes objects.
|
3
|
+
# All other messages will be sent to the most recently added attribute.
|
4
|
+
#
|
5
|
+
# collector = Nemo::MetaObject::AttributeCollector
|
6
|
+
# attributes = collector.new(metaobject).collect do
|
7
|
+
# text :name
|
8
|
+
# label 'Full Name'
|
9
|
+
# maxlength 30
|
10
|
+
# date :birthday
|
11
|
+
# label 'Birthday'
|
12
|
+
# read_only
|
13
|
+
# end
|
14
|
+
# attributes.inspect
|
15
|
+
# # => [<TextAttribute @label="Kevin", @maxlength=30>,
|
16
|
+
# # <DateAttribute @label="Birthday", @read_only=true>]
|
17
|
+
#
|
18
|
+
class Nemo::MetaObject::AttributeCollector < Nemo::Util::ObjectCollector
|
19
|
+
|
20
|
+
attr_accessor :metaobject
|
21
|
+
|
22
|
+
def initialize(metaobject)
|
23
|
+
super()
|
24
|
+
@metaobject = metaobject
|
25
|
+
@index_error = 'no attributes defined'
|
26
|
+
end
|
27
|
+
|
28
|
+
# Collects to the metaobject
|
29
|
+
#
|
30
|
+
def <<(attr)
|
31
|
+
@metaobject << attr
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
# Shortcut for creating attribute class instances
|
36
|
+
#
|
37
|
+
# self[:text, symbol] => Nemo::MetaObject::TextAttribute.new(symbol)
|
38
|
+
#
|
39
|
+
def [](type, symbol)
|
40
|
+
attr = Nemo::Util.pascal_case(type)+'Attribute'
|
41
|
+
Nemo::MetaObject.const_get(attr).new(symbol)
|
42
|
+
end
|
43
|
+
|
44
|
+
def text(symbol)
|
45
|
+
self << self[:text, symbol]
|
46
|
+
end
|
47
|
+
|
48
|
+
def password(symbol)
|
49
|
+
self << self[:password, symbol]
|
50
|
+
end
|
51
|
+
|
52
|
+
def date(symbol)
|
53
|
+
self << self[:date, symbol]
|
54
|
+
end
|
55
|
+
|
56
|
+
def boolean(symbol)
|
57
|
+
self << self[:boolean, symbol]
|
58
|
+
end
|
59
|
+
|
60
|
+
def multiple(symbol)
|
61
|
+
self << self[:multiple, symbol]
|
62
|
+
end
|
63
|
+
|
64
|
+
# SingleAttribute is simply a MultipleAttribute with an item_limit of 1
|
65
|
+
#
|
66
|
+
def single(symbol)
|
67
|
+
multiple(symbol)
|
68
|
+
item_limit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def single_relationship(symbol)
|
72
|
+
self << self[:single_relationship, symbol]
|
73
|
+
end
|
74
|
+
|
75
|
+
def multiple_relationship(symbol)
|
76
|
+
self << self[:multiple_relationship, symbol]
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
class Nemo::MetaObject::Attribute
|
2
|
+
|
3
|
+
extend Nemo::Util::Accessors
|
4
|
+
|
5
|
+
attr_accessor :cache, :metaobject, :symbol, :validation_rules
|
6
|
+
call_accessor :label
|
7
|
+
bool_accessor :hidden, :multiple_attribute, :read_only
|
8
|
+
proc_accessor :format_with
|
9
|
+
|
10
|
+
def initialize(symbol)
|
11
|
+
@format_with = Proc.new { |value| value.nil? ? value : value.to_s }
|
12
|
+
@hidden = false
|
13
|
+
@label = String.new
|
14
|
+
@multiple_attribute = false
|
15
|
+
@read_only = false
|
16
|
+
@symbol= symbol
|
17
|
+
@validation_rules = Array.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def error_class
|
21
|
+
Nemo::MetaObject::ValidationError
|
22
|
+
end
|
23
|
+
|
24
|
+
def rule_class
|
25
|
+
Nemo::MetaObject::ValidationRule
|
26
|
+
end
|
27
|
+
|
28
|
+
def accept(visitor)
|
29
|
+
raise NotImplementedError, 'subclass responsibility'
|
30
|
+
end
|
31
|
+
|
32
|
+
def commit_cache
|
33
|
+
@metaobject.baseobject.send(@symbol.to_s+'=', @cache) unless read_only?
|
34
|
+
end
|
35
|
+
|
36
|
+
def refresh_cache
|
37
|
+
@cache = value
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_cache
|
41
|
+
error = @validation_rules.find_all { |rule| ! rule.valid?(@cache) }.first
|
42
|
+
error_class.new(self, error.error_string) if error
|
43
|
+
end
|
44
|
+
|
45
|
+
def value
|
46
|
+
@metaobject.baseobject.send(@symbol)
|
47
|
+
end
|
48
|
+
|
49
|
+
def format(value)
|
50
|
+
@format_with ? @format_with.call(value).to_s : value.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def formatted_value
|
54
|
+
format(value)
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_required_rule
|
58
|
+
add_validation_rule { |value| ! value.nil? }.error_string('required')
|
59
|
+
end
|
60
|
+
|
61
|
+
def required
|
62
|
+
add_required_rule
|
63
|
+
end
|
64
|
+
|
65
|
+
def required?
|
66
|
+
@validation_rules.size > 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_validation_rule(&block)
|
70
|
+
@validation_rules << rule_class.new(&block)
|
71
|
+
@validation_rules.last
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
class Nemo::MetaObject::TextAttribute < Nemo::MetaObject::Attribute
|
77
|
+
|
78
|
+
call_accessor :maxlength
|
79
|
+
bool_accessor :multiline
|
80
|
+
|
81
|
+
def initialize(symbol)
|
82
|
+
super
|
83
|
+
@maxlength = 255
|
84
|
+
@multiline = false
|
85
|
+
end
|
86
|
+
|
87
|
+
def accept(visitor)
|
88
|
+
visitor.visit_text_attribute(self)
|
89
|
+
end
|
90
|
+
|
91
|
+
def add_required_rule
|
92
|
+
add_validation_rule { |value| ! value.to_s.strip.empty? }.error_string('required')
|
93
|
+
end
|
94
|
+
|
95
|
+
def cache
|
96
|
+
@cache.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
class Nemo::MetaObject::PasswordAttribute < Nemo::MetaObject::TextAttribute
|
102
|
+
|
103
|
+
attr_accessor :cache_confirm
|
104
|
+
|
105
|
+
def accept(visitor)
|
106
|
+
visitor.visit_password_attribute(self)
|
107
|
+
end
|
108
|
+
|
109
|
+
def refresh_cache
|
110
|
+
super
|
111
|
+
@cache_confirm = @cache
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_cache
|
115
|
+
return error_class.new(self, 'Confirmation of password is not correct.') if @cache != @cache_confirm
|
116
|
+
super
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
class Nemo::MetaObject::DateAttribute < Nemo::MetaObject::TextAttribute
|
122
|
+
|
123
|
+
def initialize(symbol)
|
124
|
+
super
|
125
|
+
@format_with = Proc.new { |value| value.to_s }
|
126
|
+
end
|
127
|
+
|
128
|
+
def accept(visitor)
|
129
|
+
visitor.visit_date_attribute(self)
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_required_rule
|
133
|
+
add_validation_rule { |value| date_from_cached(value) && true }
|
134
|
+
end
|
135
|
+
|
136
|
+
def date_from_cache
|
137
|
+
date_from_cached(@cache)
|
138
|
+
end
|
139
|
+
|
140
|
+
def date_from_cached(value)
|
141
|
+
begin Date.parse(value.to_s) rescue nil end
|
142
|
+
end
|
143
|
+
|
144
|
+
def date_to_cache(date)
|
145
|
+
@cache = begin @format_with.call(date) rescue date.to_s end
|
146
|
+
end
|
147
|
+
|
148
|
+
def refresh_cache
|
149
|
+
date_to_cache(value)
|
150
|
+
end
|
151
|
+
|
152
|
+
def commit_cache
|
153
|
+
@metaobject.baseobject.send(@symbol.to_s+'=', date_from_cache) unless read_only?
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
class Nemo::MetaObject::MultipleAttribute < Nemo::MetaObject::Attribute
|
159
|
+
|
160
|
+
call_accessor :base_class, :item_limit
|
161
|
+
bool_accessor :item_delete
|
162
|
+
proc_accessor :link_action
|
163
|
+
|
164
|
+
def initialize(symbol)
|
165
|
+
super
|
166
|
+
@multiple_attribute = true
|
167
|
+
@item_limit = 1_000_000
|
168
|
+
@item_delete = true
|
169
|
+
end
|
170
|
+
|
171
|
+
def add_required_rule
|
172
|
+
add_validation_rule { |value| ! value.to_s.empty? }.error_string('required')
|
173
|
+
end
|
174
|
+
|
175
|
+
def accept(visitor)
|
176
|
+
visitor.visit_multiple_attribute(self)
|
177
|
+
end
|
178
|
+
|
179
|
+
def formatted_value
|
180
|
+
value.collect { |item| format(item) }
|
181
|
+
end
|
182
|
+
|
183
|
+
def add_to_cache(item)
|
184
|
+
@cache ||= Array.new
|
185
|
+
@cache << item
|
186
|
+
end
|
187
|
+
|
188
|
+
def refresh_cache
|
189
|
+
@cache = value.collect
|
190
|
+
@cache = @cache[0,@item_limit] if @cache.size > @item_limit
|
191
|
+
end
|
192
|
+
|
193
|
+
def remove_from_cache(item)
|
194
|
+
cache.delete(item)
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
|
199
|
+
class Nemo::MetaObject::RelationshipAttribute < Nemo::MetaObject::Attribute
|
200
|
+
|
201
|
+
proc_accessor :link_action, :relationship_to
|
202
|
+
|
203
|
+
def items
|
204
|
+
return @relationship_to.call if @relationship_to
|
205
|
+
Array.new
|
206
|
+
end
|
207
|
+
|
208
|
+
def formatted_items
|
209
|
+
return @relationship_to.call.collect { |i| format(i) } if @relationship_to
|
210
|
+
Array.new
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
class Nemo::MetaObject::BooleanAttribute < Nemo::MetaObject::RelationshipAttribute
|
216
|
+
|
217
|
+
call_accessor :true_item_string, :false_item_string
|
218
|
+
|
219
|
+
def initialize(symbol)
|
220
|
+
super
|
221
|
+
@true_item_string = 'yes'
|
222
|
+
@false_item_string = 'no'
|
223
|
+
@relationship_to = Proc.new { [true,false] }
|
224
|
+
@format_with = Proc.new { |value| value ? @true_item_string : @false_item_string }
|
225
|
+
end
|
226
|
+
|
227
|
+
def accept(visitor)
|
228
|
+
visitor.visit_boolean_attribute(self)
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
class Nemo::MetaObject::SingleRelationshipAttribute < Nemo::MetaObject::RelationshipAttribute
|
234
|
+
|
235
|
+
call_accessor :nil_item_string
|
236
|
+
|
237
|
+
def accept(visitor)
|
238
|
+
visitor.visit_single_relationship_attribute(self)
|
239
|
+
end
|
240
|
+
|
241
|
+
def has_nil_item?
|
242
|
+
Nemo::Util.to_bool(@nil_item_string)
|
243
|
+
end
|
244
|
+
|
245
|
+
def items
|
246
|
+
@nil_item_string ? [nil]+super : super
|
247
|
+
end
|
248
|
+
|
249
|
+
def formatted_items
|
250
|
+
@nil_item_string ? [@nil_item_string]+super : super
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
class Nemo::MetaObject::MultipleRelationshipAttribute < Nemo::MetaObject::RelationshipAttribute
|
256
|
+
|
257
|
+
def accept(visitor)
|
258
|
+
visitor.visit_multiple_relationship_attribute(self)
|
259
|
+
end
|
260
|
+
|
261
|
+
def add_required_rule
|
262
|
+
add_validation_rule { |value| ! value.to_s.empty? }.error_string('required')
|
263
|
+
end
|
264
|
+
|
265
|
+
def add_to_cache(item)
|
266
|
+
@cache ||= Array.new
|
267
|
+
@cache << item
|
268
|
+
end
|
269
|
+
|
270
|
+
def refresh_cache
|
271
|
+
@cache = value.collect
|
272
|
+
end
|
273
|
+
|
274
|
+
def remove_from_cache(item)
|
275
|
+
cache.delete(item)
|
276
|
+
end
|
277
|
+
|
278
|
+
def formatted_value
|
279
|
+
value.collect { |item| format(item) }
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|