lore 0.4.8 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|