rhodes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +4 -0
  3. data/Manifest.txt +52 -0
  4. data/README.rdoc +2 -0
  5. data/Rakefile +33 -0
  6. data/bin/rhogen +8 -0
  7. data/generators/rhogen.rb +99 -0
  8. data/generators/templates/application/application.rb +4 -0
  9. data/generators/templates/application/index.html +25 -0
  10. data/generators/templates/model/config.rb +3 -0
  11. data/generators/templates/model/controller.rb +48 -0
  12. data/generators/templates/model/edit.erb +21 -0
  13. data/generators/templates/model/index.erb +10 -0
  14. data/generators/templates/model/new.erb +16 -0
  15. data/lib/ServeME.rb +7 -0
  16. data/lib/TestServe.rb +9 -0
  17. data/lib/builtinME.rb +481 -0
  18. data/lib/date.rb +1781 -0
  19. data/lib/date/format.rb +1313 -0
  20. data/lib/erb.rb +896 -0
  21. data/lib/find.rb +81 -0
  22. data/lib/rational.rb +19 -0
  23. data/lib/rho.rb +1 -0
  24. data/lib/rho/render.rb +9 -0
  25. data/lib/rho/renderME.rb +8 -0
  26. data/lib/rho/rho.rb +173 -0
  27. data/lib/rho/rhoapplication.rb +36 -0
  28. data/lib/rho/rhocontroller.rb +51 -0
  29. data/lib/rho/rhofsconnector.rb +39 -0
  30. data/lib/rho/rhofsconnectorME.rb +36 -0
  31. data/lib/rho/rhosupport.rb +139 -0
  32. data/lib/rhodes.rb +5 -0
  33. data/lib/rhofsconnector.rb +5 -0
  34. data/lib/rhom.rb +1 -0
  35. data/lib/rhom/rhom.rb +41 -0
  36. data/lib/rhom/rhom_db_adapter.rb +183 -0
  37. data/lib/rhom/rhom_db_adapterME.rb +91 -0
  38. data/lib/rhom/rhom_object.rb +53 -0
  39. data/lib/rhom/rhom_object_factory.rb +246 -0
  40. data/lib/singleton.rb +313 -0
  41. data/lib/time.rb +839 -0
  42. data/rhodes.gemspec +18 -0
  43. data/spec/app_generator_spec.rb +27 -0
  44. data/spec/generator_spec_helper.rb +12 -0
  45. data/spec/model_generator_spec.rb +28 -0
  46. data/spec/rho_spec.rb +28 -0
  47. data/spec/rhom_object_factory_spec.rb +147 -0
  48. data/spec/spec.opts +1 -0
  49. data/spec/spec_helper.rb +14 -0
  50. data/spec/stubs.rb +17 -0
  51. data/spec/syncdbtest.sqlite +0 -0
  52. data/tasks/rspec.rake +34 -0
  53. metadata +157 -0
@@ -0,0 +1,53 @@
1
+ #
2
+ # rhom_object.rb
3
+ # rhodes
4
+ #
5
+ # Copyright (C) 2008 Lars Burgess. All rights reserved.
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ #
20
+ module Rhom
21
+ module RhomObject
22
+ # defines a method at runtime for the
23
+ # dynamically created class
24
+
25
+
26
+ # we override method_missing here so that instance variables,
27
+ # when retrieved or set, are added to the object
28
+ def method_missing(name, *args)
29
+ unless name == Fixnum
30
+ varname = name.to_s.gsub(/=/,"")
31
+ if instance_variable_defined? "@#{varname}"
32
+ #TODO: Figure out why this returns an array
33
+ instance_variable_get( "@#{varname}" )[0]
34
+ else
35
+ instance_variable_set( "@#{varname}", args )
36
+ end
37
+ end
38
+ end
39
+
40
+ def strip_braces(str=nil)
41
+ str ? str.gsub(/\{/,"").gsub(/\}/,"") : nil
42
+ end
43
+
44
+ # use djb hash function to generate temp object id
45
+ def djb_hash(str, len)
46
+ hash = 5381
47
+ for i in (0..len)
48
+ hash = ((hash << 5) + hash) + str[i].to_i
49
+ end
50
+ return hash
51
+ end
52
+ end # RhomObject
53
+ end # Rhom
@@ -0,0 +1,246 @@
1
+ #
2
+ # rhom_object_factory.rb
3
+ # rhodes
4
+ # Returns an array of RhomObjects
5
+ #
6
+ # Copyright (C) 2008 Lars Burgess. All rights reserved.
7
+ #
8
+ # This program is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ #
21
+ require 'rhom'
22
+ require 'rho'
23
+
24
+ module Rhom
25
+ class RhomObjectFactory
26
+
27
+ def initialize
28
+ unless not defined? Rho::RhoConfig::sources
29
+ init_source_attribs
30
+ init_objects
31
+ end
32
+ end
33
+
34
+ def init_source_attribs
35
+ # merge source attributes into config hash
36
+ # TODO: This shouldn't reference 'source[1]' directly
37
+ Rho::RhoConfig::sources.each do |source|
38
+ src_attribs = ::Rhom::RhomDbAdapter::select_from_table(::Rhom::TABLE_NAME,
39
+ 'attrib',
40
+ {"source_id"=>source[1]['source_id'].to_s},
41
+ {"distinct"=>true})
42
+ # update our source with the proper attributes
43
+ source[1].merge!({"attribs"=>src_attribs})
44
+ end
45
+ end
46
+
47
+ # Initialize new object with dynamic attributes
48
+ def init_objects
49
+ Rho::RhoConfig::sources.each do |classname,source|
50
+ unless Object::const_defined?(classname.intern)
51
+ Object::const_set(classname.intern,
52
+ Class::new do
53
+ include ::Rhom::RhomObject
54
+ extend ::Rhom::RhomObject
55
+
56
+ def initialize(obj=nil)
57
+ if obj
58
+ # create a temp id for the create type
59
+ # TODO: This is duplicative of get_new_obj
60
+ temp_objid = djb_hash(obj.values.to_s, 10).to_s
61
+ self.send 'object'.to_sym, "#{temp_objid}"
62
+ self.send 'source_id'.to_sym, obj['source_id'].to_s
63
+ self.send 'update_type'.to_sym, 'create'
64
+ obj.each do |key,value|
65
+ val = self.inst_strip_braces(value)
66
+ self.send key, val
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ class << self
73
+
74
+ def get_source_id
75
+ Rho::RhoConfig::sources[self.name.to_s]['source_id'].to_s
76
+ end
77
+ # retrieve a single record if object id provided, otherwise return
78
+ # full list corresponding to factory's source id
79
+ def find(*args)
80
+ list = []
81
+ if args.first == :all
82
+ result = ::Rhom::RhomDbAdapter::select_from_table(::Rhom::TABLE_NAME,
83
+ '*',
84
+ {"source_id"=>get_source_id,"update_type"=>'query'},
85
+ {"order by"=>'object'})
86
+ else
87
+ obj = strip_braces(args.first.to_s)
88
+ result = ::Rhom::RhomDbAdapter::select_from_table(::Rhom::TABLE_NAME,
89
+ '*',
90
+ {"object"=>obj})
91
+ end
92
+ list = get_list(result)
93
+ if list.length == 1
94
+ return list[0]
95
+ end
96
+ list
97
+ end
98
+
99
+ def find_by(*args)
100
+ # TODO: implement
101
+ end
102
+
103
+ # returns an array of objects based on an existing array
104
+ def get_list(objs)
105
+ new_list = []
106
+ if objs and defined? Rho::RhoConfig::sources[self.name.to_s]
107
+ attrib_length = Rho::RhoConfig::sources[self.name.to_s]['attribs'].length
108
+ list_length = 0
109
+ list_length = (objs.length / attrib_length) unless attrib_length == 0
110
+ new_obj = nil
111
+ # iterate over the array and determine object
112
+ # structure based on attribute/value pairs
113
+ list_length.times do |i|
114
+ new_obj = get_new_obj(objs[i*attrib_length])
115
+ attrib_length.times do |j|
116
+ # setup index and assign accessors
117
+ idx = i*attrib_length+j
118
+ begin
119
+ # only update attributes if they belong
120
+ # to the current object
121
+ if objs[idx]['object'] == strip_braces((new_obj.send 'object'.to_sym))
122
+ attrib = objs[idx]['attrib'].to_s
123
+ value = objs[idx]['value'].to_s
124
+ new_obj.send attrib.to_sym, value
125
+ end
126
+ rescue
127
+ puts "failed to reference objs[#{idx}]..."
128
+ end
129
+ end
130
+ new_list << new_obj
131
+ end
132
+ else
133
+ # source attributes are not initialized,
134
+ # try again
135
+ RhomObjectFactory::init_sources
136
+ end
137
+ new_list
138
+ end
139
+
140
+ # returns new model instance with a temp object id
141
+ def get_new_obj(obj, type='query')
142
+ tmp_obj = self.new
143
+ tmp_obj.send 'object'.to_sym, "{#{obj['object'].to_s}}"
144
+ tmp_obj.send 'source_id'.to_sym, get_source_id
145
+ tmp_obj.send 'update_type'.to_sym, type
146
+ tmp_obj
147
+ end
148
+ end #class methods
149
+
150
+ # deletes the record from the viewable list as well as
151
+ # adding a delete record to the list of sync operations
152
+ def destroy
153
+ result = nil
154
+ obj = self.inst_strip_braces(self.object)
155
+ if obj
156
+ # first delete the record from viewable list
157
+ result = ::Rhom::RhomDbAdapter::delete_from_table(::Rhom::TABLE_NAME,
158
+ {"object"=>obj})
159
+ # now add delete operation
160
+ result = ::Rhom::RhomDbAdapter::insert_into_table(::Rhom::TABLE_NAME,
161
+ {"source_id"=>self.get_inst_source_id,
162
+ "object"=>obj,
163
+ "update_type"=>'delete'})
164
+ end
165
+ result
166
+ end
167
+
168
+ # saves the current object to the database as a create type
169
+ def save
170
+ result = nil
171
+ # iterate over each instance variable and insert create row to table
172
+ obj = self.inst_strip_braces(self.object)
173
+ self.instance_variables.each do |method|
174
+ method = method.to_s.gsub(/@/,"")
175
+ # Don't save objects with braces to database
176
+ val = self.inst_strip_braces(self.send(method.to_sym))
177
+ # add rows excluding object, source_id and update_type
178
+ unless self.method_name_reserved?(method) or val.nil?
179
+ result = ::Rhom::RhomDbAdapter::insert_into_table(::Rhom::TABLE_NAME,
180
+ {"source_id"=>self.get_inst_source_id,
181
+ "object"=>obj,
182
+ "attrib"=>method,
183
+ "value"=>val,
184
+ "update_type"=>'create'})
185
+ end
186
+ end
187
+ # Create a temporary query record to display in the list
188
+ Rho::RhoConfig::sources[self.class.name.to_s]['attribs'].each do |attrib|
189
+ result = ::Rhom::RhomDbAdapter::insert_into_table(::Rhom::TABLE_NAME,
190
+ {"source_id"=>self.get_inst_source_id,
191
+ "object"=>obj,
192
+ "attrib"=>attrib['attrib'],
193
+ "value"=>self.send(attrib['attrib'].to_sym),
194
+ "update_type"=>'query'})
195
+ end
196
+ result
197
+ end
198
+
199
+ # updates the current record in the viewable list and adds
200
+ # a sync operation to update
201
+ def update_attributes(attrs)
202
+ result = nil
203
+ obj = self.inst_strip_braces(self.object)
204
+ self.instance_variables.each do |method|
205
+ method = method.to_s.gsub(/@/,"")
206
+ val = self.send method.to_sym
207
+ # Don't save objects with braces to database
208
+ new_val = self.inst_strip_braces(attrs[method])
209
+ # if the object's value doesn't match the database record
210
+ # then we procede with update
211
+ if new_val and val != new_val
212
+ unless self.method_name_reserved?(method) or new_val.length == 0
213
+ # update viewable list
214
+ result = ::Rhom::RhomDbAdapter::update_into_table(::Rhom::TABLE_NAME,
215
+ {"value"=>new_val},
216
+ {"object"=>obj, "attrib"=>method})
217
+ # update sync list
218
+ result = ::Rhom::RhomDbAdapter::insert_into_table(::Rhom::TABLE_NAME,
219
+ {"source_id"=>self.get_inst_source_id,
220
+ "object"=>obj,
221
+ "attrib"=>method,
222
+ "value"=>new_val,
223
+ "update_type"=>'update'})
224
+ end
225
+ end
226
+ end
227
+ result
228
+ end
229
+
230
+ def get_inst_source_id
231
+ Rho::RhoConfig::sources[self.class.name.to_s]['source_id'].to_s
232
+ end
233
+
234
+ def inst_strip_braces(str=nil)
235
+ str ? str.gsub(/\{/,"").gsub(/\}/,"") : nil
236
+ end
237
+
238
+ def method_name_reserved?(method)
239
+ method =~ /object|source_id|update_type/
240
+ end
241
+ end)
242
+ end
243
+ end
244
+ end
245
+ end # RhomObjectFactory
246
+ end # Rhom
data/lib/singleton.rb ADDED
@@ -0,0 +1,313 @@
1
+ # The Singleton module implements the Singleton pattern.
2
+ #
3
+ # Usage:
4
+ # class Klass
5
+ # include Singleton
6
+ # # ...
7
+ # end
8
+ #
9
+ # * this ensures that only one instance of Klass lets call it
10
+ # ``the instance'' can be created.
11
+ #
12
+ # a,b = Klass.instance, Klass.instance
13
+ # a == b # => true
14
+ # a.new # NoMethodError - new is private ...
15
+ #
16
+ # * ``The instance'' is created at instantiation time, in other
17
+ # words the first call of Klass.instance(), thus
18
+ #
19
+ # class OtherKlass
20
+ # include Singleton
21
+ # # ...
22
+ # end
23
+ # ObjectSpace.each_object(OtherKlass){} # => 0.
24
+ #
25
+ # * This behavior is preserved under inheritance and cloning.
26
+ #
27
+ #
28
+ #
29
+ # This is achieved by marking
30
+ # * Klass.new and Klass.allocate - as private
31
+ #
32
+ # Providing (or modifying) the class methods
33
+ # * Klass.inherited(sub_klass) and Klass.clone() -
34
+ # to ensure that the Singleton pattern is properly
35
+ # inherited and cloned.
36
+ #
37
+ # * Klass.instance() - returning ``the instance''. After a
38
+ # successful self modifying (normally the first) call the
39
+ # method body is a simple:
40
+ #
41
+ # def Klass.instance()
42
+ # return @singleton__instance__
43
+ # end
44
+ #
45
+ # * Klass._load(str) - calling Klass.instance()
46
+ #
47
+ # * Klass._instantiate?() - returning ``the instance'' or
48
+ # nil. This hook method puts a second (or nth) thread calling
49
+ # Klass.instance() on a waiting loop. The return value
50
+ # signifies the successful completion or premature termination
51
+ # of the first, or more generally, current "instantiation thread".
52
+ #
53
+ #
54
+ # The instance method of Singleton are
55
+ # * clone and dup - raising TypeErrors to prevent cloning or duping
56
+ #
57
+ # * _dump(depth) - returning the empty string. Marshalling strips
58
+ # by default all state information, e.g. instance variables and
59
+ # taint state, from ``the instance''. Providing custom _load(str)
60
+ # and _dump(depth) hooks allows the (partially) resurrections of
61
+ # a previous state of ``the instance''.
62
+
63
+ require 'thread'
64
+
65
+ module Singleton
66
+ # disable build-in copying methods
67
+ def clone
68
+ raise TypeError, "can't clone instance of singleton #{self.class}"
69
+ end
70
+ def dup
71
+ raise TypeError, "can't dup instance of singleton #{self.class}"
72
+ end
73
+
74
+ # default marshalling strategy
75
+ def _dump(depth = -1)
76
+ ''
77
+ end
78
+
79
+ module SingletonClassMethods
80
+ # properly clone the Singleton pattern - did you know
81
+ # that duping doesn't copy class methods?
82
+ def clone
83
+ Singleton.__init__(super)
84
+ end
85
+
86
+ def _load(str)
87
+ instance
88
+ end
89
+
90
+ private
91
+
92
+ # ensure that the Singleton pattern is properly inherited
93
+ def inherited(sub_klass)
94
+ super
95
+ Singleton.__init__(sub_klass)
96
+ end
97
+ end
98
+
99
+ class << Singleton
100
+ def __init__(klass)
101
+ klass.instance_eval {
102
+ @singleton__instance__ = nil
103
+ @singleton__mutex__ = Mutex.new
104
+ }
105
+ def klass.instance
106
+ return @singleton__instance__ if @singleton__instance__
107
+ @singleton__mutex__.synchronize {
108
+ return @singleton__instance__ if @singleton__instance__
109
+ @singleton__instance__ = new()
110
+ }
111
+ @singleton__instance__
112
+ end
113
+ klass
114
+ end
115
+
116
+ private
117
+
118
+ # extending an object with Singleton is a bad idea
119
+ undef_method :extend_object
120
+
121
+ def append_features(mod)
122
+ # help out people counting on transitive mixins
123
+ unless mod.instance_of?(Class)
124
+ raise TypeError, "Inclusion of the OO-Singleton module in module #{mod}"
125
+ end
126
+ super
127
+ end
128
+
129
+ def included(klass)
130
+ super
131
+ klass.private_class_method :new, :allocate
132
+ klass.extend SingletonClassMethods
133
+ Singleton.__init__(klass)
134
+ end
135
+ end
136
+
137
+ end
138
+
139
+
140
+ if __FILE__ == $0
141
+
142
+ def num_of_instances(klass)
143
+ "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
144
+ end
145
+
146
+ # The basic and most important example.
147
+
148
+ class SomeSingletonClass
149
+ include Singleton
150
+ end
151
+ puts "There are #{num_of_instances(SomeSingletonClass)}"
152
+
153
+ a = SomeSingletonClass.instance
154
+ b = SomeSingletonClass.instance # a and b are same object
155
+ puts "basic test is #{a == b}"
156
+
157
+ begin
158
+ SomeSingletonClass.new
159
+ rescue NoMethodError => mes
160
+ puts mes
161
+ end
162
+
163
+
164
+
165
+ puts "\nThreaded example with exception and customized #_instantiate?() hook"; p
166
+ Thread.abort_on_exception = false
167
+
168
+ class Ups < SomeSingletonClass
169
+ def initialize
170
+ self.class.__sleep
171
+ puts "initialize called by thread ##{Thread.current[:i]}"
172
+ end
173
+ end
174
+
175
+ class << Ups
176
+ def _instantiate?
177
+ @enter.push Thread.current[:i]
178
+ while false.equal?(@singleton__instance__)
179
+ @singleton__mutex__.unlock
180
+ sleep 0.08
181
+ @singleton__mutex__.lock
182
+ end
183
+ @leave.push Thread.current[:i]
184
+ @singleton__instance__
185
+ end
186
+
187
+ def __sleep
188
+ sleep(rand(0.08))
189
+ end
190
+
191
+ def new
192
+ begin
193
+ __sleep
194
+ raise "boom - thread ##{Thread.current[:i]} failed to create instance"
195
+ ensure
196
+ # simple flip-flop
197
+ class << self
198
+ remove_method :new
199
+ end
200
+ end
201
+ end
202
+
203
+ def instantiate_all
204
+ @enter = []
205
+ @leave = []
206
+ 1.upto(9) {|i|
207
+ Thread.new {
208
+ begin
209
+ Thread.current[:i] = i
210
+ __sleep
211
+ instance
212
+ rescue RuntimeError => mes
213
+ puts mes
214
+ end
215
+ }
216
+ }
217
+ puts "Before there were #{num_of_instances(self)}"
218
+ sleep 3
219
+ puts "Now there is #{num_of_instances(self)}"
220
+ puts "#{@enter.join '; '} was the order of threads entering the waiting loop"
221
+ puts "#{@leave.join '; '} was the order of threads leaving the waiting loop"
222
+ end
223
+ end
224
+
225
+
226
+ Ups.instantiate_all
227
+ # results in message like
228
+ # Before there were 0 Ups instance(s)
229
+ # boom - thread #6 failed to create instance
230
+ # initialize called by thread #3
231
+ # Now there is 1 Ups instance(s)
232
+ # 3; 2; 1; 8; 4; 7; 5 was the order of threads entering the waiting loop
233
+ # 3; 2; 1; 7; 4; 8; 5 was the order of threads leaving the waiting loop
234
+
235
+
236
+ puts "\nLets see if class level cloning really works"
237
+ Yup = Ups.clone
238
+ def Yup.new
239
+ begin
240
+ __sleep
241
+ raise "boom - thread ##{Thread.current[:i]} failed to create instance"
242
+ ensure
243
+ # simple flip-flop
244
+ class << self
245
+ remove_method :new
246
+ end
247
+ end
248
+ end
249
+ Yup.instantiate_all
250
+
251
+
252
+ puts "\n\n","Customized marshalling"
253
+ class A
254
+ include Singleton
255
+ attr_accessor :persist, :die
256
+ def _dump(depth)
257
+ # this strips the @die information from the instance
258
+ Marshal.dump(@persist,depth)
259
+ end
260
+ end
261
+
262
+ def A._load(str)
263
+ instance.persist = Marshal.load(str)
264
+ instance
265
+ end
266
+
267
+ a = A.instance
268
+ a.persist = ["persist"]
269
+ a.die = "die"
270
+ a.taint
271
+
272
+ stored_state = Marshal.dump(a)
273
+ # change state
274
+ a.persist = nil
275
+ a.die = nil
276
+ b = Marshal.load(stored_state)
277
+ p a == b # => true
278
+ p a.persist # => ["persist"]
279
+ p a.die # => nil
280
+
281
+
282
+ puts "\n\nSingleton with overridden default #inherited() hook"
283
+ class Up
284
+ end
285
+ def Up.inherited(sub_klass)
286
+ puts "#{sub_klass} subclasses #{self}"
287
+ end
288
+
289
+
290
+ class Middle < Up
291
+ include Singleton
292
+ end
293
+
294
+ class Down < Middle; end
295
+
296
+ puts "and basic \"Down test\" is #{Down.instance == Down.instance}\n
297
+ Various exceptions"
298
+
299
+ begin
300
+ module AModule
301
+ include Singleton
302
+ end
303
+ rescue TypeError => mes
304
+ puts mes #=> Inclusion of the OO-Singleton module in module AModule
305
+ end
306
+
307
+ begin
308
+ 'aString'.extend Singleton
309
+ rescue NoMethodError => mes
310
+ puts mes #=> undefined method `extend_object' for Singleton:Module
311
+ end
312
+
313
+ end