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.
- data/LICENSE +19 -0
- data/README +74 -0
- data/aspect.rb +80 -0
- data/behaviours/lockable.rb +41 -0
- data/behaviours/movable.rb +54 -0
- data/behaviours/versioned.rb +24 -0
- data/benchmark.rb +193 -0
- data/bits.rb +52 -0
- data/cache/abstract_entity_cache.rb +82 -0
- data/cache/bits.rb +22 -0
- data/cache/cacheable.rb +202 -0
- data/cache/cached_entities.rb +116 -0
- data/cache/file_index.rb +35 -0
- data/cache/mmap_entity_cache.rb +67 -0
- data/clause.rb +528 -0
- data/connection.rb +155 -0
- data/custom_functions.sql +14 -0
- data/exception/ambiguous_attribute.rb +14 -0
- data/exception/cache_exception.rb +30 -0
- data/exception/invalid_klass_parameters.rb +63 -0
- data/exception/invalid_parameter.rb +42 -0
- data/exception/unknown_typecode.rb +19 -0
- data/file_index.sql +56 -0
- data/gui/erb_template.rb +79 -0
- data/gui/erb_template_helpers.rhtml +19 -0
- data/gui/form.rb +314 -0
- data/gui/form_element.rb +676 -0
- data/gui/form_generator.rb +151 -0
- data/gui/templates/button.rhtml +2 -0
- data/gui/templates/checkbox.rhtml +3 -0
- data/gui/templates/checkbox_row.rhtml +1 -0
- data/gui/templates/file.rhtml +2 -0
- data/gui/templates/file_readonly.rhtml +3 -0
- data/gui/templates/form_element.rhtml +5 -0
- data/gui/templates/form_element_horizontal.rhtml +3 -0
- data/gui/templates/form_element_listed.rhtml +8 -0
- data/gui/templates/form_table.rhtml +3 -0
- data/gui/templates/form_table_blank.rhtml +3 -0
- data/gui/templates/form_table_horizontal.rhtml +8 -0
- data/gui/templates/password.rhtml +2 -0
- data/gui/templates/password_readonly.rhtml +3 -0
- data/gui/templates/radio.rhtml +1 -0
- data/gui/templates/radio_row.rhtml +1 -0
- data/gui/templates/select.rhtml +23 -0
- data/gui/templates/text.rhtml +2 -0
- data/gui/templates/text_readonly.rhtml +3 -0
- data/gui/templates/textarea.rhtml +3 -0
- data/gui/templates/textarea_readonly.rhtml +4 -0
- data/lore.gemspec +40 -0
- data/lore.rb +94 -0
- data/migration.rb +48 -0
- data/model.rb +139 -0
- data/model_factory.rb +202 -0
- data/model_shortcuts.rb +16 -0
- data/query_shortcuts.rb +367 -0
- data/reserved_methods.txt +3 -0
- data/result.rb +100 -0
- data/symbol.rb +58 -0
- data/table_accessor.rb +1926 -0
- data/table_deleter.rb +115 -0
- data/table_inserter.rb +168 -0
- data/table_instance.rb +384 -0
- data/table_selector.rb +314 -0
- data/table_updater.rb +155 -0
- data/test/README +31 -0
- data/test/env.rb +5 -0
- data/test/lore_test.log +8218 -0
- data/test/model.rb +142 -0
- data/test/prepare.rb +37 -0
- data/test/tc_aspect.rb +58 -0
- data/test/tc_cache.rb +80 -0
- data/test/tc_clause.rb +104 -0
- data/test/tc_deep_inheritance.rb +49 -0
- data/test/tc_factory.rb +57 -0
- data/test/tc_filter.rb +37 -0
- data/test/tc_form.rb +32 -0
- data/test/tc_model.rb +86 -0
- data/test/tc_prepare.rb +45 -0
- data/test/tc_refined_query.rb +88 -0
- data/test/tc_table_accessor.rb +265 -0
- data/test/test.log +181 -0
- data/test/test_db.sql +400 -0
- data/test/ts_lore.rb +49 -0
- data/types.rb +55 -0
- data/validation/message.rb +60 -0
- data/validation/parameter_validator.rb +104 -0
- data/validation/reason.rb +54 -0
- data/validation/type_validator.rb +91 -0
- data/validation.rb +65 -0
- 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
|
data/model_shortcuts.rb
ADDED
data/query_shortcuts.rb
ADDED
@@ -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
|
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
|