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.
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