nemo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,5 @@
1
+ module Nemo::MetaObject; end
2
+ require 'nemo/metaobject/acollector'
3
+ require 'nemo/metaobject/attributes'
4
+ require 'nemo/metaobject/metaobject'
5
+ require 'nemo/metaobject/validation'
@@ -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