nemo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|