sequel 3.4.0 → 3.5.0

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