lore 0.4.8 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. data/Manifest.txt +16 -7
  2. data/README.rdoc +91 -0
  3. data/benchmark/benchmark.sql +11 -0
  4. data/benchmark/results.txt +28 -0
  5. data/benchmark/select.rb +352 -0
  6. data/lib/lore.rb +22 -8
  7. data/lib/lore/adapters/context.rb +64 -0
  8. data/lib/lore/adapters/postgres-pr.rb +6 -0
  9. data/lib/lore/adapters/postgres-pr/connection.rb +93 -0
  10. data/lib/lore/adapters/postgres-pr/result.rb +63 -0
  11. data/lib/lore/{types.rb → adapters/postgres-pr/types.rb} +36 -0
  12. data/lib/lore/adapters/postgres.rb +24 -0
  13. data/lib/lore/adapters/postgres/connection.rb +81 -0
  14. data/lib/lore/adapters/postgres/result.rb +82 -0
  15. data/lib/lore/adapters/postgres/types.rb +91 -0
  16. data/lib/lore/bits.rb +18 -0
  17. data/lib/lore/cache/abstract_entity_cache.rb +2 -1
  18. data/lib/lore/cache/cacheable.rb +12 -177
  19. data/lib/lore/cache/memcache_entity_cache.rb +89 -0
  20. data/lib/lore/cache/memory_entity_cache.rb +77 -0
  21. data/lib/lore/cache/mmap_entity_cache.rb +2 -2
  22. data/lib/lore/cache/mmap_entity_cache_bork.rb +86 -0
  23. data/lib/lore/clause.rb +107 -35
  24. data/lib/lore/{exception → exceptions}/ambiguous_attribute.rb +2 -2
  25. data/lib/lore/{exception → exceptions}/cache_exception.rb +1 -1
  26. data/lib/lore/exceptions/database_exception.rb +16 -0
  27. data/lib/lore/{exception/invalid_parameter.rb → exceptions/invalid_field.rb} +7 -4
  28. data/lib/lore/exceptions/unknown_type.rb +18 -0
  29. data/lib/lore/exceptions/validation_failure.rb +71 -0
  30. data/lib/lore/gui/form_generator.rb +109 -60
  31. data/lib/lore/gui/lore_model_select_field.rb +1 -0
  32. data/lib/lore/migration.rb +84 -25
  33. data/lib/lore/model.rb +3 -18
  34. data/lib/lore/{aspect.rb → model/aspect.rb} +0 -0
  35. data/lib/lore/model/associations.rb +225 -0
  36. data/lib/lore/model/attribute_settings.rb +233 -0
  37. data/lib/lore/model/filters.rb +34 -0
  38. data/lib/lore/model/mockable.rb +62 -0
  39. data/lib/lore/{model_factory.rb → model/model_factory.rb} +68 -39
  40. data/lib/lore/model/model_instance.rb +382 -0
  41. data/lib/lore/{model_shortcuts.rb → model/model_shortcuts.rb} +7 -0
  42. data/lib/lore/model/polymorphic.rb +53 -0
  43. data/lib/lore/model/prepare.rb +97 -0
  44. data/lib/lore/model/table_accessor.rb +1016 -0
  45. data/lib/lore/query.rb +71 -0
  46. data/lib/lore/query_shortcuts.rb +43 -11
  47. data/lib/lore/strategies/table_delete.rb +115 -0
  48. data/lib/lore/strategies/table_insert.rb +146 -0
  49. data/lib/lore/strategies/table_select.rb +299 -0
  50. data/lib/lore/strategies/table_update.rb +155 -0
  51. data/lib/lore/validation/parameter_validator.rb +85 -26
  52. data/lib/lore/validation/type_validator.rb +34 -78
  53. data/{custom_models.rb → lore-0.9.2.gem} +0 -0
  54. data/lore.gemspec +26 -17
  55. data/spec/clause.rb +37 -0
  56. data/spec/fixtures/blank_models.rb +37 -0
  57. data/{test/model.rb → spec/fixtures/models.rb} +64 -41
  58. data/spec/fixtures/polymorphic_models.rb +68 -0
  59. data/spec/model_associations.rb +86 -0
  60. data/spec/model_create.rb +47 -0
  61. data/spec/model_definition.rb +151 -0
  62. data/spec/model_delete.rb +31 -0
  63. data/spec/model_inheritance.rb +50 -0
  64. data/spec/model_polymorphic.rb +85 -0
  65. data/spec/model_select.rb +101 -0
  66. data/spec/model_select_eager.rb +42 -0
  67. data/spec/model_union_select.rb +33 -0
  68. data/spec/model_update.rb +45 -0
  69. data/spec/model_validation.rb +20 -0
  70. data/spec/spec_db.sql +808 -0
  71. data/spec/spec_env.rb +19 -0
  72. data/spec/spec_helpers.rb +77 -0
  73. metadata +93 -82
  74. data/lib/lore/README.txt +0 -84
  75. data/lib/lore/behaviours/lockable.rb +0 -55
  76. data/lib/lore/behaviours/movable.rb +0 -72
  77. data/lib/lore/behaviours/paginated.rb +0 -31
  78. data/lib/lore/behaviours/versioned.rb +0 -36
  79. data/lib/lore/connection.rb +0 -152
  80. data/lib/lore/exception/invalid_klass_parameters.rb +0 -63
  81. data/lib/lore/exception/unknown_typecode.rb +0 -19
  82. data/lib/lore/result.rb +0 -119
  83. data/lib/lore/symbol.rb +0 -58
  84. data/lib/lore/table_accessor.rb +0 -1790
  85. data/lib/lore/table_deleter.rb +0 -116
  86. data/lib/lore/table_inserter.rb +0 -170
  87. data/lib/lore/table_instance.rb +0 -389
  88. data/lib/lore/table_selector.rb +0 -285
  89. data/lib/lore/table_updater.rb +0 -157
  90. data/lib/lore/validation.rb +0 -65
  91. data/lib/lore/validation/message.rb +0 -60
  92. data/lib/lore/validation/reason.rb +0 -52
  93. data/lore_test.log +0 -2366
  94. data/test/README +0 -31
  95. data/test/custom_models.rb +0 -18
  96. data/test/env.rb +0 -5
  97. data/test/prepare.rb +0 -37
  98. data/test/tc_aspect.rb +0 -58
  99. data/test/tc_cache.rb +0 -83
  100. data/test/tc_clause.rb +0 -104
  101. data/test/tc_deep_inheritance.rb +0 -49
  102. data/test/tc_factory.rb +0 -57
  103. data/test/tc_filter.rb +0 -37
  104. data/test/tc_form.rb +0 -32
  105. data/test/tc_model.rb +0 -140
  106. data/test/tc_prepare.rb +0 -44
  107. data/test/tc_refined_query.rb +0 -88
  108. data/test/tc_table_accessor.rb +0 -267
  109. data/test/tc_thread.rb +0 -100
  110. data/test/test_db.sql +0 -400
  111. 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 = 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
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 + "\t " << attrib_hash[:type].to_s
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] = attrib_hash[:type]
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
- # or:
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
- @primary_keys << [ key_hash[:attribute], key_hash[:attribute]+'_seq', key_hash[:key_name] ]
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 << 'CREATE SEQUENCE ' << pk[1] + ';' << "\n"
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
- model << ' primary_key :' << key[0]
176
- model << ', :' << key[1] if key[1]
177
- model << "\n"
189
+ model << ' primary_key :' << key[0]
190
+ model << ', :' << key[1] if key[1]
191
+ model << "\n"
178
192
  }
179
- @attribute_types.each_pair { |attr, type|
180
- model << ' has_attribute :' << attr + ', Lore::Type.' << type
181
- model << "\n"
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
- 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"
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