lore 0.4.2

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.
Files changed (90) hide show
  1. data/LICENSE +19 -0
  2. data/README +74 -0
  3. data/aspect.rb +80 -0
  4. data/behaviours/lockable.rb +41 -0
  5. data/behaviours/movable.rb +54 -0
  6. data/behaviours/versioned.rb +24 -0
  7. data/benchmark.rb +193 -0
  8. data/bits.rb +52 -0
  9. data/cache/abstract_entity_cache.rb +82 -0
  10. data/cache/bits.rb +22 -0
  11. data/cache/cacheable.rb +202 -0
  12. data/cache/cached_entities.rb +116 -0
  13. data/cache/file_index.rb +35 -0
  14. data/cache/mmap_entity_cache.rb +67 -0
  15. data/clause.rb +528 -0
  16. data/connection.rb +155 -0
  17. data/custom_functions.sql +14 -0
  18. data/exception/ambiguous_attribute.rb +14 -0
  19. data/exception/cache_exception.rb +30 -0
  20. data/exception/invalid_klass_parameters.rb +63 -0
  21. data/exception/invalid_parameter.rb +42 -0
  22. data/exception/unknown_typecode.rb +19 -0
  23. data/file_index.sql +56 -0
  24. data/gui/erb_template.rb +79 -0
  25. data/gui/erb_template_helpers.rhtml +19 -0
  26. data/gui/form.rb +314 -0
  27. data/gui/form_element.rb +676 -0
  28. data/gui/form_generator.rb +151 -0
  29. data/gui/templates/button.rhtml +2 -0
  30. data/gui/templates/checkbox.rhtml +3 -0
  31. data/gui/templates/checkbox_row.rhtml +1 -0
  32. data/gui/templates/file.rhtml +2 -0
  33. data/gui/templates/file_readonly.rhtml +3 -0
  34. data/gui/templates/form_element.rhtml +5 -0
  35. data/gui/templates/form_element_horizontal.rhtml +3 -0
  36. data/gui/templates/form_element_listed.rhtml +8 -0
  37. data/gui/templates/form_table.rhtml +3 -0
  38. data/gui/templates/form_table_blank.rhtml +3 -0
  39. data/gui/templates/form_table_horizontal.rhtml +8 -0
  40. data/gui/templates/password.rhtml +2 -0
  41. data/gui/templates/password_readonly.rhtml +3 -0
  42. data/gui/templates/radio.rhtml +1 -0
  43. data/gui/templates/radio_row.rhtml +1 -0
  44. data/gui/templates/select.rhtml +23 -0
  45. data/gui/templates/text.rhtml +2 -0
  46. data/gui/templates/text_readonly.rhtml +3 -0
  47. data/gui/templates/textarea.rhtml +3 -0
  48. data/gui/templates/textarea_readonly.rhtml +4 -0
  49. data/lore.gemspec +40 -0
  50. data/lore.rb +94 -0
  51. data/migration.rb +48 -0
  52. data/model.rb +139 -0
  53. data/model_factory.rb +202 -0
  54. data/model_shortcuts.rb +16 -0
  55. data/query_shortcuts.rb +367 -0
  56. data/reserved_methods.txt +3 -0
  57. data/result.rb +100 -0
  58. data/symbol.rb +58 -0
  59. data/table_accessor.rb +1926 -0
  60. data/table_deleter.rb +115 -0
  61. data/table_inserter.rb +168 -0
  62. data/table_instance.rb +384 -0
  63. data/table_selector.rb +314 -0
  64. data/table_updater.rb +155 -0
  65. data/test/README +31 -0
  66. data/test/env.rb +5 -0
  67. data/test/lore_test.log +8218 -0
  68. data/test/model.rb +142 -0
  69. data/test/prepare.rb +37 -0
  70. data/test/tc_aspect.rb +58 -0
  71. data/test/tc_cache.rb +80 -0
  72. data/test/tc_clause.rb +104 -0
  73. data/test/tc_deep_inheritance.rb +49 -0
  74. data/test/tc_factory.rb +57 -0
  75. data/test/tc_filter.rb +37 -0
  76. data/test/tc_form.rb +32 -0
  77. data/test/tc_model.rb +86 -0
  78. data/test/tc_prepare.rb +45 -0
  79. data/test/tc_refined_query.rb +88 -0
  80. data/test/tc_table_accessor.rb +265 -0
  81. data/test/test.log +181 -0
  82. data/test/test_db.sql +400 -0
  83. data/test/ts_lore.rb +49 -0
  84. data/types.rb +55 -0
  85. data/validation/message.rb +60 -0
  86. data/validation/parameter_validator.rb +104 -0
  87. data/validation/reason.rb +54 -0
  88. data/validation/type_validator.rb +91 -0
  89. data/validation.rb +65 -0
  90. metadata +170 -0
data/model_factory.rb ADDED
@@ -0,0 +1,202 @@
1
+
2
+ def global_eval(string)
3
+ eval(string)
4
+ end
5
+
6
+ module Lore
7
+
8
+ class Model_Factory
9
+
10
+ attr_accessor :output_folder, :output_file
11
+ attr_reader :model_name, :fields, :primary_keys, :aggregates, :types, :labels
12
+
13
+ # Usage:
14
+ # builder = Model_Builder.new('new_model')
15
+ # builder.add_attribute('model_id', :type => Type.integer, :not_null => true)
16
+ # builder.add_attribute('name', :type => Type.integer, :not_null => true, :check => "name <> ''", :unique => true)
17
+ # builder.add_attribute('created', :type => Type.timestamp, :not_null => true, :default => 'now()')
18
+ # builder.add_primary_key(:key_name => 'model_pkey', :attribute => 'model_id')
19
+ # builder.set_table_space('diskvol1')
20
+ # builder.build()
21
+ def initialize(model_name, connection=nil)
22
+ name_parts = model_name.split('::')
23
+ @model_name = name_parts[-1]
24
+ @namespaces = name_parts[0..-2]
25
+
26
+ @table_name = @model_name.downcase
27
+
28
+ @output_folder = './'
29
+ @output_file = @table_name + '.rb'
30
+
31
+ @connection = connection
32
+ @connection = Lore::Connection unless @connection
33
+ @table_space = ''
34
+
35
+ @fields = Array.new
36
+ @types = Array.new
37
+ @labels = Array.new
38
+ @aggregates = Array.new
39
+ @attributes = Array.new
40
+ @constraints = Array.new
41
+ @schema_name = :public
42
+ @primary_keys = Array.new
43
+ @attribute_types = Hash.new
44
+
45
+ @base_model_klass = 'Lore::Model'
46
+ @base_model_klass_file = 'lore/model'
47
+ end
48
+
49
+ def self.drop_model(model)
50
+ query = 'DROP TABLE ' << model.table_name + ';'
51
+ model.get_sequences()[model.table_name].each { |key, seq|
52
+ query << 'DROP SEQUENCE ' << seq.to_s + ";\n"
53
+ }
54
+ Lore::Connection.perform(query)
55
+ end
56
+
57
+ def use_model(model_klass_string, model_klass_file)
58
+ @base_model_klass = model_klass_string
59
+ end
60
+
61
+ public
62
+
63
+ # Usage:
64
+ #
65
+ # add_attribute('primary_id',
66
+ # :type => integer,
67
+ # :not_null => true,
68
+ # :default => '0',
69
+ # :length => 20,
70
+ # :check => '...')
71
+ #
72
+ def add_attribute(attrib_name, attrib_hash)
73
+
74
+ @fields << { :name => attrib_name, :type => attrib_hash[:type] }
75
+
76
+ attribute_part = ''
77
+ attribute_part << attrib_name + "\t " << attrib_hash[:type].to_s
78
+ if attrib_hash[:length].instance_of? Integer then
79
+ attribute_part << '(' << attrib_hash[:length].to_s + ')'
80
+ end
81
+ attribute_part << ' UNIQUE ' if attrib_hash[:unique]
82
+ attribute_part << ' NOT NULL ' if attrib_hash[:not_null]
83
+
84
+ @attributes << attribute_part
85
+ @attribute_types[attrib_name] = attrib_hash[:type]
86
+ end
87
+ def set_attributes(attrib_hash)
88
+ attrib_hash.each_pair { |attrib_name, attrib_props|
89
+ add_attribute(attrib_name.to_s, attrib_props)
90
+ }
91
+ end
92
+
93
+ def set_table_space(table_space)
94
+ @table_space = table_space
95
+ end
96
+
97
+ def add_has_a(model, attribute_name)
98
+ @aggregates << [:has_a, model, attribute_name]
99
+ end
100
+
101
+ def add_is_a(model, attribute_name)
102
+ @aggregates << [:is_a, model, attribute]
103
+ end
104
+
105
+ def add_label(label)
106
+ @labels << label.downcase
107
+ end
108
+
109
+ def set_labels(labels)
110
+ @labels = labels
111
+ end
112
+
113
+ # Usage:
114
+ # add_primary_key(:attribute => 'my_id', :key_name => 'my_model_pkey')
115
+ # or:
116
+ # add_primary_key(:attributes => ['id_1', 'id_2'], :key_name => 'my_model_pkey')
117
+ def add_primary_key(key_hash)
118
+ key_hash[:attribute] = key_hash[:attribute].to_s
119
+ key_hash[:key_name] = key_hash[:key_name].to_s
120
+ @primary_keys << [ key_hash[:attribute], key_hash[:attribute]+'_seq', key_hash[:key_name] ]
121
+ end
122
+
123
+ public
124
+
125
+ # Finally installs model table
126
+ def build_table
127
+
128
+ query = ''
129
+ pkey_constraint = "CONSTRAINT #{@table_name}_pkey PRIMARY KEY (#{@primary_keys.collect { |pk| pk[0] }.join(',')})\n"
130
+ @primary_keys.each { |pk|
131
+ query << 'CREATE SEQUENCE ' << pk[1] + ';' << "\n"
132
+ }
133
+
134
+ query << 'CREATE TABLE ' << @table_name + " ( \n"
135
+ query << @attributes.join(", \n")
136
+ query << ', ' << "\n"
137
+ query << pkey_constraint
138
+ query << "\n" << ') ' << @table_space + ';'
139
+
140
+ @connection.perform(query)
141
+ end
142
+
143
+ def build_model_klass
144
+ build_table
145
+ model_klass_string = build_model_klass_string
146
+ if @output_file then
147
+ out = @output_folder + @output_file
148
+ prepend_header = !File.exists?(out)
149
+ File.open(out, 'a+') { |f|
150
+ f << "\nrequire('" << @base_model_klass_file + "') \n\n" if prepend_header
151
+ prepend_header = false
152
+ f << model_klass_string
153
+ f << "\n\n"
154
+ }
155
+ end
156
+ global_eval(model_klass_string)
157
+ end
158
+
159
+ def build_model_klass_string
160
+
161
+ requires = ''
162
+ @aggregates.each { |type, model_name, attribute|
163
+ requires << "require('aurita/main/model/" << model_name.downcase + "')"
164
+ requires << "\n"
165
+ }
166
+ requires << "\n"
167
+
168
+ model = requires
169
+
170
+ model << ' class ' << @model_name.to_s + ' < ' << @base_model_klass + " \n"
171
+ model << ' table :' << @table_name.to_s
172
+ model << ', :' << @schema_name.to_s
173
+ model << "\n"
174
+ @primary_keys.each { |key|
175
+ model << ' primary_key :' << key[0]
176
+ model << ', :' << key[1] if key[1]
177
+ model << "\n"
178
+ }
179
+ @attribute_types.each_pair { |attr, type|
180
+ model << ' has_attribute :' << attr + ', Lore::Type.' << type
181
+ model << "\n"
182
+ }
183
+
184
+ model << ' use_label :' << @labels.join(', :') if @labels.first
185
+ model << "\n"
186
+
187
+ @aggregates.each { |type, model_name, attribute_name|
188
+ model << ' has_a ' << model_name + ', :' << attribute_name.downcase if type==:has_a
189
+ model << ' is_a ' << model_name + ', :' << attribute_name.downcase if type==:is_a
190
+ model << "\n"
191
+ }
192
+ model << "\n" << ' end'
193
+ @namespaces.reverse.each { |ns|
194
+ model = 'module ' << ns + "\n" << model << "\n" << 'end'
195
+
196
+ }
197
+ return model
198
+ end
199
+
200
+ end # class
201
+
202
+ end # module
@@ -0,0 +1,16 @@
1
+
2
+ module Lore
3
+
4
+ module Model_Shortcuts
5
+
6
+ def html_escape_values_of(*attributes)
7
+ attributes.each { |attrib|
8
+ add_input_filter(attrib) { |a|
9
+ a.gsub("'",'&apos;')
10
+ }
11
+ }
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,367 @@
1
+
2
+ module Lore
3
+
4
+ class Refined_Query
5
+
6
+ def initialize(accessor, statements={})
7
+ @accessor = accessor
8
+ @condition = statements[:condition]
9
+ @what = statements[:what]
10
+ @limit = statements[:limit]
11
+ @offset = statements[:offset]
12
+ @order_by = statements[:order_by]
13
+ @order_by = Array.new unless @order_by
14
+ @group_by = statements[:group_by]
15
+ @having = statements[:having]
16
+ @condition = true unless @condition
17
+ # @what = @what.to_s unless @what.nil?
18
+ end
19
+
20
+ # Example:
21
+ # Adds where-condition to query. Multiple calls stack using AND. Examples;
22
+ #
23
+ # Type.find(1).with(Type.name == 'foo' & Type.position > 2).having(<...>).ordered_by(:name).result
24
+ # # ... is same as ...
25
+ # Type.find(1).with(Type.name == 'foo').with(Type.position > 2).having(<...>).ordered_by(:name).result
26
+ #
27
+ # User.all.with(User.user_id.in( Admin.all(:user_id) ))
28
+ #
29
+ def with(condition)
30
+ if((@condition.instance_of? TrueClass) || @condition.nil?) then
31
+ @condition = condition
32
+ else
33
+ @condition = (@condition & condition)
34
+ end
35
+ return self
36
+ end
37
+ alias where with
38
+
39
+ # Adds HAVING statement to query. Examples:
40
+ #
41
+ # User.all.having(User.user_id.in( Admin.all(:user_id) ))
42
+ #
43
+ def having(having_clause)
44
+ @having = having_clause
45
+ return self
46
+ end
47
+
48
+ # To be overloaded by derived classes
49
+ def perform
50
+ end
51
+
52
+ # Provides wrapper for simple SQL attribute operation keywords.
53
+ # Examples:
54
+ #
55
+ # Car.value_of.sum(Car.car_id).to_i
56
+ # -> SELECT sum(public.car.car_id) ...
57
+ # -> Amount of all cars as integer, e.g. 2342
58
+ # Car.value_of.max(Car.car_id).to_i
59
+ # -> SELECT max(public.car.car_id) ...
60
+ # -> Highest car_id as integer, e.g. 14223
61
+ #
62
+ def method_missing(method, attribute_name)
63
+ @what = method.to_s << '(' << attribute_name.to_s << ') AS "value"'
64
+ return self
65
+ end
66
+
67
+ end # class
68
+
69
+ class Refined_Select < Refined_Query
70
+
71
+ def initialize(accessor, statements={})
72
+ super(accessor, statements)
73
+ end
74
+
75
+ # Examples:
76
+ #
77
+ # Initiates request for single attribute values instead
78
+ # model instances. Returns value as string, array or two-dimensional
79
+ # array, depending from parameters passed:
80
+ #
81
+ # Car.find(2).values_of(Car.name, Car.id).ordered_by(Car.name, :asc).entities
82
+ # -> [ ['BMW318i', '3'], ['BMW Mini', '5'] ]
83
+ # Car.find(2).values_of(Car.name).ordered_by(Car.name, :asc).entities
84
+ # -> [ 'BMW318i', 'BMW Mini']
85
+ # Car.find(1).value_of(Car.name, Car.type).with(Car.name.ilike '%BMW%').entity
86
+ # -> ['BMW318i', 'Convertible']
87
+ # Car.find(1).value_of(Car.name).with(Car.name.ilike '%BMW%').entity
88
+ # -> 'BMW318i'
89
+ #
90
+ def values_of(*what)
91
+ @what = what
92
+ return self
93
+ end
94
+ alias value_of values_of
95
+
96
+ # Same as #entities.first. Useful when requesting one row or a single
97
+ # attribute only.
98
+ def entity
99
+ entities.first
100
+ end
101
+
102
+ def first
103
+ entities.first
104
+ end
105
+
106
+ # When requesting a single value, #to_i can be used to
107
+ # retreive the result as string instead of calling #entity:
108
+ #
109
+ # s1 = User.all(User.name).with(User.name.like '%Christina%').to_s
110
+ # s2 = User.all(User.name).with(User.name.like '%Christina%').entity
111
+ # assert_equal s1, s2
112
+ #
113
+ def to_s
114
+ entities.first.to_s
115
+ end
116
+ # When requesting a single value, #to_i can be used to
117
+ # retreive the result as integer instead of calling #entity:
118
+ #
119
+ # i1 = User.all(User.id).with(User.name.like '%Christina%').to_i
120
+ # i2 = User.all(User.id).with(User.name.like '%Christina%').entity.to_i
121
+ # assert_equal i1, i2
122
+ #
123
+ def to_i
124
+ entities.first.to_i
125
+ end
126
+
127
+ # Adds order statement to query. Direction is either :desc or :asc.
128
+ # Example:
129
+ #
130
+ # User.find(0..10).ordered_by(:surname, :asc)
131
+ # User.find(20).ordered_by([:surname, :forename], :desc)
132
+ #
133
+ # Aliases are #order_by and #sort_by.
134
+ #
135
+ def ordered_by(attrib, dir=:asc)
136
+ @order_by << [attrib, dir]
137
+ return self
138
+ end
139
+ alias order_by ordered_by
140
+ alias sort_by ordered_by
141
+
142
+ # Offset part of a query.
143
+ #
144
+ # Something.find(10).with(Something.name == 'foo').offset(123).entities
145
+ # Results in
146
+ # SELECT * FROM something WHERE something.name = 'foo' LIMIT 10,123
147
+ #
148
+ def offset(off)
149
+ @offset = off
150
+ return self
151
+ end
152
+
153
+ # Limit part of a query.
154
+ #
155
+ # Something.all_with(Something.name == 'foo').limit(10,123).entities
156
+ # Is same as
157
+ # Something.find(10).with(Something.name == 'foo').offset(123).entities
158
+ # Is same as
159
+ # Something.find(10,123).with(Something.name == 'foo').entities
160
+ #
161
+ # And results in
162
+ # SELECT * FROM something WHERE something.name = 'foo' LIMIT 10,123
163
+ #
164
+ def limit(lim, off=0)
165
+ @limit = lim
166
+ @offset = off
167
+ return self
168
+ end
169
+
170
+ # Handy wrapper for
171
+ # <request>.entities.each { |e| ... }
172
+ def each(&block)
173
+ entities.each &block
174
+ end
175
+
176
+ # Returns Clause instance containing this select statement.
177
+ # This is needed for all Clause methods expecting an inner select.
178
+ # Example:
179
+ #
180
+ # inner = Car.all(Car.car_id).with(Car.seats > 2)
181
+ # Car.all.where(Car.car_id).in(inner) # method 'in' calls inner.to_select
182
+ #
183
+ # Full example:
184
+ #
185
+ # Car.all.where(Car.car_id).in(
186
+ # Manufacturer_Car.values_of(Manufacturer_Car.car_id).limit(10).sort_by(Manufacturer_Car.name)
187
+ # )
188
+ #
189
+ def to_select
190
+ @accessor.select(@what) { |entity|
191
+ entity.where(@condition)
192
+ entity.limit(@limit, @offset) unless @limit.nil?
193
+ @order_by.each { |o|
194
+ entity.order_by(o[0], o[1])
195
+ }
196
+ entity.group_by(@group_by) unless @group_by.nil?
197
+ entity.having(@having) unless @having.nil?
198
+ entity
199
+ }
200
+ end
201
+
202
+ # Sends request defined by previous method calls to self. Examples:
203
+ #
204
+ # Car.find(10).with(Car.name.like '%BMW%').entities # -> Array of 10 instances of model klass 'Car'
205
+ #
206
+ # Before calling #entities, the request isn't sent, but defined only.
207
+ # Therefore you safely can pass a query (Clause) object to other methods,
208
+ # like in this example:
209
+ #
210
+ # def filter_cars(clause)
211
+ # query.with((Car.name != '') & clause).limit(10).entities
212
+ # end
213
+ #
214
+ # filder = Car.find(10).with(Car.name.like '%Audi%').limit(20) # Request is not sent yet
215
+ # filter_cars(filter) # Request will be sent here
216
+ # # -> Car.find(10).with(Car.name.like '%Audi%').with(Car.name != '').limit(10).entities
217
+ # # -> Car.find(10).with((Car.name.like '%Audi%') & (Car.name != '')).limit(10).entities
218
+ #
219
+ # There are other methods not defining but executing a request:
220
+ # #entity, #each, #to_i, #to_s
221
+ #
222
+ def entities
223
+ if @what.nil? then
224
+ result = @accessor.select { |entity|
225
+ entity.where(@condition)
226
+ entity.limit(@limit, @offset) unless @limit.nil?
227
+ @order_by.each { |o|
228
+ entity.order_by(o[0], o[1])
229
+ }
230
+ entity.group_by(@group_by) unless @group_by.nil?
231
+ entity.having(@having) unless @having.nil?
232
+ entity
233
+ }
234
+ else
235
+ result = Array.new
236
+ @accessor.select_values(@what) { |entity|
237
+ entity.where(@condition)
238
+ entity.limit(@limit, @offset) unless @limit.nil?
239
+ @order_by.each { |o|
240
+ entity.order_by(o[0], o[1])
241
+ }
242
+ entity.group_by(@group_by) unless @group_by.nil?
243
+ entity.having(@having) unless @having.nil?
244
+ entity
245
+ }.each { |row|
246
+ if row.kind_of? Hash then
247
+ result << row.values['value']
248
+ elsif row.kind_of? String then
249
+ result << row
250
+ end
251
+ }
252
+ end
253
+
254
+ return result
255
+ end
256
+ alias perform entities
257
+
258
+ end # class
259
+
260
+ class Refined_Delete < Refined_Query
261
+
262
+ def perform
263
+ @accessor.delete { |entity|
264
+ entity.where(@condition)
265
+ entity.limit(@limit, @offset) unless @limit.nil?
266
+ entity.having(@having) unless @having.nil?
267
+ entity
268
+ }
269
+ end
270
+
271
+ end # class
272
+
273
+ class Refined_Update < Refined_Query
274
+
275
+ def initialize(accessor, statements={})
276
+ @update_values = statements[:update_values]
277
+ super(accessor, statements)
278
+ end
279
+
280
+ def perform
281
+ @accessor.update { |entity|
282
+ entity.set(@update_values)
283
+ entity.where(@condition)
284
+ entity
285
+ }
286
+ end
287
+
288
+ end
289
+
290
+
291
+ module Query_Shortcuts
292
+
293
+ # Example:
294
+ #
295
+ # Users.value_of.sum(:user_id)
296
+ #
297
+ def value_of(what=nil)
298
+ Refined_Select.new(self, :condition => true, :what => what)
299
+ end
300
+
301
+ def all(what=nil)
302
+ Refined_Select.new(self, :condition => true, :what => what)
303
+ end
304
+
305
+ # Wrapper for
306
+ #
307
+ # Accessor.all.entities.each { |e| ... }
308
+ #
309
+ def each(&block)
310
+ all.entities.each(&block)
311
+ end
312
+
313
+ # Returns Refined_Select instance with limit set to amount.
314
+ # Example:
315
+ #
316
+ # Car.find(10).with(...) ...
317
+ #
318
+ def find(amount, offset=0)
319
+ if amount == :all then
320
+ all()
321
+ else
322
+ Refined_Select.new(self, :limit => amount, :offset => offset)
323
+ end
324
+ end
325
+
326
+ # Returns Refined_Select instance with WHERE statement set
327
+ # to condition.
328
+ # Same as
329
+ #
330
+ # Accessor.find(:all).with(condition)
331
+ # or
332
+ # Accessor.all.with(condition)
333
+ #
334
+ def all_with(condition)
335
+ Refined_Select.new(self, :condition => condition)
336
+ end
337
+
338
+ # Example:
339
+ #
340
+ # Accessor.set(:attribute => 'value').where(...).perform
341
+ #
342
+ def set(values)
343
+ Refined_Update.new(self, :update_values => values)
344
+ end
345
+
346
+ # Example:
347
+ #
348
+ # Accessor.delete.where(...).perform
349
+ #
350
+ def delete
351
+ Refined_Delete.new(self)
352
+ end
353
+
354
+ # Deletes all entities of model class, i.e. empties its tables (!).
355
+ # Example:
356
+ #
357
+ # Car.delete_all
358
+ #
359
+ def delete_all
360
+ delete { |entity|
361
+ entity.where(true)
362
+ }
363
+ end
364
+
365
+ end # module
366
+
367
+ end # module
@@ -0,0 +1,3 @@
1
+
2
+ Model#touched
3
+ Whether attribute values have changed since loading of instance.
data/result.rb ADDED
@@ -0,0 +1,100 @@
1
+
2
+ require('postgres')
3
+ require('digest/md5')
4
+
5
+ module Lore
6
+
7
+ # :nodoc
8
+ class Result
9
+
10
+ attr_reader :query_hashval
11
+
12
+ def initialize(query, result) # expects PGresult
13
+
14
+ @result = result
15
+ @field_types = nil
16
+ @result_rows = Array.new
17
+ # @query_hashval = Digest::MD5.hexdigest(query)
18
+ @num_fields = @result.num_fields
19
+ @field_counter = 0
20
+
21
+ end # def initialize
22
+
23
+ def get_field_value(row_index, field_name)
24
+
25
+ field_index = @result.fieldnum(field_name)
26
+ return @result.getvalue(row_index, field_index)
27
+
28
+ end # def get_field_value
29
+
30
+ def get_field_names()
31
+ return @result.fields
32
+ end
33
+
34
+ def get_field_types()
35
+
36
+ return @field_types unless @field_types.nil?
37
+
38
+ @field_types = Hash.new
39
+ for field_index in 0...get_field_num()
40
+ @field_types[@result.fields[field_index]] = @result.type(field_index)
41
+ end
42
+
43
+ return @field_types
44
+ end
45
+
46
+ def get_field_num()
47
+ return @result.num_fields
48
+ end
49
+
50
+ def get_tuple_num()
51
+ return @result.num_tuples
52
+ end
53
+
54
+ def get_row(row_num=0)
55
+
56
+ return @result_rows.at(row_num) if @result_rows.at(row_num)
57
+ return if @result.num_tuples == 0
58
+
59
+ # We cannot use a hash here, as there might be
60
+ # duplicate attribute names due to joins:
61
+ #
62
+ # {
63
+ # :fields => [ 'id', 'foo', 'foreign_id, 'id', 'bar' ]
64
+ # :values => [ '1', 'wombat', '5', '5', 'cthulluh' ]
65
+ # }
66
+ row_result = Array.new
67
+
68
+ @field_counter = 0
69
+ for @field_counter in 0...@num_fields do
70
+ row_result << @result.getvalue(row_num, @field_counter)
71
+ end
72
+ @result_rows[row_num] = row_result
73
+ return row_result
74
+
75
+ end
76
+
77
+ def get_rows()
78
+ if !@result_rows.first then
79
+ for tuple_counter in 0...@result.num_tuples do
80
+ get_row(tuple_counter)
81
+ end
82
+ @fieldnames = []
83
+ for @field_counter in 0...@num_fields do
84
+ @fieldnames << @result.fieldname(@field_counter)
85
+ end
86
+ end
87
+
88
+ result = { :values => @result_rows, :fields => @fieldnames }
89
+ return result
90
+ end
91
+
92
+ def fieldname(index)
93
+
94
+ return @result.fieldname(index)
95
+
96
+ end
97
+
98
+ end # class Result
99
+
100
+ end # module Lore