lore 0.4.8 → 0.9.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/Manifest.txt +16 -7
- data/README.rdoc +91 -0
- data/benchmark/benchmark.sql +11 -0
- data/benchmark/results.txt +28 -0
- data/benchmark/select.rb +352 -0
- data/lib/lore.rb +22 -8
- data/lib/lore/adapters/context.rb +64 -0
- data/lib/lore/adapters/postgres-pr.rb +6 -0
- data/lib/lore/adapters/postgres-pr/connection.rb +93 -0
- data/lib/lore/adapters/postgres-pr/result.rb +63 -0
- data/lib/lore/{types.rb → adapters/postgres-pr/types.rb} +36 -0
- data/lib/lore/adapters/postgres.rb +24 -0
- data/lib/lore/adapters/postgres/connection.rb +81 -0
- data/lib/lore/adapters/postgres/result.rb +82 -0
- data/lib/lore/adapters/postgres/types.rb +91 -0
- data/lib/lore/bits.rb +18 -0
- data/lib/lore/cache/abstract_entity_cache.rb +2 -1
- data/lib/lore/cache/cacheable.rb +12 -177
- data/lib/lore/cache/memcache_entity_cache.rb +89 -0
- data/lib/lore/cache/memory_entity_cache.rb +77 -0
- data/lib/lore/cache/mmap_entity_cache.rb +2 -2
- data/lib/lore/cache/mmap_entity_cache_bork.rb +86 -0
- data/lib/lore/clause.rb +107 -35
- data/lib/lore/{exception → exceptions}/ambiguous_attribute.rb +2 -2
- data/lib/lore/{exception → exceptions}/cache_exception.rb +1 -1
- data/lib/lore/exceptions/database_exception.rb +16 -0
- data/lib/lore/{exception/invalid_parameter.rb → exceptions/invalid_field.rb} +7 -4
- data/lib/lore/exceptions/unknown_type.rb +18 -0
- data/lib/lore/exceptions/validation_failure.rb +71 -0
- data/lib/lore/gui/form_generator.rb +109 -60
- data/lib/lore/gui/lore_model_select_field.rb +1 -0
- data/lib/lore/migration.rb +84 -25
- data/lib/lore/model.rb +3 -18
- data/lib/lore/{aspect.rb → model/aspect.rb} +0 -0
- data/lib/lore/model/associations.rb +225 -0
- data/lib/lore/model/attribute_settings.rb +233 -0
- data/lib/lore/model/filters.rb +34 -0
- data/lib/lore/model/mockable.rb +62 -0
- data/lib/lore/{model_factory.rb → model/model_factory.rb} +68 -39
- data/lib/lore/model/model_instance.rb +382 -0
- data/lib/lore/{model_shortcuts.rb → model/model_shortcuts.rb} +7 -0
- data/lib/lore/model/polymorphic.rb +53 -0
- data/lib/lore/model/prepare.rb +97 -0
- data/lib/lore/model/table_accessor.rb +1016 -0
- data/lib/lore/query.rb +71 -0
- data/lib/lore/query_shortcuts.rb +43 -11
- data/lib/lore/strategies/table_delete.rb +115 -0
- data/lib/lore/strategies/table_insert.rb +146 -0
- data/lib/lore/strategies/table_select.rb +299 -0
- data/lib/lore/strategies/table_update.rb +155 -0
- data/lib/lore/validation/parameter_validator.rb +85 -26
- data/lib/lore/validation/type_validator.rb +34 -78
- data/{custom_models.rb → lore-0.9.2.gem} +0 -0
- data/lore.gemspec +26 -17
- data/spec/clause.rb +37 -0
- data/spec/fixtures/blank_models.rb +37 -0
- data/{test/model.rb → spec/fixtures/models.rb} +64 -41
- data/spec/fixtures/polymorphic_models.rb +68 -0
- data/spec/model_associations.rb +86 -0
- data/spec/model_create.rb +47 -0
- data/spec/model_definition.rb +151 -0
- data/spec/model_delete.rb +31 -0
- data/spec/model_inheritance.rb +50 -0
- data/spec/model_polymorphic.rb +85 -0
- data/spec/model_select.rb +101 -0
- data/spec/model_select_eager.rb +42 -0
- data/spec/model_union_select.rb +33 -0
- data/spec/model_update.rb +45 -0
- data/spec/model_validation.rb +20 -0
- data/spec/spec_db.sql +808 -0
- data/spec/spec_env.rb +19 -0
- data/spec/spec_helpers.rb +77 -0
- metadata +93 -82
- data/lib/lore/README.txt +0 -84
- data/lib/lore/behaviours/lockable.rb +0 -55
- data/lib/lore/behaviours/movable.rb +0 -72
- data/lib/lore/behaviours/paginated.rb +0 -31
- data/lib/lore/behaviours/versioned.rb +0 -36
- data/lib/lore/connection.rb +0 -152
- data/lib/lore/exception/invalid_klass_parameters.rb +0 -63
- data/lib/lore/exception/unknown_typecode.rb +0 -19
- data/lib/lore/result.rb +0 -119
- data/lib/lore/symbol.rb +0 -58
- data/lib/lore/table_accessor.rb +0 -1790
- data/lib/lore/table_deleter.rb +0 -116
- data/lib/lore/table_inserter.rb +0 -170
- data/lib/lore/table_instance.rb +0 -389
- data/lib/lore/table_selector.rb +0 -285
- data/lib/lore/table_updater.rb +0 -157
- data/lib/lore/validation.rb +0 -65
- data/lib/lore/validation/message.rb +0 -60
- data/lib/lore/validation/reason.rb +0 -52
- data/lore_test.log +0 -2366
- data/test/README +0 -31
- data/test/custom_models.rb +0 -18
- data/test/env.rb +0 -5
- data/test/prepare.rb +0 -37
- data/test/tc_aspect.rb +0 -58
- data/test/tc_cache.rb +0 -83
- data/test/tc_clause.rb +0 -104
- data/test/tc_deep_inheritance.rb +0 -49
- data/test/tc_factory.rb +0 -57
- data/test/tc_filter.rb +0 -37
- data/test/tc_form.rb +0 -32
- data/test/tc_model.rb +0 -140
- data/test/tc_prepare.rb +0 -44
- data/test/tc_refined_query.rb +0 -88
- data/test/tc_table_accessor.rb +0 -267
- data/test/tc_thread.rb +0 -100
- data/test/test_db.sql +0 -400
- data/test/test_lore.rb +0 -50
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
|
|
2
|
+
module Lore
|
|
3
|
+
|
|
4
|
+
class Filters
|
|
5
|
+
|
|
6
|
+
attr_accessor :input_filters, :output_filters
|
|
7
|
+
|
|
8
|
+
def initialize(accessor)
|
|
9
|
+
@accessor = accessor
|
|
10
|
+
@input_filters = {}
|
|
11
|
+
@output_filters = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def inherit(base_model)
|
|
15
|
+
parent_filters = base_model.__filters__
|
|
16
|
+
@input_filters.update(parent_filters.input_filters.dup)
|
|
17
|
+
@output_filters.update(parent_filters.output_filters.dup)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_input_filter(attr_name, &block)
|
|
21
|
+
# Filters are mapped to attribute names disregarding their
|
|
22
|
+
# original table.
|
|
23
|
+
@input_filters[attr_name.to_sym] = block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_output_filter(attr_name, &block)
|
|
27
|
+
# Filters are mapped to attribute names disregarding their
|
|
28
|
+
# original table.
|
|
29
|
+
@output_filters[attr_name.to_sym] = block
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
|
|
2
|
+
module Lore
|
|
3
|
+
|
|
4
|
+
module Mockable
|
|
5
|
+
|
|
6
|
+
# Create a shallow instance, that is: A mock instance with no reference to
|
|
7
|
+
# DB model. Attribute values passed to Table_Accessor.create_shallow are
|
|
8
|
+
# not processed through hooks, filters, and validation.
|
|
9
|
+
# Values are, however, processed through output filters.
|
|
10
|
+
# Usage and result is
|
|
11
|
+
# the same as for Table_Accessor.create, but it only returns
|
|
12
|
+
# an accessor instance, without storing it in the database.
|
|
13
|
+
# To commit a shallow copy to database (and thus process given attribute
|
|
14
|
+
# values through stages mentioned before), call #commit.
|
|
15
|
+
def create_shallow(attrib_values)
|
|
16
|
+
before_create(attrib_values)
|
|
17
|
+
input_filters = get_input_filters
|
|
18
|
+
attrib_key = ''
|
|
19
|
+
attrib_name = ''
|
|
20
|
+
|
|
21
|
+
attrib_values.each_pair { |attrib_name, attrib_value|
|
|
22
|
+
if attrib_name.instance_of? Symbol then
|
|
23
|
+
attrib_key = attrib_name
|
|
24
|
+
else
|
|
25
|
+
attrib_key = attrib_name.split('.')[-1].intern
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if (input_filters && input_filters[attrib_key]) then
|
|
29
|
+
attrib_values[attrib_name] = input_filters[attrib_key].call(attrib_value)
|
|
30
|
+
end
|
|
31
|
+
}
|
|
32
|
+
after_filters(attrib_values)
|
|
33
|
+
|
|
34
|
+
values = distribute_attrib_values(attrib_values)
|
|
35
|
+
|
|
36
|
+
before_validation(values)
|
|
37
|
+
Lore::Validation::Parameter_Validator.invalid_params(self, values)
|
|
38
|
+
|
|
39
|
+
before_insert(attrib_values)
|
|
40
|
+
values = distribute_attrib_values(attrib_values)
|
|
41
|
+
flat_attribs = []
|
|
42
|
+
get_all_table_names.each { |table|
|
|
43
|
+
get_attributes[table].each { |attrib|
|
|
44
|
+
flat_attribs << (values[table][attrib])
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
instance = self.new(flat_attribs)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Alias module .self methods
|
|
51
|
+
def self.extended(object)
|
|
52
|
+
class << object
|
|
53
|
+
alias_method :create_mock, :create_shallow unless method_defined? :create_mock
|
|
54
|
+
alias_method :create_shallow, :create_mock
|
|
55
|
+
alias_method :mock, :create_shallow unless method_defined? :mock
|
|
56
|
+
alias_method :create_shallow, :mock
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
@@ -19,28 +19,31 @@ module Lore
|
|
|
19
19
|
# builder.set_table_space('diskvol1')
|
|
20
20
|
# builder.build()
|
|
21
21
|
def initialize(model_name, connection=nil)
|
|
22
|
-
name_parts
|
|
23
|
-
@model_name
|
|
24
|
-
@
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@connection
|
|
33
|
-
@
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
@
|
|
37
|
-
@
|
|
38
|
-
@
|
|
39
|
-
@
|
|
40
|
-
@
|
|
41
|
-
@
|
|
42
|
-
@
|
|
43
|
-
@
|
|
22
|
+
name_parts = model_name.to_s.split('::')
|
|
23
|
+
@model_name = name_parts[-1]
|
|
24
|
+
@model_name = @model_name.split('_').map { |p| p.upcase }.join('_')
|
|
25
|
+
@namespaces = name_parts[0..-2]
|
|
26
|
+
|
|
27
|
+
@table_name = @model_name.downcase
|
|
28
|
+
|
|
29
|
+
@output_folder = './'
|
|
30
|
+
@output_file = @table_name + '.rb'
|
|
31
|
+
|
|
32
|
+
@connection = connection
|
|
33
|
+
@connection = Lore::Connection unless @connection
|
|
34
|
+
@table_space = ''
|
|
35
|
+
|
|
36
|
+
@fields = Array.new
|
|
37
|
+
@types = Array.new
|
|
38
|
+
@labels = Array.new
|
|
39
|
+
@aggregates = Array.new
|
|
40
|
+
@attributes = Array.new
|
|
41
|
+
@expects = Array.new
|
|
42
|
+
@constraints = Array.new
|
|
43
|
+
@schema_name = :public
|
|
44
|
+
@primary_keys = Array.new
|
|
45
|
+
@attribute_types = Hash.new
|
|
46
|
+
@attribute_labels = Hash.new
|
|
44
47
|
|
|
45
48
|
@base_model_klass = 'Lore::Model'
|
|
46
49
|
@base_model_klass_file = 'lore/model'
|
|
@@ -69,20 +72,26 @@ module Lore
|
|
|
69
72
|
# :length => 20,
|
|
70
73
|
# :check => '...')
|
|
71
74
|
#
|
|
72
|
-
def add_attribute(attrib_name, attrib_hash)
|
|
75
|
+
def add_attribute(attrib_name, attrib_hash={})
|
|
73
76
|
|
|
74
77
|
@fields << { :name => attrib_name, :type => attrib_hash[:type] }
|
|
75
78
|
|
|
76
79
|
attribute_part = ''
|
|
77
|
-
attribute_part << attrib_name
|
|
80
|
+
attribute_part << "#{attrib_name} \t #{attrib_hash[:type]}"
|
|
78
81
|
if attrib_hash[:length].instance_of? Integer then
|
|
79
82
|
attribute_part << '(' << attrib_hash[:length].to_s + ')'
|
|
80
83
|
end
|
|
84
|
+
if attrib_hash[:mandatory] ||
|
|
85
|
+
attrib_hash[:null].instance_of?(FalseClass) ||
|
|
86
|
+
attrib_hash[:not_null] then
|
|
87
|
+
add_mandatory(attrib_name)
|
|
88
|
+
attribute_part << ' NOT NULL '
|
|
89
|
+
end
|
|
81
90
|
attribute_part << ' UNIQUE ' if attrib_hash[:unique]
|
|
82
|
-
attribute_part << ' NOT NULL ' if attrib_hash[:not_null]
|
|
83
91
|
|
|
84
92
|
@attributes << attribute_part
|
|
85
|
-
@attribute_types[attrib_name]
|
|
93
|
+
@attribute_types[attrib_name] = attrib_hash[:type]
|
|
94
|
+
@attribute_labels[attrib_name] = attrib_hash[:label]
|
|
86
95
|
end
|
|
87
96
|
def set_attributes(attrib_hash)
|
|
88
97
|
attrib_hash.each_pair { |attrib_name, attrib_props|
|
|
@@ -98,6 +107,11 @@ module Lore
|
|
|
98
107
|
@aggregates << [:has_a, model, attribute_name]
|
|
99
108
|
end
|
|
100
109
|
|
|
110
|
+
def add_mandatory(attribute_name)
|
|
111
|
+
@expects << attribute_name.to_s
|
|
112
|
+
@expects.uniq!
|
|
113
|
+
end
|
|
114
|
+
|
|
101
115
|
def add_is_a(model, attribute_name)
|
|
102
116
|
@aggregates << [:is_a, model, attribute]
|
|
103
117
|
end
|
|
@@ -112,12 +126,12 @@ module Lore
|
|
|
112
126
|
|
|
113
127
|
# Usage:
|
|
114
128
|
# add_primary_key(:attribute => 'my_id', :key_name => 'my_model_pkey')
|
|
115
|
-
#
|
|
116
|
-
# add_primary_key(:attributes => ['id_1', 'id_2'], :key_name => 'my_model_pkey')
|
|
129
|
+
#
|
|
117
130
|
def add_primary_key(key_hash)
|
|
118
131
|
key_hash[:attribute] = key_hash[:attribute].to_s
|
|
119
132
|
key_hash[:key_name] = key_hash[:key_name].to_s
|
|
120
|
-
|
|
133
|
+
sequence = key_hash[:sequence]
|
|
134
|
+
@primary_keys << [ key_hash[:attribute], sequence, key_hash[:key_name] ]
|
|
121
135
|
end
|
|
122
136
|
|
|
123
137
|
public
|
|
@@ -128,7 +142,7 @@ module Lore
|
|
|
128
142
|
query = ''
|
|
129
143
|
pkey_constraint = "CONSTRAINT #{@table_name}_pkey PRIMARY KEY (#{@primary_keys.collect { |pk| pk[0] }.join(',')})\n"
|
|
130
144
|
@primary_keys.each { |pk|
|
|
131
|
-
query <<
|
|
145
|
+
query << "CREATE SEQUENCE #{pk[1]}; \n" if pk[1]
|
|
132
146
|
}
|
|
133
147
|
|
|
134
148
|
query << 'CREATE TABLE ' << @table_name + " ( \n"
|
|
@@ -172,22 +186,37 @@ module Lore
|
|
|
172
186
|
model << ', :' << @schema_name.to_s
|
|
173
187
|
model << "\n"
|
|
174
188
|
@primary_keys.each { |key|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
model << ' primary_key :' << key[0]
|
|
190
|
+
model << ', :' << key[1] if key[1]
|
|
191
|
+
model << "\n"
|
|
178
192
|
}
|
|
179
|
-
@attribute_types.each_pair { |
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
@attribute_types.each_pair { |attrib, type|
|
|
194
|
+
model << ' # has_attribute :' << attrib + ', Lore::Type.' << type
|
|
195
|
+
model << "\n"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
model << "\n"
|
|
199
|
+
model << " def self.attribute_labels\n"
|
|
200
|
+
model << " { \n"
|
|
201
|
+
@attribute_labels.each_pair { |attrib, label|
|
|
202
|
+
model << " '#{attrib}' => '#{label}',\n"
|
|
203
|
+
}
|
|
204
|
+
model << " :none => ''\n"
|
|
205
|
+
model << " } \n"
|
|
206
|
+
model << " end\n"
|
|
207
|
+
|
|
208
|
+
@expects.each { |attrib|
|
|
209
|
+
model << ' expects :' << attrib
|
|
210
|
+
model << "\n"
|
|
182
211
|
}
|
|
183
212
|
|
|
184
213
|
model << ' use_label :' << @labels.join(', :') if @labels.first
|
|
185
214
|
model << "\n"
|
|
186
215
|
|
|
187
216
|
@aggregates.each { |type, model_name, attribute_name|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
model << ' has_a ' << model_name + ', :' << attribute_name.downcase if type==:has_a
|
|
218
|
+
model << ' is_a ' << model_name + ', :' << attribute_name.downcase if type==:is_a
|
|
219
|
+
model << "\n"
|
|
191
220
|
}
|
|
192
221
|
model << "\n" << ' end'
|
|
193
222
|
@namespaces.reverse.each { |ns|
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
|
|
2
|
+
require('lore/exceptions/ambiguous_attribute')
|
|
3
|
+
require('lore/model/behaviours/movable')
|
|
4
|
+
require('lore/model/behaviours/versioned')
|
|
5
|
+
require('lore/model/behaviours/lockable')
|
|
6
|
+
require('lore/model/polymorphic')
|
|
7
|
+
|
|
8
|
+
module Lore
|
|
9
|
+
|
|
10
|
+
class Attribute_Hash < Hash # :nodoc:
|
|
11
|
+
|
|
12
|
+
alias random_access_op []
|
|
13
|
+
alias random_access_assign_op []=
|
|
14
|
+
def [](key)
|
|
15
|
+
if !random_access_op(key).nil? then
|
|
16
|
+
return random_access_op(key)
|
|
17
|
+
elsif !random_access_op(key.to_s).nil? then
|
|
18
|
+
return random_access_op(key.to_s)
|
|
19
|
+
else
|
|
20
|
+
return random_access_op(key.to_s[0..24].to_sym)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def []=(key, value)
|
|
25
|
+
begin
|
|
26
|
+
key = key.to_s[0..24].to_sym
|
|
27
|
+
rescue ::Exception => excep
|
|
28
|
+
Lore.logger.debug { 'Error when trying to access attribute ' << key.inspect }
|
|
29
|
+
raise excep
|
|
30
|
+
end
|
|
31
|
+
self.random_access_assign_op(key, value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def method_missing(key)
|
|
35
|
+
self[key]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end # class
|
|
39
|
+
|
|
40
|
+
# Used as mixin for Table_Accessor.
|
|
41
|
+
# This module holds methods provided by Table_Accessor instances.
|
|
42
|
+
module Model_Instance
|
|
43
|
+
include Polymorphic_Instance_Methods
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def table_accessor
|
|
47
|
+
self.class
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update_values
|
|
51
|
+
@update_values
|
|
52
|
+
end
|
|
53
|
+
def update_pkey_values
|
|
54
|
+
@update_pkey_values
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Create a marshalled dump of this model instance.
|
|
58
|
+
# Returns attribute values as The only difference
|
|
59
|
+
# between model instances is their value set.
|
|
60
|
+
def marshal_dump
|
|
61
|
+
{
|
|
62
|
+
:klass => self.class.to_s,
|
|
63
|
+
:values => @attribute_values_flat,
|
|
64
|
+
:joined => @joined_models
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Creates an instance of self from marshalled value set.
|
|
69
|
+
def marshal_load(dump)
|
|
70
|
+
return initialize(dump[:values], dump[:joined], :cached)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Whether this instance has been loaded from
|
|
74
|
+
# cache or live from DB.
|
|
75
|
+
def is_cached_entity?
|
|
76
|
+
# Set in initialize via marshal_load
|
|
77
|
+
@loaded_from_cache
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def get_primary_key_values
|
|
81
|
+
return @primary_key_values if @primary_key_values
|
|
82
|
+
|
|
83
|
+
keys = self.class.get_primary_keys[self.class.table_name]
|
|
84
|
+
@primary_key_values = keys.map { |pkey|
|
|
85
|
+
@attribute_values_flat[pkey]
|
|
86
|
+
}
|
|
87
|
+
@primary_key_values
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns primary key values mapped to table names.
|
|
91
|
+
def get_primary_key_value_map
|
|
92
|
+
# {{{
|
|
93
|
+
return @primary_key_value_map if (@primary_key_value_map && !@touched)
|
|
94
|
+
|
|
95
|
+
accessor = self.class
|
|
96
|
+
base_models = accessor.__associations__.base_klasses()
|
|
97
|
+
table_name = accessor.table_name
|
|
98
|
+
pkey_fields = accessor.get_primary_keys
|
|
99
|
+
|
|
100
|
+
if !pkey_fields[table_name] then
|
|
101
|
+
raise ::Exception.new("Unable to resolve pkey fields for #{self.class.to_s}. Known fields are: #{pkey_fields.inspect}")
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
@primary_key_value_map = { table_name => {} }
|
|
105
|
+
pkey_fields[table_name].each { |own_pkey|
|
|
106
|
+
@primary_key_value_map[table_name][own_pkey] = @attribute_values_flat[own_pkey]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# Map own foreign key values back to foreign primary key
|
|
110
|
+
# values. This is necessary as joined primary key field names are
|
|
111
|
+
# shadowed.
|
|
112
|
+
accessor.__associations__.pkey_value_lookup.each { |mapping|
|
|
113
|
+
foreign_pkeys = {}
|
|
114
|
+
mapping.at(1).each_with_index { |fkey,idx|
|
|
115
|
+
value = @attribute_values_flat[fkey]
|
|
116
|
+
foreign_pkeys[mapping.at(2).at(idx)] = value
|
|
117
|
+
}
|
|
118
|
+
@primary_key_value_map[mapping.at(0)] = foreign_pkeys
|
|
119
|
+
}
|
|
120
|
+
return @primary_key_value_map
|
|
121
|
+
end # }}}
|
|
122
|
+
|
|
123
|
+
# Returns primary key values of own table
|
|
124
|
+
def key
|
|
125
|
+
get_primary_key_value_map[self.class.table_name]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def get_label_string
|
|
129
|
+
if !@label_string || @touched then
|
|
130
|
+
value = ''
|
|
131
|
+
self.class.get_labels.each { |label_attrib|
|
|
132
|
+
label_parts = label_attrib.split('.')
|
|
133
|
+
value << @attribute_values[label_parts[0..1].join('.')][label_parts[2]].to_s + ' '
|
|
134
|
+
}
|
|
135
|
+
value = '[no label given]' if value == ''
|
|
136
|
+
@label_string = value
|
|
137
|
+
end
|
|
138
|
+
return @label_string
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def touched?
|
|
143
|
+
(@touched === true)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def touch(attrib_name=nil)
|
|
147
|
+
@touched = true
|
|
148
|
+
@touched_fields ||= []
|
|
149
|
+
@touched_fields << attrib_name if attrib_name
|
|
150
|
+
@primary_key_value_map = false
|
|
151
|
+
@primary_key_values = false
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def untouch(attrib_name=nil)
|
|
155
|
+
@touched = false
|
|
156
|
+
@touched_fields.delete(attrib_name) if attrib_name
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def method_missing(meth, *args)
|
|
160
|
+
return @attribute_values_flat[meth]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
alias :obj_id :id
|
|
164
|
+
def id
|
|
165
|
+
@attribute_values_flat[:id] || obj_id
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Set value for given attribute, e.g. for later commit.
|
|
169
|
+
# It is recommended to use random access assignment instead:
|
|
170
|
+
#
|
|
171
|
+
# instance.set_attribute_value(:name, 'Wombat')
|
|
172
|
+
# is same as
|
|
173
|
+
# instance[:name] = 'Wombat'
|
|
174
|
+
#
|
|
175
|
+
def set_attribute_value(attrib_name, attrib_value)
|
|
176
|
+
# {{{
|
|
177
|
+
|
|
178
|
+
if @input_filters && (@input_filters.has_key?(attrib_name.intern)) then
|
|
179
|
+
attrib_value = @input_filters[attrib_name.intern].call(attrib_value)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# touch(attrib_name)
|
|
183
|
+
@touched = true
|
|
184
|
+
@touched_fields ||= []
|
|
185
|
+
@touched_fields << attrib_name if attrib_name
|
|
186
|
+
|
|
187
|
+
@attribute_values_flat[attrib_name.to_sym] = attrib_value
|
|
188
|
+
end # def }}}
|
|
189
|
+
|
|
190
|
+
def set_attribute_values(value_hash)
|
|
191
|
+
value_hash.each_pair { |attrib_name,value|
|
|
192
|
+
if @input_filters && @input_filters.has_key?(attrib_name.intern) then
|
|
193
|
+
value_hash[attrib_name] = @input_filters[attrib_name.intern].call(attrib_value)
|
|
194
|
+
end
|
|
195
|
+
@attribute_values_flat[attrib_name.to_sym] = attrib_value
|
|
196
|
+
@touched_fields << attrib_name.intern
|
|
197
|
+
}
|
|
198
|
+
touch
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Sets attribute value. Example:
|
|
202
|
+
# instance[:name] = 'Wombat'
|
|
203
|
+
# instance.commit
|
|
204
|
+
alias :[]= set_attribute_value
|
|
205
|
+
|
|
206
|
+
# Explicit attribute request.
|
|
207
|
+
# Example:
|
|
208
|
+
# Car[Vehicle.name]
|
|
209
|
+
# In case name is attribute field in Car and Vehicle.
|
|
210
|
+
def [](clause)
|
|
211
|
+
abs_attr(clause)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Returns true if instance points to same records as other instance.
|
|
215
|
+
# Only compares primary key values.
|
|
216
|
+
def ==(other)
|
|
217
|
+
return false if self.class.to_s != other.class.to_s
|
|
218
|
+
return pkeys() == other.pkeys()
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Return primary key value. In case primary key is composed, return it as array.
|
|
222
|
+
def pkey
|
|
223
|
+
table = self.class.table_name
|
|
224
|
+
key = get_primary_key_values
|
|
225
|
+
return key.first if key.length < 2
|
|
226
|
+
return key
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def pkeys
|
|
230
|
+
table = self.class.table_name
|
|
231
|
+
return get_primary_key_values
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Returns true if instance points to same records as other instance,
|
|
235
|
+
# also compares non-key attribute values.
|
|
236
|
+
def ===(other)
|
|
237
|
+
return false unless (self == other)
|
|
238
|
+
|
|
239
|
+
end
|
|
240
|
+
# See ==
|
|
241
|
+
def <=>(other)
|
|
242
|
+
return !(self.==(other))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Returns all attribute values as hash.
|
|
246
|
+
def get_attribute_values() # :nodoc:
|
|
247
|
+
@attribute_values_flat
|
|
248
|
+
end # def
|
|
249
|
+
|
|
250
|
+
# Returns attribute values mapped to table names.
|
|
251
|
+
def get_attribute_value_map
|
|
252
|
+
return @attribute_values if @attribute_values
|
|
253
|
+
@attribute_values = self.class.distribute_attrib_values(@attribute_values_flat)
|
|
254
|
+
return @attribute_values
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Returns value hash of instance attributes like:
|
|
258
|
+
#
|
|
259
|
+
# {
|
|
260
|
+
# 'schema.table.id' => 123,
|
|
261
|
+
# 'schema.atable.name' => 'example'
|
|
262
|
+
# }
|
|
263
|
+
#
|
|
264
|
+
# Common usage:
|
|
265
|
+
#
|
|
266
|
+
# table_instance.attr[:id] -> 123
|
|
267
|
+
#
|
|
268
|
+
# But it is recommended to use
|
|
269
|
+
#
|
|
270
|
+
# table_instance.id -> 123
|
|
271
|
+
#
|
|
272
|
+
def attr
|
|
273
|
+
return @attribute_values_flat
|
|
274
|
+
if @flat_attr.nil? then
|
|
275
|
+
@flat_attr = Attribute_Hash.new
|
|
276
|
+
@attribute_values.each_pair { |table, attribs|
|
|
277
|
+
attribs.each_pair { |attrib_name, value|
|
|
278
|
+
@flat_attr[attrib_name] = value unless value.nil?
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
end
|
|
282
|
+
@flat_attr
|
|
283
|
+
end # def
|
|
284
|
+
|
|
285
|
+
# Returns value hash of instance attributes
|
|
286
|
+
# of a given subtype like:
|
|
287
|
+
#
|
|
288
|
+
# {
|
|
289
|
+
# 'id' => 123,
|
|
290
|
+
# 'name' => 'example'
|
|
291
|
+
# }
|
|
292
|
+
#
|
|
293
|
+
# Common usage:
|
|
294
|
+
#
|
|
295
|
+
# self.class.abs_attr(Klass_A)[:id] -> 123
|
|
296
|
+
#
|
|
297
|
+
def abs_attr(klass=nil)
|
|
298
|
+
Lore.logger.warn { 'abs_attr() is deprecated' }
|
|
299
|
+
|
|
300
|
+
klass = klass.to_s if klass.instance_of? Symbol
|
|
301
|
+
return @attribute_values if klass.nil?
|
|
302
|
+
return @attribute_values[klass.table_name] if klass.kind_of? Lore::Table_Accessor
|
|
303
|
+
return @attribute_values[klass] if klass.instance_of? String
|
|
304
|
+
return @attribute_values[klass.to_s.split('.')[0..1].join('.').to_s][klass.to_s.split('.').at(-1)] if klass.instance_of? Lore::Clause
|
|
305
|
+
end # def
|
|
306
|
+
|
|
307
|
+
def attribute_values
|
|
308
|
+
@attribute_values ||= self.class.distribute_attrib_values(@attribute_values_flat)
|
|
309
|
+
@attribute_values
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Commit changes on Table_Accessor instance to DB.
|
|
313
|
+
# Results in one or more SQL update calls.
|
|
314
|
+
#
|
|
315
|
+
# Common usage:
|
|
316
|
+
#
|
|
317
|
+
# unit.name = 'changed'
|
|
318
|
+
# unit.commit()
|
|
319
|
+
#
|
|
320
|
+
def commit
|
|
321
|
+
# {{{
|
|
322
|
+
return unless @touched
|
|
323
|
+
|
|
324
|
+
Lore.logger.debug { "Updating #{self.to_s}. " }
|
|
325
|
+
Lore.logger.debug { "Touched values are: #{@touched_fields.inspect}" }
|
|
326
|
+
|
|
327
|
+
# TODO: Optimize this!
|
|
328
|
+
@attribute_values = self.class.distribute_attrib_values(@attribute_values_flat)
|
|
329
|
+
foreign_pkey_values = false
|
|
330
|
+
@update_values = {}
|
|
331
|
+
@update_pkey_values = {}
|
|
332
|
+
@attribute_values.each_pair { |table,attributes|
|
|
333
|
+
@touched_fields.each { |name|
|
|
334
|
+
value = @attribute_values[table][name]
|
|
335
|
+
filter = self.class.__filters__.input_filters[name]
|
|
336
|
+
value = filter.call(value) if filter
|
|
337
|
+
if attributes[name] then
|
|
338
|
+
update_values[table] ||= {}
|
|
339
|
+
@update_values[table][name] = value
|
|
340
|
+
end
|
|
341
|
+
}
|
|
342
|
+
foreign_pkey_values = get_primary_key_value_map[table]
|
|
343
|
+
|
|
344
|
+
@update_pkey_values[table] = foreign_pkey_values if foreign_pkey_values
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
Validation::Parameter_Validator.validate_update(self.class, update_values)
|
|
348
|
+
|
|
349
|
+
self.class.before_commit(self)
|
|
350
|
+
self.class.__update_strategy__.perform_update(self)
|
|
351
|
+
self.class.after_commit(self)
|
|
352
|
+
|
|
353
|
+
@touched = false
|
|
354
|
+
end # def }}}
|
|
355
|
+
alias save commit
|
|
356
|
+
|
|
357
|
+
# Delete this instance from DB.
|
|
358
|
+
# Common usage:
|
|
359
|
+
#
|
|
360
|
+
# unit = Some_Table_Accessor.select { ... }.first
|
|
361
|
+
# unit.delete
|
|
362
|
+
#
|
|
363
|
+
# Calls hooks Table_Accessor.before_instance_delete(self)
|
|
364
|
+
# and Table_Accessor.after_instance_delete(self).
|
|
365
|
+
#
|
|
366
|
+
def delete
|
|
367
|
+
# Called before entity_instance.delete
|
|
368
|
+
self.class.before_instance_delete(self)
|
|
369
|
+
|
|
370
|
+
self.class.__delete_strategy__.perform_delete(@attribute_values_flat)
|
|
371
|
+
# Called after entity_instance.delete
|
|
372
|
+
self.class.after_instance_delete(self)
|
|
373
|
+
end # def
|
|
374
|
+
|
|
375
|
+
def inspect
|
|
376
|
+
# {{{
|
|
377
|
+
'Lore::Table_Accessor entity: ' << @attribute_values_flat.inspect
|
|
378
|
+
end # }}}
|
|
379
|
+
|
|
380
|
+
end # module
|
|
381
|
+
|
|
382
|
+
end # module
|