sequel 2.11.0 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
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