sequel 3.4.0 → 3.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +1 -1
  3. data/doc/cheat_sheet.rdoc +5 -2
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.5.0.txt +510 -0
  6. data/lib/sequel/adapters/ado.rb +3 -1
  7. data/lib/sequel/adapters/ado/mssql.rb +2 -2
  8. data/lib/sequel/adapters/do.rb +2 -11
  9. data/lib/sequel/adapters/do/mysql.rb +7 -0
  10. data/lib/sequel/adapters/do/postgres.rb +2 -2
  11. data/lib/sequel/adapters/firebird.rb +3 -3
  12. data/lib/sequel/adapters/informix.rb +3 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +3 -3
  14. data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
  15. data/lib/sequel/adapters/mysql.rb +60 -21
  16. data/lib/sequel/adapters/odbc.rb +1 -1
  17. data/lib/sequel/adapters/openbase.rb +3 -3
  18. data/lib/sequel/adapters/oracle.rb +1 -5
  19. data/lib/sequel/adapters/postgres.rb +3 -3
  20. data/lib/sequel/adapters/shared/mssql.rb +142 -33
  21. data/lib/sequel/adapters/shared/mysql.rb +54 -31
  22. data/lib/sequel/adapters/shared/oracle.rb +17 -6
  23. data/lib/sequel/adapters/shared/postgres.rb +7 -7
  24. data/lib/sequel/adapters/shared/progress.rb +3 -3
  25. data/lib/sequel/adapters/shared/sqlite.rb +3 -17
  26. data/lib/sequel/connection_pool.rb +4 -6
  27. data/lib/sequel/core.rb +29 -113
  28. data/lib/sequel/database.rb +14 -12
  29. data/lib/sequel/dataset.rb +8 -21
  30. data/lib/sequel/dataset/convenience.rb +1 -1
  31. data/lib/sequel/dataset/graph.rb +9 -2
  32. data/lib/sequel/dataset/sql.rb +170 -104
  33. data/lib/sequel/exceptions.rb +3 -0
  34. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  35. data/lib/sequel/extensions/named_timezones.rb +61 -0
  36. data/lib/sequel/extensions/schema_dumper.rb +7 -1
  37. data/lib/sequel/extensions/sql_expr.rb +122 -0
  38. data/lib/sequel/extensions/string_date_time.rb +4 -4
  39. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  40. data/lib/sequel/model/associations.rb +105 -45
  41. data/lib/sequel/model/base.rb +37 -28
  42. data/lib/sequel/plugins/active_model.rb +35 -0
  43. data/lib/sequel/plugins/association_dependencies.rb +96 -0
  44. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  45. data/lib/sequel/plugins/force_encoding.rb +61 -0
  46. data/lib/sequel/plugins/many_through_many.rb +32 -11
  47. data/lib/sequel/plugins/nested_attributes.rb +7 -2
  48. data/lib/sequel/plugins/subclasses.rb +45 -0
  49. data/lib/sequel/plugins/touch.rb +118 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +61 -0
  51. data/lib/sequel/sql.rb +31 -30
  52. data/lib/sequel/timezones.rb +161 -0
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/mssql_spec.rb +262 -0
  55. data/spec/adapters/mysql_spec.rb +46 -8
  56. data/spec/adapters/postgres_spec.rb +6 -3
  57. data/spec/adapters/spec_helper.rb +21 -0
  58. data/spec/adapters/sqlite_spec.rb +1 -1
  59. data/spec/core/connection_pool_spec.rb +1 -1
  60. data/spec/core/database_spec.rb +27 -1
  61. data/spec/core/dataset_spec.rb +63 -1
  62. data/spec/core/object_graph_spec.rb +1 -1
  63. data/spec/core/schema_spec.rb +1 -0
  64. data/spec/extensions/active_model_spec.rb +47 -0
  65. data/spec/extensions/association_dependencies_spec.rb +108 -0
  66. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  67. data/spec/extensions/force_encoding_spec.rb +75 -0
  68. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  69. data/spec/extensions/many_through_many_spec.rb +60 -2
  70. data/spec/extensions/named_timezones_spec.rb +72 -0
  71. data/spec/extensions/nested_attributes_spec.rb +29 -1
  72. data/spec/extensions/schema_dumper_spec.rb +10 -0
  73. data/spec/extensions/spec_helper.rb +1 -1
  74. data/spec/extensions/sql_expr_spec.rb +89 -0
  75. data/spec/extensions/subclasses_spec.rb +52 -0
  76. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  77. data/spec/extensions/touch_spec.rb +155 -0
  78. data/spec/extensions/typecast_on_load_spec.rb +60 -0
  79. data/spec/integration/database_test.rb +8 -0
  80. data/spec/integration/dataset_test.rb +9 -9
  81. data/spec/integration/plugin_test.rb +139 -0
  82. data/spec/integration/schema_test.rb +7 -7
  83. data/spec/integration/spec_helper.rb +32 -1
  84. data/spec/integration/timezone_test.rb +3 -3
  85. data/spec/integration/transaction_test.rb +1 -1
  86. data/spec/integration/type_test.rb +6 -6
  87. data/spec/model/association_reflection_spec.rb +18 -0
  88. data/spec/model/associations_spec.rb +169 -9
  89. data/spec/model/base_spec.rb +2 -0
  90. data/spec/model/eager_loading_spec.rb +82 -2
  91. data/spec/model/model_spec.rb +8 -1
  92. data/spec/model/record_spec.rb +52 -9
  93. metadata +33 -23
@@ -317,8 +317,9 @@ module Sequel
317
317
  # You can set it to nil to not have a primary key, but that
318
318
  # cause certain things not to work, see no_primary_key.
319
319
  def set_primary_key(*key)
320
+ key = key.flatten
320
321
  @simple_pk = key.length == 1 ? db.literal(key.first) : nil
321
- @primary_key = (key.length == 1) ? key[0] : key.flatten
322
+ @primary_key = (key.length == 1) ? key[0] : key
322
323
  end
323
324
 
324
325
  # Set the columns to restrict in new/set/update. Using this means that
@@ -489,7 +490,7 @@ module Sequel
489
490
  # same name, caching the result in an instance variable. Define
490
491
  # standard attr_writer method for modifying that instance variable
491
492
  def self.class_attr_overridable(*meths) # :nodoc:
492
- meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (@#{meth} = self.class.#{meth}) : @#{meth} end")}
493
+ meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (@#{meth} = self.class.#{meth}) : @#{meth} end", __FILE__, __LINE__)}
493
494
  attr_writer(*meths)
494
495
  end
495
496
 
@@ -498,7 +499,7 @@ module Sequel
498
499
  #
499
500
  # define_method(meth){self.class.send(meth)}
500
501
  def self.class_attr_reader(*meths) # :nodoc:
501
- meths.each{|meth| class_eval("def #{meth}; model.#{meth} end")}
502
+ meths.each{|meth| class_eval("def #{meth}; model.#{meth} end", __FILE__, __LINE__)}
502
503
  end
503
504
 
504
505
  private_class_method :class_attr_overridable, :class_attr_reader
@@ -539,21 +540,19 @@ module Sequel
539
540
  @values[column]
540
541
  end
541
542
 
542
- # Sets value of the column's attribute and marks the column as changed.
543
- # If the column already has the same value, this is a no-op. Note that
544
- # changing a columns value and then changing it back will cause the
545
- # column to appear in changed_columns. Similarly, providing a
546
- # value that is different from the column's current value but is the
547
- # same after typecasting will also cause changed_columns to include the
548
- # column.
543
+ # Sets the value for the given column. If typecasting is enabled for
544
+ # this object, typecast the value based on the column's type.
545
+ # If this a a new record or the typecasted value isn't the same
546
+ # as the current value for the column, mark the column as changed.
549
547
  def []=(column, value)
550
548
  # If it is new, it doesn't have a value yet, so we should
551
549
  # definitely set the new value.
552
550
  # If the column isn't in @values, we can't assume it is
553
551
  # NULL in the database, so assume it has changed.
554
- if new? || !@values.include?(column) || value != @values[column]
552
+ v = typecast_value(column, value)
553
+ if new? || !@values.include?(column) || v != @values[column]
555
554
  changed_columns << column unless changed_columns.include?(column)
556
- @values[column] = typecast_value(column, value)
555
+ @values[column] = v
557
556
  end
558
557
  end
559
558
 
@@ -655,9 +654,10 @@ module Sequel
655
654
  end
656
655
 
657
656
  # Whether this object has been modified since last saved, used by
658
- # save_changes to determine whether changes should be saved
657
+ # save_changes to determine whether changes should be saved. New
658
+ # values are always considered modified.
659
659
  def modified?
660
- !changed_columns.empty?
660
+ new? || !changed_columns.empty?
661
661
  end
662
662
 
663
663
  # Returns true if the current instance represents a new record.
@@ -802,6 +802,22 @@ module Sequel
802
802
  self
803
803
  end
804
804
 
805
+ def _insert
806
+ ds = model.dataset
807
+ if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
808
+ @values = h
809
+ nil
810
+ else
811
+ iid = ds.insert(@values)
812
+ # if we have a regular primary key and it's not set in @values,
813
+ # we assume it's the last inserted id
814
+ if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
815
+ @values[pk] = iid
816
+ end
817
+ pk
818
+ end
819
+ end
820
+
805
821
  # Refresh using a particular dataset, used inside save to make sure the same server
806
822
  # is used for reading newly inserted values from the database
807
823
  def _refresh(dataset)
@@ -817,19 +833,8 @@ module Sequel
817
833
  return save_failure(:save) if before_save == false
818
834
  if new?
819
835
  return save_failure(:create) if before_create == false
820
- ds = model.dataset
821
- if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
822
- @values = h
823
- @this = nil
824
- else
825
- iid = ds.insert(@values)
826
- # if we have a regular primary key and it's not set in @values,
827
- # we assume it's the last inserted id
828
- if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
829
- @values[pk] = iid
830
- end
831
- @this = nil if pk
832
- end
836
+ pk = _insert
837
+ @this = nil if pk
833
838
  @new = false
834
839
  @was_new = true
835
840
  after_create
@@ -850,7 +855,7 @@ module Sequel
850
855
  changed_columns.reject!{|c| columns.include?(c)}
851
856
  end
852
857
  Array(primary_key).each{|x| @columns_updated.delete(x)}
853
- this.update(@columns_updated) unless @columns_updated.empty?
858
+ _update(@columns_updated) unless @columns_updated.empty?
854
859
  after_update
855
860
  after_save
856
861
  @columns_updated = nil
@@ -858,6 +863,10 @@ module Sequel
858
863
  self
859
864
  end
860
865
 
866
+ def _update(columns)
867
+ this.update(columns)
868
+ end
869
+
861
870
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
862
871
  def inspect_values
863
872
  @values.inspect
@@ -0,0 +1,35 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The ActiveModel plugin makes Sequel::Model objects the
4
+ # pass the ActiveModel::Lint tests, which should
5
+ # hopefully mean full ActiveModel compliance. This should
6
+ # allow the full support of Sequel::Model objects in Rails 3.
7
+ module ActiveModel
8
+ module InstanceMethods
9
+ # Record that an object was destroyed, for later use by
10
+ # destroyed?
11
+ def after_destroy
12
+ super
13
+ @destroyed = true
14
+ end
15
+
16
+ # Whether the object was destroyed by destroy. Not true
17
+ # for objects that were deleted.
18
+ def destroyed?
19
+ @destroyed == true
20
+ end
21
+
22
+ # An alias for new?
23
+ def new_record?
24
+ new?
25
+ end
26
+
27
+ # With the ActiveModel plugin, Sequel model objects are already
28
+ # compliant, so this returns self.
29
+ def to_model
30
+ self
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,96 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The AssociationDependencies plugin allows you do easily set up before and/or after destroy hooks
4
+ # for destroying, deleting, or nullifying associated model objects. The following
5
+ # association types support the following dependency actions:
6
+ #
7
+ # * :many_to_many - :nullify (removes all related entries in join table)
8
+ # * :many_to_one - :delete, :destroy
9
+ # * :one_to_many - :delete, :destroy, :nullify (sets foreign key to NULL for all associated objects)
10
+ #
11
+ # This plugin works directly with the association datasets and does not use any cached association values.
12
+ # The :delete action will delete all associated objects from the database in a single SQL call.
13
+ # The :destroy action will load each associated object from the database and call the destroy method on it.
14
+ #
15
+ # To set up an association dependency, you must provide a hash with association name symbols
16
+ # and dependency action values. You can provide the hash to the plugin call itself or
17
+ # to the add_association_dependencies method:
18
+ #
19
+ # Business.plugin :association_dependencies, :address=>delete
20
+ # # or:
21
+ # Artist.plugin :association_dependencies
22
+ # Artist.add_association_dependencies :albums=>:destroy, :reviews=>:delete, :tags=>:nullify
23
+ module AssociationDependencies
24
+ # Mapping of association types to when the dependency calls should be made (either
25
+ # :before for in before_destroy or :after for in after_destroy)
26
+ ASSOCIATION_MAPPING = {:one_to_many=>:before, :many_to_one=>:after, :many_to_many=>:before}
27
+
28
+ # The valid dependence actions
29
+ DEPENDENCE_ACTIONS = [:delete, :destroy, :nullify]
30
+
31
+ # Initialize the association_dependencies hash for this model.
32
+ def self.apply(model, hash={})
33
+ model.instance_eval{@association_dependencies = {:before_delete=>[], :before_destroy=>[], :before_nullify=>[], :after_delete=>[], :after_destroy=>[]}}
34
+ end
35
+
36
+ # Call add_association_dependencies with any dependencies given in the plugin call.
37
+ def self.configure(model, hash={})
38
+ model.add_association_dependencies(hash) unless hash.empty?
39
+ end
40
+
41
+ module ClassMethods
42
+ # A hash specifying the association dependencies for each model. The keys
43
+ # are symbols indicating the type of action and when it should be executed
44
+ # (e.g. :before_delete). Values are an array of method symbols.
45
+ # For before_nullify, the symbols are remove_all_association methods. For other
46
+ # types, the symbols are association_dataset methods, on which delete or
47
+ # destroy is called.
48
+ attr_reader :association_dependencies
49
+
50
+ # Add association dependencies to this model. The hash should have association name
51
+ # symbol keys and dependency action symbol values (e.g. :albums=>:destroy).
52
+ def add_association_dependencies(hash)
53
+ hash.each do |association, action|
54
+ raise(Error, "Nonexistent association: #{association}") unless r = association_reflection(association)
55
+ raise(Error, "Invalid dependence action type: association: #{association}, dependence action: #{action}") unless DEPENDENCE_ACTIONS.include?(action)
56
+ raise(Error, "Invalid association type: association: #{association}, type: #{r[:type]}") unless time = ASSOCIATION_MAPPING[r[:type]]
57
+ association_dependencies[:"#{time}_#{action}"] << if action == :nullify
58
+ raise(Error, "Can't nullify many_to_one associated objects: association: #{association}") if r[:type] == :many_to_one
59
+ r.remove_all_method
60
+ else
61
+ raise(Error, "Can only nullify many_to_many associations: association: #{association}") if r[:type] == :many_to_many
62
+ r.dataset_method
63
+ end
64
+ end
65
+ end
66
+
67
+ # Copy the current model object's association_dependencies into the subclass.
68
+ def inherited(subclass)
69
+ super
70
+ ad = association_dependencies.dup
71
+ ad.keys.each{|k| ad[k] = ad[k].dup}
72
+ subclass.instance_variable_set(:@association_dependencies, ad)
73
+ end
74
+ end
75
+
76
+ module InstanceMethods
77
+ # Run the delete and destroy association dependency actions for
78
+ # many_to_one associations.
79
+ def after_destroy
80
+ super
81
+ model.association_dependencies[:after_delete].each{|m| send(m).delete}
82
+ model.association_dependencies[:after_destroy].each{|m| send(m).destroy}
83
+ end
84
+
85
+ # Run the delete, destroy, and nullify association dependency actions for
86
+ # *_to_many associations.
87
+ def before_destroy
88
+ model.association_dependencies[:before_delete].each{|m| send(m).delete}
89
+ model.association_dependencies[:before_destroy].each{|m| send(m).destroy}
90
+ model.association_dependencies[:before_nullify].each{|m| send(m)}
91
+ super
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,214 @@
1
+ module Sequel
2
+ module Plugins
3
+ # The class_table_inheritance plugin allows you to model inheritance in the
4
+ # database using a table per model class in the hierarchy, with only columns
5
+ # unique to that model class (or subclass hierarchy) being stored in the related
6
+ # table. For example, with this hierarchy:
7
+ #
8
+ # Employee
9
+ # / \
10
+ # Staff Manager
11
+ # |
12
+ # Executive
13
+ #
14
+ # the following database schema may be used (table - columns):
15
+ #
16
+ # * employees - id, name, kind
17
+ # * staff - id, manager_id
18
+ # * managers - id, num_staff
19
+ # * executives - id, num_managers
20
+ #
21
+ # The class_table_inheritance plugin assumes that the main table
22
+ # (e.g. employees) has a primary key field (usually autoincrementing),
23
+ # and all other tables have a foreign key of the same name that points
24
+ # to the same key in their superclass's table. For example:
25
+ #
26
+ # * employees.id - primary key, autoincrementing
27
+ # * staff.id - foreign key referencing employees(id)
28
+ # * managers.id - foreign key referencing employees(id)
29
+ # * executives.id - foreign key referencing managers(id)
30
+ #
31
+ # When using the class_table_inheritance plugin, subclasses use joined
32
+ # datasets:
33
+ #
34
+ # Employee.dataset.sql # SELECT * FROM employees
35
+ # Manager.dataset.sql # SELECT * FROM employees
36
+ # # INNER JOIN managers USING (id)
37
+ # Executive.dataset.sql # SELECT * FROM employees
38
+ # # INNER JOIN managers USING (id)
39
+ # # INNER JOIN executives USING (id)
40
+ #
41
+ # This allows Executive.all to return instances with all attributes
42
+ # loaded. The plugin overrides the deleting, inserting, and updating
43
+ # in the model to work with multiple tables, by handling each table
44
+ # individually.
45
+ #
46
+ # This plugin allows the use of a :key option when loading to mark
47
+ # a column holding a class name. This allows methods on the
48
+ # superclass to return instances of specific subclasses.
49
+ # This plugin also requires the lazy_attributes plugin and uses it to
50
+ # return subclass specific attributes that would not be loaded
51
+ # when calling superclass methods (since those wouldn't join
52
+ # to the subclass tables). For example:
53
+ #
54
+ # a = Employee.all # [<#Staff>, <#Manager>, <#Executive>]
55
+ # a.first.values # {:id=>1, name=>'S', :kind=>'Staff'}
56
+ # a.first.manager_id # Loads the manager_id attribute from the database
57
+ module ClassTableInheritance
58
+ # The class_table_inheritance plugin requires the lazy_attributes plugin
59
+ # to handle lazily-loaded attributes for subclass instances returned
60
+ # by superclass methods.
61
+ def self.apply(model, opts={}, &block)
62
+ model.plugin :lazy_attributes
63
+ end
64
+
65
+ # Initialize the per-model data structures and set the dataset's row_proc
66
+ # to check for the :key option column for the type of class when loading objects.
67
+ # Options:
68
+ # * :key - The column symbol holding the name of the model class this
69
+ # is an instance of. Necessary if you want to call model methods
70
+ # using the superclass, but have them return subclass instances.
71
+ # * :table_map - Hash with class name symbol keys and table name symbol
72
+ # values. Necessary if the implicit table name for the model class
73
+ # does not match the database table name
74
+ # Example:
75
+ # class Employee < Sequel::Model
76
+ # plugin :class_table_inheritance, :key=>:kind, :table_map=>{:Staff=>:staff}
77
+ # end
78
+ def self.configure(model, opts={}, &block)
79
+ model.instance_eval do
80
+ m = method(:constantize)
81
+ @cti_base_model = self
82
+ @cti_key = key = opts[:key]
83
+ @cti_tables = [table_name]
84
+ @cti_columns = {table_name=>columns}
85
+ @cti_table_map = opts[:table_map] || {}
86
+ dataset.row_proc = lambda{|r| (m.call(r[key]) rescue model).load(r)}
87
+ end
88
+ end
89
+
90
+ module ClassMethods
91
+ # The parent/root/base model for this class table inheritance hierarchy.
92
+ # This is the only model in the hierarchy that load the
93
+ # class_table_inheritance plugin.
94
+ attr_reader :cti_base_model
95
+
96
+ # Hash with table name symbol keys and arrays of column symbol values,
97
+ # giving the columns to update in each backing database table.
98
+ attr_reader :cti_columns
99
+
100
+ # The column containing the class name as a string. Used to
101
+ # return instances of subclasses when calling the superclass's
102
+ # load method.
103
+ attr_reader :cti_key
104
+
105
+ # An array of table symbols that back this model. The first is
106
+ # cti_base_model table symbol, and the last is the current model
107
+ # table symbol.
108
+ attr_reader :cti_tables
109
+
110
+ # A hash with class name symbol keys and table name symbol values.
111
+ # Specified with the :table_map option to the plugin, and used if
112
+ # the implicit naming is incorrect.
113
+ attr_reader :cti_table_map
114
+
115
+ # Add the appropriate data structures to the subclass. Does not
116
+ # allow anonymous subclasses to be created, since they would not
117
+ # be mappable to a table.
118
+ def inherited(subclass)
119
+ cc = cti_columns
120
+ ck = cti_key
121
+ ct = cti_tables.dup
122
+ ctm = cti_table_map.dup
123
+ cbm = cti_base_model
124
+ pk = primary_key
125
+ ds = dataset
126
+ subclass.instance_eval do
127
+ raise(Error, "cannot create anonymous subclass for model class using class_table_inheritance") if !(n = name) || n.empty?
128
+ table = ctm[n.to_sym] || implicit_table_name
129
+ columns = db.from(table).columns
130
+ @cti_key = ck
131
+ @cti_tables = ct + [table]
132
+ @cti_columns = cc.merge(table=>columns)
133
+ @cti_table_map = ctm
134
+ @cti_base_model = cbm
135
+ # Need to set dataset and columns before calling super so that
136
+ # the main column accessor module is included in the class before any
137
+ # plugin accessor modules (such as the lazy attributes accessor module).
138
+ set_dataset(ds.join(table, [pk]))
139
+ set_columns(self.columns)
140
+ end
141
+ super
142
+ subclass.instance_eval do
143
+ m = method(:constantize)
144
+ dataset.row_proc = lambda{|r| (m.call(r[ck]) rescue subclass).load(r)}
145
+ (columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
146
+ cti_tables.reverse.each do |table|
147
+ db.schema(table).each{|k,v| db_schema[k] = v}
148
+ end
149
+ end
150
+ end
151
+
152
+ # The primary key in the parent/base/root model, which should have a
153
+ # foreign key with the same name referencing it in each model subclass.
154
+ def primary_key
155
+ return super if self == cti_base_model
156
+ cti_base_model.primary_key
157
+ end
158
+
159
+ # The table name for the current model class's main table (not used
160
+ # by any superclasses).
161
+ def table_name
162
+ self == cti_base_model ? super : cti_tables.last
163
+ end
164
+ end
165
+
166
+ module InstanceMethods
167
+ # Set the cti_key column to the name of the model.
168
+ def before_create
169
+ return false if super == false
170
+ send("#{model.cti_key}=", model.name.to_s) if model.cti_key
171
+ end
172
+
173
+ # Delete the row from all backing tables, starting from the
174
+ # most recent table and going through all superclasses.
175
+ def delete
176
+ m = model
177
+ m.cti_tables.reverse.each do |table|
178
+ m.db.from(table).filter(m.primary_key=>pk).delete
179
+ end
180
+ self
181
+ end
182
+
183
+ private
184
+
185
+ # Insert rows into all backing tables, using the columns
186
+ # in each table.
187
+ def _insert
188
+ return super if model == model.cti_base_model
189
+ iid = nil
190
+ m = model
191
+ m.cti_tables.each do |table|
192
+ h = {}
193
+ h[m.primary_key] = iid if iid
194
+ m.cti_columns[table].each{|c| h[c] = @values[c] if @values.include?(c)}
195
+ nid = m.db.from(table).insert(h)
196
+ iid ||= nid
197
+ end
198
+ @values[primary_key] = iid
199
+ end
200
+
201
+ # Update rows in all backing tables, using the columns in each table.
202
+ def _update(columns)
203
+ pkh = pk_hash
204
+ m = model
205
+ m.cti_tables.each do |table|
206
+ h = {}
207
+ m.cti_columns[table].each{|c| h[c] = columns[c] if columns.include?(c)}
208
+ m.db.from(table).filter(pkh).update(h) unless h.empty?
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end