sequel 2.11.0 → 2.12.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 (162) hide show
  1. data/CHANGELOG +168 -0
  2. data/README.rdoc +77 -95
  3. data/Rakefile +100 -80
  4. data/bin/sequel +2 -1
  5. data/doc/advanced_associations.rdoc +23 -32
  6. data/doc/cheat_sheet.rdoc +23 -40
  7. data/doc/dataset_filtering.rdoc +6 -6
  8. data/doc/prepared_statements.rdoc +22 -22
  9. data/doc/release_notes/2.12.0.txt +534 -0
  10. data/doc/schema.rdoc +3 -1
  11. data/doc/sharding.rdoc +8 -8
  12. data/doc/virtual_rows.rdoc +65 -0
  13. data/lib/sequel.rb +1 -1
  14. data/lib/{sequel_core → sequel}/adapters/ado.rb +3 -3
  15. data/lib/{sequel_core → sequel}/adapters/db2.rb +0 -0
  16. data/lib/{sequel_core → sequel}/adapters/dbi.rb +1 -1
  17. data/lib/{sequel_core → sequel}/adapters/do.rb +9 -5
  18. data/lib/{sequel_core → sequel}/adapters/do/mysql.rb +1 -1
  19. data/lib/{sequel_core → sequel}/adapters/do/postgres.rb +1 -1
  20. data/lib/{sequel_core → sequel}/adapters/do/sqlite.rb +1 -1
  21. data/lib/{sequel_core → sequel}/adapters/firebird.rb +84 -80
  22. data/lib/{sequel_core → sequel}/adapters/informix.rb +1 -1
  23. data/lib/{sequel_core → sequel}/adapters/jdbc.rb +21 -14
  24. data/lib/{sequel_core → sequel}/adapters/jdbc/h2.rb +14 -13
  25. data/lib/{sequel_core → sequel}/adapters/jdbc/mysql.rb +1 -1
  26. data/lib/{sequel_core → sequel}/adapters/jdbc/oracle.rb +1 -1
  27. data/lib/{sequel_core → sequel}/adapters/jdbc/postgresql.rb +1 -1
  28. data/lib/{sequel_core → sequel}/adapters/jdbc/sqlite.rb +1 -1
  29. data/lib/{sequel_core → sequel}/adapters/mysql.rb +60 -39
  30. data/lib/{sequel_core → sequel}/adapters/odbc.rb +8 -4
  31. data/lib/{sequel_core → sequel}/adapters/openbase.rb +0 -0
  32. data/lib/{sequel_core → sequel}/adapters/oracle.rb +38 -7
  33. data/lib/{sequel_core → sequel}/adapters/postgres.rb +24 -24
  34. data/lib/{sequel_core → sequel}/adapters/shared/mssql.rb +5 -5
  35. data/lib/{sequel_core → sequel}/adapters/shared/mysql.rb +126 -71
  36. data/lib/{sequel_core → sequel}/adapters/shared/oracle.rb +7 -10
  37. data/lib/{sequel_core → sequel}/adapters/shared/postgres.rb +159 -125
  38. data/lib/{sequel_core → sequel}/adapters/shared/progress.rb +1 -2
  39. data/lib/{sequel_core → sequel}/adapters/shared/sqlite.rb +72 -67
  40. data/lib/{sequel_core → sequel}/adapters/sqlite.rb +11 -7
  41. data/lib/{sequel_core → sequel}/adapters/utils/date_format.rb +0 -0
  42. data/lib/{sequel_core → sequel}/adapters/utils/stored_procedures.rb +0 -0
  43. data/lib/{sequel_core → sequel}/adapters/utils/unsupported.rb +19 -0
  44. data/lib/{sequel_core → sequel}/connection_pool.rb +7 -5
  45. data/lib/sequel/core.rb +221 -0
  46. data/lib/{sequel_core → sequel}/core_sql.rb +91 -49
  47. data/lib/{sequel_core → sequel}/database.rb +264 -149
  48. data/lib/{sequel_core/schema/generator.rb → sequel/database/schema_generator.rb} +6 -2
  49. data/lib/{sequel_core/database/schema.rb → sequel/database/schema_methods.rb} +12 -12
  50. data/lib/sequel/database/schema_sql.rb +224 -0
  51. data/lib/{sequel_core → sequel}/dataset.rb +78 -236
  52. data/lib/{sequel_core → sequel}/dataset/convenience.rb +99 -61
  53. data/lib/{sequel_core/object_graph.rb → sequel/dataset/graph.rb} +16 -14
  54. data/lib/{sequel_core → sequel}/dataset/prepared_statements.rb +1 -1
  55. data/lib/{sequel_core → sequel}/dataset/sql.rb +150 -99
  56. data/lib/sequel/deprecated.rb +593 -0
  57. data/lib/sequel/deprecated_migration.rb +91 -0
  58. data/lib/sequel/exceptions.rb +48 -0
  59. data/lib/sequel/extensions/blank.rb +42 -0
  60. data/lib/{sequel_model → sequel/extensions}/inflector.rb +8 -1
  61. data/lib/{sequel_core → sequel/extensions}/migration.rb +1 -1
  62. data/lib/{sequel_core/dataset → sequel/extensions}/pagination.rb +0 -0
  63. data/lib/{sequel_core → sequel/extensions}/pretty_table.rb +7 -0
  64. data/lib/{sequel_core/dataset → sequel/extensions}/query.rb +7 -0
  65. data/lib/sequel/extensions/string_date_time.rb +47 -0
  66. data/lib/sequel/metaprogramming.rb +43 -0
  67. data/lib/sequel/model.rb +110 -0
  68. data/lib/sequel/model/associations.rb +1300 -0
  69. data/lib/sequel/model/base.rb +937 -0
  70. data/lib/sequel/model/deprecated.rb +204 -0
  71. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  72. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  73. data/lib/sequel/model/deprecated_validations.rb +388 -0
  74. data/lib/sequel/model/errors.rb +39 -0
  75. data/lib/{sequel_model → sequel/model}/exceptions.rb +4 -4
  76. data/lib/sequel/model/inflections.rb +208 -0
  77. data/lib/sequel/model/plugins.rb +76 -0
  78. data/lib/sequel/plugins/caching.rb +122 -0
  79. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  80. data/lib/sequel/plugins/schema.rb +53 -0
  81. data/lib/sequel/plugins/serialization.rb +117 -0
  82. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  83. data/lib/sequel/plugins/validation_class_methods.rb +384 -0
  84. data/lib/sequel/plugins/validation_helpers.rb +150 -0
  85. data/lib/{sequel_core → sequel}/sql.rb +125 -190
  86. data/lib/{sequel_core → sequel}/version.rb +2 -1
  87. data/lib/sequel_core.rb +1 -172
  88. data/lib/sequel_model.rb +1 -91
  89. data/spec/adapters/firebird_spec.rb +5 -5
  90. data/spec/adapters/informix_spec.rb +1 -1
  91. data/spec/adapters/mysql_spec.rb +128 -42
  92. data/spec/adapters/oracle_spec.rb +47 -19
  93. data/spec/adapters/postgres_spec.rb +64 -52
  94. data/spec/adapters/spec_helper.rb +1 -1
  95. data/spec/adapters/sqlite_spec.rb +12 -17
  96. data/spec/{sequel_core → core}/connection_pool_spec.rb +10 -10
  97. data/spec/{sequel_core → core}/core_ext_spec.rb +19 -19
  98. data/spec/{sequel_core → core}/core_sql_spec.rb +68 -71
  99. data/spec/{sequel_core → core}/database_spec.rb +135 -99
  100. data/spec/{sequel_core → core}/dataset_spec.rb +398 -242
  101. data/spec/{sequel_core → core}/expression_filters_spec.rb +13 -13
  102. data/spec/core/migration_spec.rb +263 -0
  103. data/spec/{sequel_core → core}/object_graph_spec.rb +10 -10
  104. data/spec/{sequel_core → core}/pretty_table_spec.rb +2 -2
  105. data/spec/{sequel_core → core}/schema_generator_spec.rb +0 -0
  106. data/spec/{sequel_core → core}/schema_spec.rb +8 -10
  107. data/spec/{sequel_core → core}/spec_helper.rb +29 -2
  108. data/spec/{sequel_core → core}/version_spec.rb +0 -0
  109. data/spec/extensions/blank_spec.rb +67 -0
  110. data/spec/extensions/caching_spec.rb +201 -0
  111. data/spec/{sequel_model/hooks_spec.rb → extensions/hook_class_methods_spec.rb} +8 -23
  112. data/spec/{sequel_model → extensions}/inflector_spec.rb +3 -0
  113. data/spec/{sequel_core → extensions}/migration_spec.rb +4 -4
  114. data/spec/extensions/pagination_spec.rb +99 -0
  115. data/spec/extensions/pretty_table_spec.rb +91 -0
  116. data/spec/extensions/query_spec.rb +85 -0
  117. data/spec/{sequel_model → extensions}/schema_spec.rb +22 -1
  118. data/spec/extensions/serialization_spec.rb +109 -0
  119. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  120. data/spec/{sequel_model → extensions}/spec_helper.rb +13 -4
  121. data/spec/extensions/string_date_time_spec.rb +93 -0
  122. data/spec/{sequel_model/validations_spec.rb → extensions/validation_class_methods_spec.rb} +15 -103
  123. data/spec/extensions/validation_helpers_spec.rb +291 -0
  124. data/spec/integration/dataset_test.rb +31 -0
  125. data/spec/integration/eager_loader_test.rb +17 -30
  126. data/spec/integration/schema_test.rb +8 -5
  127. data/spec/integration/spec_helper.rb +17 -0
  128. data/spec/integration/transaction_test.rb +68 -0
  129. data/spec/{sequel_model → model}/association_reflection_spec.rb +0 -0
  130. data/spec/{sequel_model → model}/associations_spec.rb +23 -10
  131. data/spec/{sequel_model → model}/base_spec.rb +29 -20
  132. data/spec/{sequel_model → model}/caching_spec.rb +16 -14
  133. data/spec/{sequel_model → model}/dataset_methods_spec.rb +0 -0
  134. data/spec/{sequel_model → model}/eager_loading_spec.rb +8 -8
  135. data/spec/model/hooks_spec.rb +472 -0
  136. data/spec/model/inflector_spec.rb +126 -0
  137. data/spec/{sequel_model → model}/model_spec.rb +25 -20
  138. data/spec/model/plugins_spec.rb +142 -0
  139. data/spec/{sequel_model → model}/record_spec.rb +121 -62
  140. data/spec/model/schema_spec.rb +92 -0
  141. data/spec/model/spec_helper.rb +124 -0
  142. data/spec/model/validations_spec.rb +1080 -0
  143. metadata +136 -107
  144. data/lib/sequel_core/core_ext.rb +0 -217
  145. data/lib/sequel_core/dataset/callback.rb +0 -13
  146. data/lib/sequel_core/dataset/schema.rb +0 -15
  147. data/lib/sequel_core/deprecated.rb +0 -26
  148. data/lib/sequel_core/exceptions.rb +0 -44
  149. data/lib/sequel_core/schema.rb +0 -2
  150. data/lib/sequel_core/schema/sql.rb +0 -325
  151. data/lib/sequel_model/association_reflection.rb +0 -267
  152. data/lib/sequel_model/associations.rb +0 -499
  153. data/lib/sequel_model/base.rb +0 -539
  154. data/lib/sequel_model/caching.rb +0 -82
  155. data/lib/sequel_model/dataset_methods.rb +0 -26
  156. data/lib/sequel_model/eager_loading.rb +0 -370
  157. data/lib/sequel_model/hooks.rb +0 -101
  158. data/lib/sequel_model/plugins.rb +0 -62
  159. data/lib/sequel_model/record.rb +0 -568
  160. data/lib/sequel_model/schema.rb +0 -49
  161. data/lib/sequel_model/validations.rb +0 -429
  162. data/spec/sequel_model/plugins_spec.rb +0 -80
@@ -1,101 +0,0 @@
1
- module Sequel
2
- class Model
3
- # Hooks that are safe for public use
4
- HOOKS = [:after_initialize, :before_create, :after_create, :before_update,
5
- :after_update, :before_save, :after_save, :before_destroy, :after_destroy,
6
- :before_validation, :after_validation]
7
-
8
- # Hooks that are only for internal use
9
- PRIVATE_HOOKS = [:before_update_values, :before_delete]
10
-
11
- # This adds a new hook type. It will define both a class
12
- # method that you can use to add hooks, as well as an instance method
13
- # that you can use to call all hooks of that type. The class method
14
- # can be called with a symbol or a block or both. If a block is given and
15
- # and symbol is not, it adds the hook block to the hook type. If a block
16
- # and symbol are both given, it replaces the hook block associated with
17
- # that symbol for a given hook type, or adds it if there is no hook block
18
- # with that symbol for that hook type. If no block is given, it assumes
19
- # the symbol specifies an instance method to call and adds it to the hook
20
- # type.
21
- #
22
- # If any hook block returns false, the instance method will return false
23
- # immediately without running the rest of the hooks of that type.
24
- #
25
- # It is recommended that you always provide a symbol to this method,
26
- # for descriptive purposes. It's only necessary to do so when you
27
- # are using a system that reloads code.
28
- #
29
- # All of Sequel's standard hook types are also implemented using this
30
- # method.
31
- #
32
- # Example of usage:
33
- #
34
- # class MyModel
35
- # define_hook :before_move_to
36
- # before_move_to(:check_move_allowed){|o| o.allow_move?}
37
- # def move_to(there)
38
- # return if before_move_to == false
39
- # # move MyModel object to there
40
- # end
41
- # end
42
- def self.add_hook_type(*hooks)
43
- hooks.each do |hook|
44
- @hooks[hook] = []
45
- instance_eval("def #{hook}(method = nil, &block); define_hook_instance_method(:#{hook}); add_hook(:#{hook}, method, &block) end")
46
- class_eval("def #{hook}; end")
47
- end
48
- end
49
-
50
- # Returns true if there are any hook blocks for the given hook.
51
- def self.has_hooks?(hook)
52
- !@hooks[hook].empty?
53
- end
54
-
55
- # Yield every block related to the given hook.
56
- def self.hook_blocks(hook)
57
- @hooks[hook].each{|k,v| yield v}
58
- end
59
-
60
- ### Private Class Methods ###
61
-
62
- # Add a hook block to the list of hook methods.
63
- # If a non-nil tag is given and it already is in the list of hooks,
64
- # replace it with the new block.
65
- def self.add_hook(hook, tag, &block) #:nodoc:
66
- unless block
67
- (raise Error, 'No hook method specified') unless tag
68
- block = proc {send tag}
69
- end
70
- h = @hooks[hook]
71
- if tag && (old = h.find{|x| x[0] == tag})
72
- old[1] = block
73
- else
74
- if hook.to_s =~ /^before/
75
- h.unshift([tag,block])
76
- else
77
- h << [tag, block]
78
- end
79
- end
80
- end
81
-
82
- # Define a hook instance method that calls the run_hooks instance method.
83
- def self.define_hook_instance_method(hook) #:nodoc:
84
- class_eval("def #{hook}; run_hooks(:#{hook}); end")
85
- end
86
-
87
- private_class_method :add_hook, :define_hook_instance_method
88
-
89
- private
90
-
91
- # Runs all hook blocks of given hook type on this object.
92
- # Stops running hook blocks and returns false if any hook block returns false.
93
- def run_hooks(hook)
94
- model.hook_blocks(hook){|block| return false if instance_eval(&block) == false}
95
- end
96
-
97
- # For performance reasons, we define empty hook instance methods, which are
98
- # overwritten with real hook instance methods whenever the hook class method is called.
99
- add_hook_type(*(HOOKS + PRIVATE_HOOKS))
100
- end
101
- end
@@ -1,62 +0,0 @@
1
- module Sequel
2
- # Empty namespace that plugins should use to store themselves,
3
- # so they can be loaded via Model.is.
4
- #
5
- # Plugins should be modules with one of the following conditions:
6
- # * A singleton method named apply, which takes a model and
7
- # additional arguments.
8
- # * A module inside the plugin module named InstanceMethods,
9
- # which will be included in the model class.
10
- # * A module inside the plugin module named ClassMethods,
11
- # which will extend the model class.
12
- # * A module inside the plugin module named DatasetMethods,
13
- # which will extend the model's dataset.
14
- module Plugins
15
- end
16
-
17
- class Model
18
- # Loads a plugin for use with the model class, passing optional arguments
19
- # to the plugin. If the plugin has a DatasetMethods module and the model
20
- # doesn't have a dataset, raise an Error.
21
- def self.is(plugin, *args)
22
- m = plugin_module(plugin)
23
- raise(Error, "Plugin cannot be applied because the model class has no dataset") if m.const_defined?("DatasetMethods") && !@dataset
24
- if m.respond_to?(:apply)
25
- m.apply(self, *args)
26
- end
27
- if m.const_defined?("InstanceMethods")
28
- class_def(:"#{plugin}_opts") {args.first}
29
- include(m::InstanceMethods)
30
- end
31
- if m.const_defined?("ClassMethods")
32
- meta_def(:"#{plugin}_opts") {args.first}
33
- extend(m::ClassMethods)
34
- end
35
- if m.const_defined?("DatasetMethods")
36
- dataset.meta_def(:"#{plugin}_opts") {args.first}
37
- dataset.extend(m::DatasetMethods)
38
- def_dataset_method(*m::DatasetMethods.public_instance_methods)
39
- end
40
- end
41
- metaalias :is_a, :is
42
-
43
- ### Private Class Methods ###
44
-
45
- # Returns the gem name for the given plugin.
46
- def self.plugin_gem(plugin) # :nodoc:
47
- "sequel_#{plugin}"
48
- end
49
-
50
- # Returns the module for the specified plugin. If the module is not
51
- # defined, the corresponding plugin gem is automatically loaded.
52
- def self.plugin_module(plugin) # :nodoc:
53
- module_name = plugin.to_s.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
54
- if not Sequel::Plugins.const_defined?(module_name)
55
- require plugin_gem(plugin)
56
- end
57
- Sequel::Plugins.const_get(module_name)
58
- end
59
-
60
- private_class_method :plugin_gem, :plugin_module
61
- end
62
- end
@@ -1,568 +0,0 @@
1
- module Sequel
2
- class Model
3
- # The setter methods (methods ending with =) that are never allowed
4
- # to be called automatically via set.
5
- RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
6
-
7
- # The hash of attribute values. Keys are symbols with the names of the
8
- # underlying database columns.
9
- attr_reader :values
10
-
11
- class_attr_reader :columns, :dataset, :db, :primary_key, :str_columns
12
- class_attr_overridable :db_schema, :raise_on_save_failure, :raise_on_typecast_failure, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment
13
- remove_method :db_schema=
14
-
15
- # Creates new instance with values set to passed-in Hash.
16
- # If a block is given, yield the instance to the block unless
17
- # from_db is true.
18
- # This method runs the after_initialize hook after
19
- # it has optionally yielded itself to the block.
20
- #
21
- # Arguments:
22
- # * values - should be a hash with symbol keys, though
23
- # string keys will work if from_db is false.
24
- # * from_db - should only be set by Model.load, forget it
25
- # exists.
26
- def initialize(values = {}, from_db = false)
27
- if from_db
28
- @new = false
29
- @values = values
30
- else
31
- @values = {}
32
- @new = true
33
- set(values)
34
- changed_columns.clear
35
- yield self if block_given?
36
- end
37
- after_initialize
38
- end
39
-
40
- # Returns value of the column's attribute.
41
- def [](column)
42
- @values[column]
43
- end
44
-
45
- # Sets value of the column's attribute and marks the column as changed.
46
- # If the column already has the same value, this is a no-op.
47
- def []=(column, value)
48
- # If it is new, it doesn't have a value yet, so we should
49
- # definitely set the new value.
50
- # If the column isn't in @values, we can't assume it is
51
- # NULL in the database, so assume it has changed.
52
- if new? || !@values.include?(column) || value != @values[column]
53
- changed_columns << column unless changed_columns.include?(column)
54
- @values[column] = typecast_value(column, value)
55
- end
56
- end
57
-
58
- # Compares model instances by values.
59
- def ==(obj)
60
- (obj.class == model) && (obj.values == @values)
61
- end
62
- alias_method :eql?, :"=="
63
-
64
- # If pk is not nil, true only if the objects have the same class and pk.
65
- # If pk is nil, false.
66
- def ===(obj)
67
- pk.nil? ? false : (obj.class == model) && (obj.pk == pk)
68
- end
69
-
70
- # class is defined in Object, but it is also a keyword,
71
- # and since a lot of instance methods call class methods,
72
- # the model makes it so you can use model instead of
73
- # self.class.
74
- alias_method :model, :class
75
-
76
- # The current cached associations. A hash with the keys being the
77
- # association name symbols and the values being the associated object
78
- # or nil (many_to_one), or the array of associated objects (*_to_many).
79
- def associations
80
- @associations ||= {}
81
- end
82
-
83
- # The columns that have been updated. This isn't completely accurate,
84
- # see Model#[]=.
85
- def changed_columns
86
- @changed_columns ||= []
87
- end
88
-
89
- # Deletes and returns self. Does not run destroy hooks.
90
- # Look into using destroy instead.
91
- def delete
92
- before_delete
93
- this.delete
94
- self
95
- end
96
-
97
- # Like delete but runs hooks before and after delete.
98
- # If before_destroy returns false, returns false without
99
- # deleting the object the the database. Otherwise, deletes
100
- # the item from the database and returns self.
101
- def destroy
102
- db.transaction do
103
- return save_failure(:destroy) if before_destroy == false
104
- delete
105
- after_destroy
106
- end
107
- self
108
- end
109
-
110
- # Enumerates through all attributes.
111
- #
112
- # Example:
113
- # Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }
114
- def each(&block)
115
- @values.each(&block)
116
- end
117
-
118
- # Returns true when current instance exists, false otherwise.
119
- def exists?
120
- this.count > 0
121
- end
122
-
123
- # Unique for objects with the same class and pk (if pk is not nil), or
124
- # the same class and values (if pk is nil).
125
- def hash
126
- [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
127
- end
128
-
129
- # Returns value for the :id attribute, even if the primary key is
130
- # not id. To get the primary key value, use #pk.
131
- def id
132
- @values[:id]
133
- end
134
-
135
- # Returns a string representation of the model instance including
136
- # the class name and values.
137
- def inspect
138
- "#<#{model.name} @values=#{inspect_values}>"
139
- end
140
-
141
- # Returns attribute names as an array of symbols.
142
- def keys
143
- @values.keys
144
- end
145
-
146
- # Returns true if the current instance represents a new record.
147
- def new?
148
- @new
149
- end
150
-
151
- # Returns the primary key value identifying the model instance.
152
- # Raises an error if this model does not have a primary key.
153
- # If the model has a composite primary key, returns an array of values.
154
- def pk
155
- raise(Error, "No primary key is associated with this model") unless key = primary_key
156
- case key
157
- when Array
158
- key.collect{|k| @values[k]}
159
- else
160
- @values[key]
161
- end
162
- end
163
-
164
- # Returns a hash identifying the model instance. It should be true that:
165
- #
166
- # Model[model_instance.pk_hash] === model_instance
167
- def pk_hash
168
- model.primary_key_hash(pk)
169
- end
170
-
171
- # Reloads attributes from database and returns self. Also clears all
172
- # cached association information. Raises an Error if the record no longer
173
- # exists in the database.
174
- def refresh
175
- @values = this.first || raise(Error, "Record not found")
176
- changed_columns.clear
177
- associations.clear
178
- self
179
- end
180
- alias_method :reload, :refresh
181
-
182
- # Creates or updates the record, after making sure the record
183
- # is valid. If the record is not valid, or before_save,
184
- # before_create (if new?), or before_update (if !new?) return
185
- # false, returns nil unless raise_on_save_failure is true (if it
186
- # is true, it raises an error).
187
- # Otherwise, returns self. You can provide an optional list of
188
- # columns to update, in which case it only updates those columns.
189
- def save(*columns)
190
- valid? ? save!(*columns) : save_failure(:invalid)
191
- end
192
-
193
- # Creates or updates the record, without attempting to validate
194
- # it first. You can provide an optional list of columns to update,
195
- # in which case it only updates those columns.
196
- # If before_save, before_create (if new?), or before_update
197
- # (if !new?) return false, returns nil unless raise_on_save_failure
198
- # is true (if it is true, it raises an error). Otherwise, returns self.
199
- def save!(*columns)
200
- opts = columns.extract_options!
201
- return save_failure(:save) if before_save == false
202
- if new?
203
- return save_failure(:create) if before_create == false
204
- ds = model.dataset
205
- if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
206
- @values = h
207
- @this = nil
208
- else
209
- iid = ds.insert(@values)
210
- # if we have a regular primary key and it's not set in @values,
211
- # we assume it's the last inserted id
212
- if (pk = primary_key) && !(Array === pk) && !@values[pk]
213
- @values[pk] = iid
214
- end
215
- @this = nil if pk
216
- end
217
- after_create
218
- after_save
219
- @new = false
220
- refresh if pk
221
- else
222
- return save_failure(:update) if before_update == false
223
- if columns.empty?
224
- vals = opts[:changed] ? @values.reject{|k,v| !changed_columns.include?(k)} : @values
225
- this.update(vals)
226
- else # update only the specified columns
227
- this.update(@values.reject{|k, v| !columns.include?(k)})
228
- end
229
- after_update
230
- after_save
231
- if columns.empty?
232
- changed_columns.clear
233
- else
234
- changed_columns.reject!{|c| columns.include?(c)}
235
- end
236
- end
237
- self
238
- end
239
-
240
- # Saves only changed columns or does nothing if no columns are marked as
241
- # chanaged. If no columns have been changed, returns nil. If unable to
242
- # save, returns false unless raise_on_save_failure is true.
243
- def save_changes
244
- save(:changed=>true) || false unless changed_columns.empty?
245
- end
246
-
247
- # Updates the instance with the supplied values with support for virtual
248
- # attributes, raising an exception if a value is used that doesn't have
249
- # a setter method (or ignoring it if strict_param_setting = false).
250
- # Does not save the record.
251
- #
252
- # If no columns have been set for this model (very unlikely), assume symbol
253
- # keys are valid column names, and assign the column value based on that.
254
- def set(hash)
255
- set_restricted(hash, nil, nil)
256
- end
257
- alias_method :set_with_params, :set
258
-
259
- # Set all values using the entries in the hash, ignoring any setting of
260
- # allowed_columns or restricted columns in the model.
261
- def set_all(hash)
262
- set_restricted(hash, false, false)
263
- end
264
-
265
- # Set all values using the entries in the hash, except for the keys
266
- # given in except.
267
- def set_except(hash, *except)
268
- set_restricted(hash, false, except.flatten)
269
- end
270
-
271
- # Set the values using the entries in the hash, only if the key
272
- # is included in only.
273
- def set_only(hash, *only)
274
- set_restricted(hash, only.flatten, false)
275
- end
276
-
277
- # Sets the value attributes without saving the record. Returns
278
- # the values changed. Raises an error if the keys are not symbols
279
- # or strings or a string key was passed that was not a valid column.
280
- # This is a low level method that does not respect virtual attributes. It
281
- # should probably be avoided. Look into using set instead.
282
- def set_values(values)
283
- s = str_columns
284
- vals = values.inject({}) do |m, kv|
285
- k, v = kv
286
- k = case k
287
- when Symbol
288
- k
289
- when String
290
- # Prevent denial of service via memory exhaustion by only
291
- # calling to_sym if the symbol already exists.
292
- raise(Error, "all string keys must be a valid columns") unless s.include?(k)
293
- k.to_sym
294
- else
295
- raise(Error, "Only symbols and strings allows as keys")
296
- end
297
- m[k] = v
298
- m
299
- end
300
- vals.each {|k, v| @values[k] = v}
301
- vals
302
- end
303
-
304
- # Returns (naked) dataset that should return only this instance.
305
- def this
306
- @this ||= dataset.filter(pk_hash).limit(1).naked
307
- end
308
-
309
- # Runs set with the passed hash and runs save_changes (which runs any callback methods).
310
- def update(hash)
311
- update_restricted(hash, nil, nil)
312
- end
313
- alias_method :update_with_params, :update
314
-
315
- # Update all values using the entries in the hash, ignoring any setting of
316
- # allowed_columns or restricted columns in the model.
317
- def update_all(hash)
318
- update_restricted(hash, false, false)
319
- end
320
-
321
- # Update all values using the entries in the hash, except for the keys
322
- # given in except.
323
- def update_except(hash, *except)
324
- update_restricted(hash, false, except.flatten)
325
- end
326
-
327
- # Update the values using the entries in the hash, only if the key
328
- # is included in only.
329
- def update_only(hash, *only)
330
- update_restricted(hash, only.flatten, false)
331
- end
332
-
333
- # Sets the values attributes with set_values and then updates
334
- # the record in the database using those values. This is a
335
- # low level method that does not run the usual save callbacks.
336
- # It should probably be avoided. Look into using update_with_params instead.
337
- def update_values(values)
338
- before_update_values
339
- this.update(set_values(values))
340
- end
341
-
342
- private
343
-
344
- # Backbone behind association_dataset
345
- def _dataset(opts)
346
- raise(Sequel::Error, "model object #{model} does not have a primary key") if opts.dataset_need_primary_key? && !pk
347
- ds = send(opts._dataset_method)
348
- ds.extend(Associations::DatasetMethods)
349
- ds.model_object = self
350
- ds.association_reflection = opts
351
- opts[:extend].each{|m| ds.extend(m)}
352
- ds = ds.select(*opts.select) if opts.select
353
- ds = ds.filter(opts[:conditions]) if opts[:conditions]
354
- ds = ds.order(*opts[:order]) if opts[:order]
355
- ds = ds.limit(*opts[:limit]) if opts[:limit]
356
- ds = ds.eager(*opts[:eager]) if opts[:eager]
357
- ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
358
- ds = send(opts.dataset_helper_method, ds) if opts[:block]
359
- ds
360
- end
361
-
362
- # Add the given associated object to the given association
363
- def add_associated_object(opts, o)
364
- raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
365
- raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
366
- return if run_association_callbacks(opts, :before_add, o) == false
367
- send(opts._add_method, o)
368
- associations[opts[:name]].push(o) if associations.include?(opts[:name])
369
- add_reciprocal_object(opts, o)
370
- run_association_callbacks(opts, :after_add, o)
371
- o
372
- end
373
-
374
- # Add/Set the current object to/as the given object's reciprocal association.
375
- def add_reciprocal_object(opts, o)
376
- return unless reciprocal = opts.reciprocal
377
- if opts.reciprocal_array?
378
- if array = o.associations[reciprocal] and !array.include?(self)
379
- array.push(self)
380
- end
381
- else
382
- o.associations[reciprocal] = self
383
- end
384
- end
385
-
386
- # Default inspection output for a record, overwrite to change the way #inspect prints the @values hash
387
- def inspect_values
388
- @values.inspect
389
- end
390
-
391
- # Load the associated objects using the dataset
392
- def load_associated_objects(opts, reload=false)
393
- name = opts[:name]
394
- if associations.include?(name) and !reload
395
- associations[name]
396
- else
397
- objs = if opts.returns_array?
398
- send(opts.dataset_method).all
399
- else
400
- if !opts[:key]
401
- send(opts.dataset_method).all.first
402
- elsif send(opts[:key])
403
- send(opts.dataset_method).first
404
- end
405
- end
406
- run_association_callbacks(opts, :after_load, objs)
407
- # Only one_to_many associations should set the reciprocal object
408
- objs.each{|o| add_reciprocal_object(opts, o)} if opts.set_reciprocal_to_self?
409
- associations[name] = objs
410
- end
411
- end
412
-
413
- # Remove all associated objects from the given association
414
- def remove_all_associated_objects(opts)
415
- raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
416
- send(opts._remove_all_method)
417
- ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
418
- associations[opts[:name]] = []
419
- ret
420
- end
421
-
422
- # Remove the given associated object from the given association
423
- def remove_associated_object(opts, o)
424
- raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
425
- raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
426
- return if run_association_callbacks(opts, :before_remove, o) == false
427
- send(opts._remove_method, o)
428
- associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
429
- remove_reciprocal_object(opts, o)
430
- run_association_callbacks(opts, :after_remove, o)
431
- o
432
- end
433
-
434
- # Remove/unset the current object from/as the given object's reciprocal association.
435
- def remove_reciprocal_object(opts, o)
436
- return unless reciprocal = opts.reciprocal
437
- if opts.reciprocal_array?
438
- if array = o.associations[reciprocal]
439
- array.delete_if{|x| self === x}
440
- end
441
- else
442
- o.associations[reciprocal] = nil
443
- end
444
- end
445
-
446
- # Run the callback for the association with the object.
447
- def run_association_callbacks(reflection, callback_type, object)
448
- raise_error = raise_on_save_failure || !reflection.returns_array?
449
- stop_on_false = [:before_add, :before_remove].include?(callback_type)
450
- reflection[callback_type].each do |cb|
451
- res = case cb
452
- when Symbol
453
- send(cb, object)
454
- when Proc
455
- cb.call(self, object)
456
- else
457
- raise Error, "callbacks should either be Procs or Symbols"
458
- end
459
- if res == false and stop_on_false
460
- raise(BeforeHookFailed, "Unable to modify association for record: one of the #{callback_type} hooks returned false") if raise_error
461
- return false
462
- end
463
- end
464
- end
465
-
466
- # Raise an error if raise_on_save_failure is true
467
- def save_failure(type)
468
- if raise_on_save_failure
469
- if type == :invalid
470
- raise ValidationFailed, errors.full_messages.join(', ')
471
- else
472
- raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
473
- end
474
- end
475
- end
476
-
477
- # Set the given object as the associated object for the given association
478
- def set_associated_object(opts, o)
479
- raise(Sequel::Error, "model object #{model} does not have a primary key") if o && !o.pk
480
- old_val = send(opts.association_method)
481
- return o if old_val == o
482
- return if old_val and run_association_callbacks(opts, :before_remove, old_val) == false
483
- return if o and run_association_callbacks(opts, :before_add, o) == false
484
- send(opts._setter_method, o)
485
- associations[opts[:name]] = o
486
- remove_reciprocal_object(opts, old_val) if old_val
487
- if o
488
- add_reciprocal_object(opts, o)
489
- run_association_callbacks(opts, :after_add, o)
490
- end
491
- run_association_callbacks(opts, :after_remove, old_val) if old_val
492
- o
493
- end
494
-
495
- # Set the columns, filtered by the only and except arrays.
496
- def set_restricted(hash, only, except)
497
- columns_not_set = model.instance_variable_get(:@columns).blank?
498
- meths = setter_methods(only, except)
499
- strict = strict_param_setting
500
- hash.each do |k,v|
501
- m = "#{k}="
502
- if meths.include?(m)
503
- send(m, v)
504
- elsif columns_not_set && (Symbol === k)
505
- self[k] = v
506
- elsif strict
507
- raise Error, "method #{m} doesn't exist or access is restricted to it"
508
- end
509
- end
510
- self
511
- end
512
-
513
- # Returns all methods that can be used for attribute
514
- # assignment (those that end with =), modified by the only
515
- # and except arguments:
516
- #
517
- # * only
518
- # * false - Don't modify the results
519
- # * nil - if the model has allowed_columns, use only these, otherwise, don't modify
520
- # * Array - allow only the given methods to be used
521
- # * except
522
- # * false - Don't modify the results
523
- # * nil - if the model has restricted_columns, remove these, otherwise, don't modify
524
- # * Array - remove the given methods
525
- #
526
- # only takes precedence over except, and if only is not used, certain methods are always
527
- # restricted (RESTRICTED_SETTER_METHODS). The primary key is restricted by default as
528
- # well, see Model.unrestrict_primary_key to change this.
529
- def setter_methods(only, except)
530
- only = only.nil? ? model.allowed_columns : only
531
- except = except.nil? ? model.restricted_columns : except
532
- if only
533
- only.map{|x| "#{x}="}
534
- else
535
- meths = methods.collect{|x| x.to_s}.grep(/=\z/) - RESTRICTED_SETTER_METHODS
536
- meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && model.restrict_primary_key?
537
- meths -= except.map{|x| "#{x}="} if except
538
- meths
539
- end
540
- end
541
-
542
- # Typecast the value to the column's type if typecasting. Calls the database's
543
- # typecast_value method, so database adapters can override/augment the handling
544
- # for database specific column types.
545
- def typecast_value(column, value)
546
- return value unless typecast_on_assignment && db_schema && (col_schema = db_schema[column]) && !model.serialized?(column)
547
- value = nil if value == '' and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
548
- raise(Error::InvalidValue, "nil/NULL is not allowed for the #{column} column") if raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
549
- begin
550
- model.db.typecast_value(col_schema[:type], value)
551
- rescue Error::InvalidValue
552
- raise_on_typecast_failure ? raise : value
553
- end
554
- end
555
-
556
- # Call uniq! on the given array. This is used by the :uniq option,
557
- # and is an actual method for memory reasons.
558
- def array_uniq!(a)
559
- a.uniq!
560
- end
561
-
562
- # Set the columns, filtered by the only and except arrays.
563
- def update_restricted(hash, only, except)
564
- set_restricted(hash, only, except)
565
- save_changes
566
- end
567
- end
568
- end