sequel 4.47.0 → 4.48.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 (177) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +134 -0
  3. data/Rakefile +1 -1
  4. data/doc/release_notes/4.48.0.txt +293 -0
  5. data/lib/sequel/adapters/ado/access.rb +2 -1
  6. data/lib/sequel/adapters/do/postgres.rb +5 -2
  7. data/lib/sequel/adapters/ibmdb.rb +24 -7
  8. data/lib/sequel/adapters/jdbc.rb +36 -22
  9. data/lib/sequel/adapters/jdbc/db2.rb +12 -3
  10. data/lib/sequel/adapters/jdbc/derby.rb +4 -5
  11. data/lib/sequel/adapters/jdbc/oracle.rb +16 -2
  12. data/lib/sequel/adapters/jdbc/postgresql.rb +43 -18
  13. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +9 -7
  14. data/lib/sequel/adapters/jdbc/sqlserver.rb +11 -4
  15. data/lib/sequel/adapters/mock.rb +24 -19
  16. data/lib/sequel/adapters/mysql.rb +17 -16
  17. data/lib/sequel/adapters/mysql2.rb +4 -5
  18. data/lib/sequel/adapters/oracle.rb +5 -9
  19. data/lib/sequel/adapters/postgres.rb +89 -102
  20. data/lib/sequel/adapters/shared/db2.rb +22 -6
  21. data/lib/sequel/adapters/shared/mssql.rb +5 -4
  22. data/lib/sequel/adapters/shared/mysql.rb +75 -24
  23. data/lib/sequel/adapters/shared/postgres.rb +196 -94
  24. data/lib/sequel/adapters/shared/sqlanywhere.rb +23 -10
  25. data/lib/sequel/adapters/shared/sqlite.rb +72 -82
  26. data/lib/sequel/adapters/sqlanywhere.rb +4 -1
  27. data/lib/sequel/adapters/sqlite.rb +5 -3
  28. data/lib/sequel/adapters/swift/postgres.rb +5 -2
  29. data/lib/sequel/adapters/tinytds.rb +0 -5
  30. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  31. data/lib/sequel/adapters/utils/pg_types.rb +2 -76
  32. data/lib/sequel/core.rb +2 -2
  33. data/lib/sequel/database/connecting.rb +5 -5
  34. data/lib/sequel/database/dataset.rb +6 -3
  35. data/lib/sequel/database/misc.rb +1 -1
  36. data/lib/sequel/database/query.rb +3 -0
  37. data/lib/sequel/database/schema_methods.rb +1 -1
  38. data/lib/sequel/dataset/actions.rb +18 -10
  39. data/lib/sequel/dataset/graph.rb +1 -1
  40. data/lib/sequel/dataset/misc.rb +1 -0
  41. data/lib/sequel/dataset/prepared_statements.rb +3 -3
  42. data/lib/sequel/dataset/query.rb +19 -8
  43. data/lib/sequel/extensions/core_extensions.rb +4 -1
  44. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  45. data/lib/sequel/extensions/empty_array_ignore_nulls.rb +3 -0
  46. data/lib/sequel/extensions/filter_having.rb +2 -0
  47. data/lib/sequel/extensions/freeze_datasets.rb +2 -0
  48. data/lib/sequel/extensions/from_block.rb +1 -1
  49. data/lib/sequel/extensions/graph_each.rb +2 -2
  50. data/lib/sequel/extensions/hash_aliases.rb +2 -0
  51. data/lib/sequel/extensions/identifier_mangling.rb +0 -7
  52. data/lib/sequel/extensions/meta_def.rb +2 -0
  53. data/lib/sequel/extensions/migration.rb +6 -6
  54. data/lib/sequel/extensions/no_auto_literal_strings.rb +1 -1
  55. data/lib/sequel/extensions/pagination.rb +1 -1
  56. data/lib/sequel/extensions/pg_array.rb +207 -130
  57. data/lib/sequel/extensions/pg_hstore.rb +38 -20
  58. data/lib/sequel/extensions/pg_inet.rb +18 -6
  59. data/lib/sequel/extensions/pg_interval.rb +19 -12
  60. data/lib/sequel/extensions/pg_json.rb +25 -14
  61. data/lib/sequel/extensions/pg_json_ops.rb +2 -2
  62. data/lib/sequel/extensions/pg_range.rb +133 -100
  63. data/lib/sequel/extensions/pg_range_ops.rb +4 -3
  64. data/lib/sequel/extensions/pg_row.rb +68 -39
  65. data/lib/sequel/extensions/pg_row_ops.rb +11 -5
  66. data/lib/sequel/extensions/query_literals.rb +2 -0
  67. data/lib/sequel/extensions/ruby18_symbol_extensions.rb +2 -0
  68. data/lib/sequel/extensions/s.rb +1 -1
  69. data/lib/sequel/extensions/schema_dumper.rb +24 -24
  70. data/lib/sequel/extensions/sequel_3_dataset_methods.rb +3 -1
  71. data/lib/sequel/extensions/sequel_4_dataset_methods.rb +83 -0
  72. data/lib/sequel/extensions/set_overrides.rb +2 -2
  73. data/lib/sequel/extensions/string_agg.rb +0 -1
  74. data/lib/sequel/extensions/symbol_aref.rb +0 -4
  75. data/lib/sequel/model.rb +25 -57
  76. data/lib/sequel/model/associations.rb +14 -5
  77. data/lib/sequel/model/base.rb +96 -32
  78. data/lib/sequel/plugins/association_pks.rb +73 -46
  79. data/lib/sequel/plugins/association_proxies.rb +1 -1
  80. data/lib/sequel/plugins/auto_validations.rb +6 -2
  81. data/lib/sequel/plugins/boolean_readers.rb +1 -1
  82. data/lib/sequel/plugins/caching.rb +19 -13
  83. data/lib/sequel/plugins/class_table_inheritance.rb +19 -10
  84. data/lib/sequel/plugins/column_conflicts.rb +7 -2
  85. data/lib/sequel/plugins/column_select.rb +1 -1
  86. data/lib/sequel/plugins/csv_serializer.rb +8 -8
  87. data/lib/sequel/plugins/defaults_setter.rb +10 -0
  88. data/lib/sequel/plugins/eager_each.rb +1 -1
  89. data/lib/sequel/plugins/force_encoding.rb +2 -2
  90. data/lib/sequel/plugins/hook_class_methods.rb +9 -12
  91. data/lib/sequel/plugins/identifier_columns.rb +2 -0
  92. data/lib/sequel/plugins/instance_filters.rb +3 -1
  93. data/lib/sequel/plugins/instance_hooks.rb +17 -9
  94. data/lib/sequel/plugins/json_serializer.rb +17 -10
  95. data/lib/sequel/plugins/lazy_attributes.rb +8 -7
  96. data/lib/sequel/plugins/modification_detection.rb +3 -0
  97. data/lib/sequel/plugins/nested_attributes.rb +5 -1
  98. data/lib/sequel/plugins/pg_array_associations.rb +5 -0
  99. data/lib/sequel/plugins/prepared_statements.rb +1 -0
  100. data/lib/sequel/plugins/rcte_tree.rb +4 -4
  101. data/lib/sequel/plugins/serialization.rb +3 -10
  102. data/lib/sequel/plugins/single_table_inheritance.rb +2 -2
  103. data/lib/sequel/plugins/split_values.rb +6 -5
  104. data/lib/sequel/plugins/static_cache.rb +31 -25
  105. data/lib/sequel/plugins/subset_conditions.rb +3 -1
  106. data/lib/sequel/plugins/table_select.rb +1 -1
  107. data/lib/sequel/plugins/touch.rb +2 -1
  108. data/lib/sequel/plugins/validation_class_methods.rb +5 -6
  109. data/lib/sequel/plugins/validation_helpers.rb +2 -4
  110. data/lib/sequel/plugins/xml_serializer.rb +4 -4
  111. data/lib/sequel/sql.rb +2 -2
  112. data/lib/sequel/version.rb +1 -1
  113. data/spec/adapters/db2_spec.rb +115 -14
  114. data/spec/adapters/mysql_spec.rb +78 -28
  115. data/spec/adapters/oracle_spec.rb +24 -24
  116. data/spec/adapters/postgres_spec.rb +38 -24
  117. data/spec/adapters/sqlanywhere_spec.rb +88 -86
  118. data/spec/adapters/sqlite_spec.rb +29 -24
  119. data/spec/core/connection_pool_spec.rb +17 -0
  120. data/spec/core/database_spec.rb +6 -0
  121. data/spec/core/dataset_spec.rb +46 -36
  122. data/spec/core/schema_spec.rb +16 -0
  123. data/spec/core/spec_helper.rb +1 -0
  124. data/spec/core_extensions_spec.rb +6 -2
  125. data/spec/extensions/active_model_spec.rb +1 -1
  126. data/spec/extensions/arbitrary_servers_spec.rb +1 -1
  127. data/spec/extensions/association_pks_spec.rb +34 -2
  128. data/spec/extensions/auto_literal_strings_spec.rb +5 -1
  129. data/spec/extensions/auto_validations_spec.rb +2 -0
  130. data/spec/extensions/boolean_readers_spec.rb +1 -1
  131. data/spec/extensions/boolean_subsets_spec.rb +1 -1
  132. data/spec/extensions/class_table_inheritance_spec.rb +48 -2
  133. data/spec/extensions/column_conflicts_spec.rb +11 -0
  134. data/spec/extensions/connection_validator_spec.rb +1 -1
  135. data/spec/extensions/dataset_associations_spec.rb +8 -8
  136. data/spec/extensions/defaults_setter_spec.rb +1 -1
  137. data/spec/extensions/filter_having_spec.rb +5 -3
  138. data/spec/extensions/hash_aliases_spec.rb +3 -1
  139. data/spec/extensions/identifier_columns_spec.rb +3 -1
  140. data/spec/extensions/implicit_subquery_spec.rb +4 -2
  141. data/spec/extensions/json_serializer_spec.rb +18 -0
  142. data/spec/extensions/lazy_attributes_spec.rb +3 -3
  143. data/spec/extensions/meta_def_spec.rb +9 -0
  144. data/spec/extensions/migration_spec.rb +3 -3
  145. data/spec/extensions/nested_attributes_spec.rb +14 -3
  146. data/spec/extensions/no_auto_literal_strings_spec.rb +8 -4
  147. data/spec/extensions/pg_array_associations_spec.rb +29 -18
  148. data/spec/extensions/pg_array_spec.rb +44 -25
  149. data/spec/extensions/pg_hstore_spec.rb +10 -0
  150. data/spec/extensions/pg_inet_spec.rb +26 -0
  151. data/spec/extensions/pg_interval_spec.rb +20 -0
  152. data/spec/extensions/pg_json_spec.rb +24 -0
  153. data/spec/extensions/pg_range_spec.rb +98 -14
  154. data/spec/extensions/pg_row_spec.rb +14 -4
  155. data/spec/extensions/prepared_statements_safe_spec.rb +1 -1
  156. data/spec/extensions/query_literals_spec.rb +3 -1
  157. data/spec/extensions/schema_dumper_spec.rb +96 -98
  158. data/spec/extensions/sequel_3_dataset_methods_spec.rb +10 -6
  159. data/spec/extensions/sequel_4_dataset_methods_spec.rb +121 -0
  160. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  161. data/spec/extensions/spec_helper.rb +7 -1
  162. data/spec/extensions/static_cache_spec.rb +75 -24
  163. data/spec/extensions/string_agg_spec.rb +1 -1
  164. data/spec/extensions/touch_spec.rb +9 -0
  165. data/spec/extensions/validation_helpers_spec.rb +9 -3
  166. data/spec/extensions/whitelist_security_spec.rb +26 -0
  167. data/spec/integration/dataset_test.rb +45 -44
  168. data/spec/integration/plugin_test.rb +20 -0
  169. data/spec/integration/prepared_statement_test.rb +3 -0
  170. data/spec/integration/schema_test.rb +21 -1
  171. data/spec/integration/transaction_test.rb +40 -40
  172. data/spec/model/class_dataset_methods_spec.rb +14 -4
  173. data/spec/model/dataset_methods_spec.rb +12 -3
  174. data/spec/model/model_spec.rb +8 -0
  175. metadata +6 -4
  176. data/spec/adapters/firebird_spec.rb +0 -405
  177. data/spec/adapters/informix_spec.rb +0 -100
@@ -23,6 +23,8 @@
23
23
  #
24
24
  # Related module: Sequel::Sequel3DatasetMethods
25
25
 
26
+ Sequel::Deprecation.deprecate("The sequel_3_dataset_methods extension", "Please consider maintaining it yourself as an external gem if you want to continue using it")
27
+
26
28
  #
27
29
  module Sequel
28
30
  module Sequel3DatasetMethods
@@ -122,7 +124,7 @@ module Sequel
122
124
  cols = n.columns
123
125
  csv = String.new
124
126
  csv << "#{cols.join(', ')}\r\n" if include_column_titles
125
- n.each{|r| csv << "#{cols.collect{|c| r[c]}.join(', ')}\r\n"}
127
+ n.each{|r| csv << "#{cols.map{|c| r[c]}.join(', ')}\r\n"}
126
128
  csv
127
129
  end
128
130
  end
@@ -0,0 +1,83 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # This adds the following dataset methods:
4
+ #
5
+ # and :: alias for where
6
+ # exclude_where :: alias for exclude
7
+ # interval :: Returns max - min, using a single query
8
+ # range :: Returns min..max, using a single query
9
+ #
10
+ # It is only recommended to use this for backwards compatibility.
11
+ #
12
+ # You can load this extension into specific datasets:
13
+ #
14
+ # ds = DB[:table]
15
+ # ds = ds.extension(:sequel_4_dataset_methods)
16
+ #
17
+ # Or you can load it into all of a database's datasets, which
18
+ # is probably the desired behavior if you are using this extension:
19
+ #
20
+ # DB.extension(:sequel_4_dataset_methods)
21
+ #
22
+ # Related module: Sequel::Sequel4DatasetMethods
23
+
24
+ #
25
+ module Sequel
26
+ module Sequel4DatasetMethods
27
+ # Alias for where.
28
+ def and(*cond, &block)
29
+ where(*cond, &block)
30
+ end
31
+
32
+ # Alias for exclude.
33
+ def exclude_where(*cond, &block)
34
+ exclude(*cond, &block)
35
+ end
36
+
37
+ # Returns the interval between minimum and maximum values for the given
38
+ # column/expression. Uses a virtual row block if no argument is given.
39
+ #
40
+ # DB[:table].interval(:id) # SELECT (max(id) - min(id)) FROM table LIMIT 1
41
+ # # => 6
42
+ # DB[:table].interval{function(column)} # SELECT (max(function(column)) - min(function(column))) FROM table LIMIT 1
43
+ # # => 7
44
+ def interval(column=Sequel.virtual_row(&Proc.new))
45
+ if loader = cached_placeholder_literalizer(:_interval_loader) do |pl|
46
+ arg = pl.arg
47
+ aggregate_dataset.limit(1).select((SQL::Function.new(:max, arg) - SQL::Function.new(:min, arg)).as(:interval))
48
+ end
49
+
50
+ loader.get(column)
51
+ else
52
+ aggregate_dataset.get{(max(column) - min(column)).as(:interval)}
53
+ end
54
+ end
55
+
56
+ # Returns a +Range+ instance made from the minimum and maximum values for the
57
+ # given column/expression. Uses a virtual row block if no argument is given.
58
+ #
59
+ # DB[:table].range(:id) # SELECT max(id) AS v1, min(id) AS v2 FROM table LIMIT 1
60
+ # # => 1..10
61
+ # DB[:table].interval{function(column)} # SELECT max(function(column)) AS v1, min(function(column)) AS v2 FROM table LIMIT 1
62
+ # # => 0..7
63
+ def range(column=Sequel.virtual_row(&Proc.new))
64
+ r = if loader = cached_placeholder_literalizer(:_range_loader) do |pl|
65
+ arg = pl.arg
66
+ aggregate_dataset.limit(1).select(SQL::Function.new(:min, arg).as(:v1), SQL::Function.new(:max, arg).as(:v2))
67
+ end
68
+
69
+ loader.first(column)
70
+ else
71
+ aggregate_dataset.select{[min(column).as(v1), max(column).as(v2)]}.first
72
+ end
73
+
74
+ if r
75
+ (r[:v1]..r[:v2])
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ Dataset.register_extension(:sequel_4_dataset_methods, Sequel4DatasetMethods)
82
+ end
83
+
@@ -73,8 +73,8 @@ module Sequel
73
73
  end
74
74
 
75
75
  # Dataset options that do not affect the generated SQL.
76
- def non_sql_options
77
- super + [:defaults, :overrides]
76
+ def non_sql_option?(key)
77
+ super || key == :defaults || key == :overrides
78
78
  end
79
79
  end
80
80
 
@@ -53,7 +53,6 @@
53
53
  # * DB2 9.7+ (except distinct)
54
54
  # * MySQL
55
55
  # * HSQLDB
56
- # * CUBRID
57
56
  # * H2
58
57
  #
59
58
  # Related module: Sequel::SQL::StringAgg
@@ -18,10 +18,6 @@
18
18
  # If you are using Ruby 2+, and you would like to use refinements, there
19
19
  # is a refinement version of this in the symbol_aref_refinement extension.
20
20
  #
21
- # If you are using the ruby18_symbol_extensions, and would like symbol_aref
22
- # to take affect, load the symbol_aref extension after the
23
- # ruby18_symbol_extensions.
24
- #
25
21
  # Related module: Sequel::SymbolAref
26
22
 
27
23
  if RUBY_VERSION >= '2.0'
@@ -31,62 +31,36 @@ module Sequel
31
31
  class Model
32
32
  OPTS = Sequel::OPTS
33
33
 
34
- # Class methods added to model that call the method of the same name on the dataset
35
- DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:each_server, :where_all, :where_each, :where_single_value]) -
36
- [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases] # SEQUEL5: Remove set_graph_aliases
37
-
38
- # Boolean settings that can be modified at the global, class, or instance level.
39
- BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
40
- :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions,
41
- :use_after_commit_rollback # SEQUEL5: Remove
42
- ]
43
-
44
- # Hooks that are called before an action. Can return false to not do the action. When
45
- # overriding these, it is recommended to call +super+ as the last line of your method,
46
- # so later hooks are called before earlier hooks.
34
+ DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:each_server, :where_all, :where_each, :where_single_value]) - [:and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases]
35
+ Sequel::Deprecation.deprecate_constant(self, :DATASET_METHODS)
36
+ BOOLEAN_SETTINGS = [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions, :use_after_commit_rollback]
37
+ Sequel::Deprecation.deprecate_constant(self, :BOOLEAN_SETTINGS)
47
38
  BEFORE_HOOKS = [:before_create, :before_update, :before_save, :before_destroy, :before_validation]
48
-
49
- # Hooks that are called after an action. When overriding these, it is recommended to call
50
- # +super+ on the first line of your method, so later hooks are called after earlier hooks.
51
- AFTER_HOOKS = [:after_create, :after_update, :after_save, :after_destroy, :after_validation,
52
- :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback] # SEQUEL5: Remove commit/rollback hooks
53
-
54
- # Hooks that are called around an action. If overridden, these methods must call super
55
- # exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
56
- # raised by the code they wrap or ensure that some behavior is executed no matter what.
39
+ Sequel::Deprecation.deprecate_constant(self, :BEFORE_HOOKS)
40
+ AFTER_HOOKS = [:after_create, :after_update, :after_save, :after_destroy, :after_validation, :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback]
41
+ Sequel::Deprecation.deprecate_constant(self, :AFTER_HOOKS)
57
42
  AROUND_HOOKS = [:around_create, :around_update, :around_save, :around_destroy, :around_validation]
43
+ Sequel::Deprecation.deprecate_constant(self, :AROUND_HOOKS)
44
+ NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
45
+ Sequel::Deprecation.deprecate_constant(self, :NORMAL_METHOD_NAME_REGEXP)
46
+ SETTER_METHOD_REGEXP = /=\z/
47
+ Sequel::Deprecation.deprecate_constant(self, :SETTER_METHOD_REGEXP)
48
+ ANONYMOUS_MODEL_CLASSES = @Model_cache = {}
49
+ Sequel::Deprecation.deprecate_constant(self, :ANONYMOUS_MODEL_CLASSES)
50
+ ANONYMOUS_MODEL_CLASSES_MUTEX = Mutex.new
51
+ Sequel::Deprecation.deprecate_constant(self, :ANONYMOUS_MODEL_CLASSES_MUTEX)
52
+ INHERITED_INSTANCE_VARIABLES = { :@allowed_columns=>:dup, :@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil, :@raise_on_save_failure=>nil, :@require_modification=>nil, :@restrict_primary_key=>nil, :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil, :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil, :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil, :@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil, :@fast_instance_delete_sql=>nil, :@finders=>:dup, :@finder_loaders=>:dup, :@db=>nil, :@default_set_fields_options=>:dup, :@require_valid_table=>nil, :@cache_anonymous_models=>nil, :@dataset_module_class=>nil}
53
+ Sequel::Deprecation.deprecate_constant(self, :INHERITED_INSTANCE_VARIABLES)
58
54
 
59
55
  # Empty instance methods to create that the user can override to get hook/callback behavior.
60
56
  # Just like any other method defined by Sequel, if you override one of these, you should
61
57
  # call +super+ to get the default behavior (while empty by default, they can also be defined
62
58
  # by plugins). See the {"Model Hooks" guide}[rdoc-ref:doc/model_hooks.rdoc] for
63
59
  # more detail on hooks.
64
- HOOKS = BEFORE_HOOKS + AFTER_HOOKS
65
-
66
- # Class instance variables that are inherited in subclasses. If the value is <tt>:dup</tt>, dup is called
67
- # on the superclass's instance variable when creating the instance variable in the subclass.
68
- # If the value is +nil+, the superclass's instance variable is used directly in the subclass.
69
- INHERITED_INSTANCE_VARIABLES = {
70
- :@allowed_columns=>:dup, # SEQUEL5: Remove
71
- :@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
72
- :@raise_on_save_failure=>nil, :@require_modification=>nil, :@restrict_primary_key=>nil,
73
- :@simple_pk=>nil, :@simple_table=>nil, :@strict_param_setting=>nil,
74
- :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
75
- :@raise_on_typecast_failure=>nil, :@plugins=>:dup, :@setter_methods=>nil,
76
- :@use_after_commit_rollback=>nil, :@fast_pk_lookup_sql=>nil,
77
- :@fast_instance_delete_sql=>nil,
78
- :@finders=>:dup, :@finder_loaders=>:dup, # SEQUEL5: Remove
79
- :@db=>nil, :@default_set_fields_options=>:dup, :@require_valid_table=>nil,
80
- :@cache_anonymous_models=>nil, :@dataset_module_class=>nil}
81
-
82
- # Regular expression that determines if a method name is normal in the sense that
83
- # it could be used literally in ruby code without using send. Used to
84
- # avoid problems when using eval with a string to define methods.
85
- NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
86
-
87
- # Regular expression that determines if the method is a valid setter name
88
- # (i.e. it ends with =).
89
- SETTER_METHOD_REGEXP = /=\z/
60
+ HOOKS = [:before_create, :before_update, :before_save, :before_destroy, :before_validation,
61
+ :after_create, :after_update, :after_save, :after_destroy, :after_validation,
62
+ :after_commit, :after_rollback, :after_destroy_commit, :after_destroy_rollback # SEQUEL5: Remove commit/rollback hooks
63
+ ]#.freeze # SEQUEL5
90
64
 
91
65
  @allowed_columns = nil # SEQUEL5: Remove
92
66
  @cache_anonymous_models = true
@@ -123,11 +97,11 @@ module Sequel
123
97
  plugin Model::Associations
124
98
  end
125
99
 
100
+ def_Model(::Sequel)
101
+
126
102
  # The setter methods (methods ending with =) that are never allowed
127
103
  # to be called automatically via +set+/+update+/+new+/etc..
128
- RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).grep(SETTER_METHOD_REGEXP)
129
-
130
- def_Model(::Sequel)
104
+ RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).select{|l| l.end_with?('=')}#.freeze # SEQUEL5
131
105
 
132
106
  # SEQUEL5: Remove
133
107
  class DeprecatedColumnsUpdated # :nodoc:
@@ -140,11 +114,5 @@ module Sequel
140
114
  @columns_updated.send(*args, &block)
141
115
  end
142
116
  end
143
-
144
- ANONYMOUS_MODEL_CLASSES = @Model_cache # :nodoc:
145
- Sequel::Deprecation.deprecate_constant(self, :ANONYMOUS_MODEL_CLASSES)
146
-
147
- ANONYMOUS_MODEL_CLASSES_MUTEX = Mutex.new # :nodoc:
148
- Sequel::Deprecation.deprecate_constant(self, :ANONYMOUS_MODEL_CLASSES_MUTEX)
149
117
  end
150
118
  end
@@ -1450,7 +1450,7 @@ module Sequel
1450
1450
 
1451
1451
  # This module contains methods added to all association datasets
1452
1452
  module AssociationDatasetMethods
1453
- Dataset.def_deprecated_opts_setter(self, :model, :association_reflection)
1453
+ Dataset.def_deprecated_opts_setter(self, :model_object, :association_reflection)
1454
1454
 
1455
1455
  # The model object that created the association dataset
1456
1456
  def model_object
@@ -1461,6 +1461,12 @@ module Sequel
1461
1461
  def association_reflection
1462
1462
  @opts[:association_reflection]
1463
1463
  end
1464
+
1465
+ private
1466
+
1467
+ def non_sql_option?(key)
1468
+ super || key == :model_object || key == :association_reflection
1469
+ end
1464
1470
  end
1465
1471
 
1466
1472
  # Each kind of association adds a number of instance methods to the model class which
@@ -1782,7 +1788,6 @@ module Sequel
1782
1788
  opts.merge!(orig_opts)
1783
1789
  opts.merge!(:type => type, :name => name, :cache=>({} if cache_associations), :model => self)
1784
1790
 
1785
- opts
1786
1791
  opts[:block] = block if block
1787
1792
  if !opts.has_key?(:instance_specific) && (block || orig_opts[:block] || orig_opts[:dataset])
1788
1793
  # It's possible the association is instance specific, in that it depends on
@@ -1912,7 +1917,7 @@ module Sequel
1912
1917
  # Adds the association method to the association methods module.
1913
1918
  def def_association_method(opts)
1914
1919
  association_module_def(opts.association_method, opts) do |*dynamic_opts, &block|
1915
- Sequel::Deprecation.deprecate("Passing multiple arguments to ##{opts.association_method}", "Additional arguments are currently ignored.") if dynamic_opts.length > 1
1920
+ Sequel::Deprecation.deprecate("Passing multiple arguments to ##{opts.association_method}", "Additional arguments are currently ignored") if dynamic_opts.length > 1
1916
1921
  load_associated_objects(opts, dynamic_opts.length == 0 ? OPTS : dynamic_opts[0], &block)
1917
1922
  end
1918
1923
  end
@@ -2815,7 +2820,7 @@ END
2815
2820
 
2816
2821
  # If the dataset is being eagerly loaded, default to calling all
2817
2822
  # instead of each.
2818
- def to_hash(key_column=nil, value_column=nil, opts=OPTS)
2823
+ def as_hash(key_column=nil, value_column=nil, opts=OPTS)
2819
2824
  if (@opts[:eager_graph] || @opts[:eager]) && !opts.has_key?(:all)
2820
2825
  opts = Hash[opts]
2821
2826
  opts[:all] = true
@@ -3040,7 +3045,7 @@ END
3040
3045
  # specific foreign/primary key
3041
3046
  key_hash = {}
3042
3047
  # Reflections for all associations to eager load
3043
- reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3048
+ reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3044
3049
 
3045
3050
  # Populate the key_hash entry for each association being eagerly loaded
3046
3051
  reflections.each do |r|
@@ -3136,6 +3141,10 @@ END
3136
3141
  end
3137
3142
  alias one_to_one_association_filter_expression one_to_many_association_filter_expression
3138
3143
 
3144
+ def non_sql_option?(key)
3145
+ super || key == :eager || key == :eager_graph
3146
+ end
3147
+
3139
3148
  # Build associations from the graph if #eager_graph was used,
3140
3149
  # and/or load other associations if #eager was used.
3141
3150
  def post_load(all_records)
@@ -7,8 +7,20 @@ module Sequel
7
7
 
8
8
  # Class methods for Sequel::Model that implement basic model functionality.
9
9
  #
10
- # * All of the method names in Model::DATASET_METHODS have class methods created that call
11
- # the Model's dataset with the method of the same name with the given arguments.
10
+ # * All of the following methods have class methods created that send the method
11
+ # to the model's dataset: all, as_hash, avg, count, cross_join, distinct, each,
12
+ # each_server, empty?, except, exclude, exclude_having, fetch_rows,
13
+ # filter, first, first!, for_update, from, from_self, full_join, full_outer_join,
14
+ # get, graph, grep, group, group_and_count, group_append, group_by, having, import,
15
+ # inner_join, insert, intersect, invert, join, join_table, last, left_join,
16
+ # left_outer_join, limit, lock_style, map, max, min, multi_insert, naked, natural_full_join,
17
+ # natural_join, natural_left_join, natural_right_join, offset, order, order_append, order_by,
18
+ # order_more, order_prepend, paged_each, qualify, reverse, reverse_order, right_join,
19
+ # right_outer_join, select, select_all, select_append, select_group, select_hash,
20
+ # select_hash_groups, select_map, select_more, select_order_map, server,
21
+ # single_record, single_record!, single_value, single_value!, sum, to_hash, to_hash_groups,
22
+ # truncate, unfiltered, ungraphed, ungrouped, union, unlimited, unordered, where, where_all,
23
+ # where_each, where_single_value, with, with_recursive, with_sql
12
24
  module ClassMethods
13
25
  # Which columns should be the only columns allowed in a call to a mass assignment method (e.g. set)
14
26
  # (default: not set, so all columns not otherwise restricted are allowed).
@@ -215,16 +227,20 @@ module Sequel
215
227
  end
216
228
 
217
229
  def initialize_copy(_)
218
- Sequel::Deprecation.deprecate("Model.clone", "Create a subclass of the model instead of cloning it.")
230
+ Sequel::Deprecation.deprecate("Model.clone", "Create a subclass of the model instead of cloning it")
219
231
  # raise(Error, "cannot dup/clone a Sequel::Model class") # SEQUEL5
220
232
  super
221
233
  end
222
234
  def dup
223
- Sequel::Deprecation.deprecate("Model.dup", "Create a subclass of the model instead of duping it.")
235
+ Sequel::Deprecation.deprecate("Model.dup", "Create a subclass of the model instead of duping it")
224
236
  # raise(Error, "cannot dup/clone a Sequel::Model class") # SEQUEL5
225
237
  super
226
238
  end
227
239
 
240
+ def <<(arg)
241
+ Sequel::Deprecation.deprecate("Sequel::Model.<<", "Switch to using #insert")
242
+ dataset << (arg)
243
+ end
228
244
 
229
245
  # Returns the first record from the database matching the conditions.
230
246
  # If a hash is given, it is used as the conditions. If another
@@ -661,7 +677,7 @@ module Sequel
661
677
  # end
662
678
  def inherited(subclass)
663
679
  super
664
- ivs = subclass.instance_variables.collect(&:to_s)
680
+ ivs = subclass.instance_variables.map(&:to_s)
665
681
  inherited_instance_variables.each do |iv, dup|
666
682
  next if ivs.include?(iv.to_s)
667
683
  if (sup_class_value = instance_variable_get(iv)) && dup
@@ -707,7 +723,7 @@ module Sequel
707
723
 
708
724
  # Clear the setter_methods cache when a setter method is added
709
725
  def method_added(meth)
710
- clear_setter_methods_cache if meth.to_s =~ SETTER_METHOD_REGEXP
726
+ clear_setter_methods_cache if meth.to_s.end_with?('=')
711
727
  super
712
728
  end
713
729
 
@@ -976,7 +992,8 @@ module Sequel
976
992
  end
977
993
 
978
994
  # Add model methods that call dataset methods
979
- Plugins.def_dataset_methods(self, DATASET_METHODS)
995
+ Plugins.def_dataset_methods(self, (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS + [:each_server, :where_all, :where_each, :where_single_value]) - [:<<, :and, :or, :[], :columns, :columns!, :delete, :update, :add_graph_aliases])
996
+ # SEQUEL5: add :set_graph_aliases to remove list and remove :and
980
997
 
981
998
  private
982
999
 
@@ -1007,6 +1024,7 @@ module Sequel
1007
1024
  ds = db.from(ds)
1008
1025
  when Dataset
1009
1026
  if ds.joined_dataset?
1027
+ # raise Error, "Using a joined dataset as a model dataset is not support, use from_self on the dataset to wrap it in a subquery" # SEQUEL5
1010
1028
  Sequel::Deprecation.deprecate("Using a joined dataset as a Sequel::Model dataset", respond_to?(:cti_base_model) ? "Use the class_table_inheritance plugin :alias option in #{cti_base_model.inspect}" : "Call from_self on the dataset to wrap it in a subquery")
1011
1029
  end
1012
1030
 
@@ -1045,9 +1063,9 @@ module Sequel
1045
1063
  # use a string to define the method for speed. For other columns names, use a block.
1046
1064
  def def_column_accessor(*columns)
1047
1065
  clear_setter_methods_cache
1048
- columns, bad_columns = columns.partition{|x| NORMAL_METHOD_NAME_REGEXP.match(x.to_s)}
1066
+ columns, bad_columns = columns.partition{|x| /\A[A-Za-z_][A-Za-z0-9_]*\z/.match(x.to_s)}
1049
1067
  bad_columns.each{|x| def_bad_column_accessor(x)}
1050
- im = instance_methods.collect(&:to_s)
1068
+ im = instance_methods.map(&:to_s)
1051
1069
  columns.each do |column|
1052
1070
  meth = "#{column}="
1053
1071
  overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__) unless im.include?(column.to_s)
@@ -1061,7 +1079,7 @@ module Sequel
1061
1079
  def def_model_dataset_method(meth)
1062
1080
  return if respond_to?(meth, true)
1063
1081
 
1064
- if meth.to_s =~ NORMAL_METHOD_NAME_REGEXP
1082
+ if meth.to_s =~ /\A[A-Za-z_][A-Za-z0-9_]*\z/
1065
1083
  instance_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
1066
1084
  else
1067
1085
  (class << self; self; end).send(:define_method, meth){|*args, &block| dataset.send(meth, *args, &block)}
@@ -1111,7 +1129,7 @@ module Sequel
1111
1129
  # Set the primary key(s) based on the schema information,
1112
1130
  # if the schema information includes primary key information
1113
1131
  if schema_array.all?{|k,v| v.has_key?(:primary_key)}
1114
- pks = schema_array.collect{|k,v| k if v[:primary_key]}.compact
1132
+ pks = schema_array.map{|k,v| k if v[:primary_key]}.compact
1115
1133
  pks.length > 0 ? set_primary_key(pks) : no_primary_key
1116
1134
  end
1117
1135
 
@@ -1126,7 +1144,7 @@ module Sequel
1126
1144
  # Dataset is for a single table with all columns,
1127
1145
  # so set the columns based on the order they were
1128
1146
  # returned by the schema.
1129
- cols = schema_array.collect{|k,v| k}
1147
+ cols = schema_array.map{|k,v| k}
1130
1148
  set_columns(cols)
1131
1149
  # Also set the columns for the dataset, so the dataset
1132
1150
  # doesn't have to do a query to get them.
@@ -1148,18 +1166,46 @@ module Sequel
1148
1166
  # SEQUEL5: Remove allowed_columns handling
1149
1167
  allowed_columns.map{|x| "#{x}="}
1150
1168
  else
1151
- meths = instance_methods.collect(&:to_s).grep(SETTER_METHOD_REGEXP) - RESTRICTED_SETTER_METHODS
1169
+ meths = instance_methods.map(&:to_s).select{|l| l.end_with?('=')} - RESTRICTED_SETTER_METHODS
1152
1170
  meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && restrict_primary_key?
1153
1171
  meths
1154
1172
  end
1155
1173
  end
1156
1174
 
1157
1175
  # A hash of instance variables to automatically set up in subclasses.
1158
- # See Sequel::Model::INHERITED_INSTANCE_VARIABLES. It is safe to modify
1159
- # the hash returned by this method, though it may not be safe to modify
1160
- # values of the hash.
1176
+ # Keys are instance variable symbols, values should be:
1177
+ # nil :: Assign directly from superclass to subclass (frozen objects)
1178
+ # :dup :: Dup object when assigning from superclass to subclass (mutable objects)
1179
+ # :hash_dup :: Assign hash with same keys, but dup all the values
1180
+ # Proc :: Call with subclass to do the assignment
1161
1181
  def inherited_instance_variables
1162
- INHERITED_INSTANCE_VARIABLES.dup
1182
+ {
1183
+ :@allowed_columns=>:dup, # SEQUEL5: Remove
1184
+ :@cache_anonymous_models=>nil,
1185
+ :@dataset_method_modules=>:dup,
1186
+ :@dataset_module_class=>nil,
1187
+ :@db=>nil,
1188
+ :@default_set_fields_options=>:dup,
1189
+ :@fast_instance_delete_sql=>nil,
1190
+ :@fast_pk_lookup_sql=>nil,
1191
+ :@finder_loaders=>:dup, # SEQUEL5: Remove
1192
+ :@finders=>:dup, # SEQUEL5: Remove
1193
+ :@plugins=>:dup,
1194
+ :@primary_key=>nil,
1195
+ :@raise_on_save_failure=>nil,
1196
+ :@raise_on_typecast_failure=>nil,
1197
+ :@require_modification=>nil,
1198
+ :@require_valid_table=>nil,
1199
+ :@restrict_primary_key=>nil,
1200
+ :@setter_methods=>nil,
1201
+ :@simple_pk=>nil,
1202
+ :@simple_table=>nil,
1203
+ :@strict_param_setting=>nil,
1204
+ :@typecast_empty_string_to_nil=>nil,
1205
+ :@typecast_on_assignment=>nil,
1206
+ :@use_after_commit_rollback=>nil,
1207
+ :@use_transactions=>nil
1208
+ }
1163
1209
  end
1164
1210
 
1165
1211
  # For the given opts hash and default name or :class option, add a
@@ -1256,6 +1302,7 @@ module Sequel
1256
1302
  ds.fetch_rows(sql){|r| return ds.row_proc.call(r)}
1257
1303
  nil
1258
1304
  elsif dataset.joined_dataset?
1305
+ # SEQUEL5: Remove as joined model datasets are not allowed
1259
1306
  dataset.first(qualified_primary_key_hash(pk))
1260
1307
  else
1261
1308
  dataset.first(primary_key_hash(pk))
@@ -1318,19 +1365,21 @@ module Sequel
1318
1365
 
1319
1366
  # Sequel::Model instance methods that implement basic model functionality.
1320
1367
  #
1321
- # * All of the methods in +HOOKS+ and +AROUND_HOOKS+ create instance methods that are called
1368
+ # * All of the model before/after/around hooks are implemented as instance methods that are called
1322
1369
  # by Sequel when the appropriate action occurs. For example, when destroying
1323
1370
  # a model object, Sequel will call +around_destroy+, which will call +before_destroy+, do
1324
1371
  # the destroy, and then call +after_destroy+.
1325
1372
  # * The following instance_methods all call the class method of the same
1326
1373
  # name: columns, db, primary_key, db_schema.
1327
- # * All of the methods in +BOOLEAN_SETTINGS+ create attr_writers allowing you
1328
- # to set values for the attribute. It also creates instance getters returning
1329
- # the value of the setting. If the value has not yet been set, it
1330
- # gets the default value from the class by calling the class method of the same name.
1374
+ # * The following accessor methods are defined via metaprogramming:
1375
+ # raise_on_save_failure, raise_on_typecast_failure, require_modification,
1376
+ # strict_param_setting, typecast_empty_string_to_nil, typecast_on_assignment,
1377
+ # and use_transactions. The setter methods will change the setting for the
1378
+ # instance, and the getter methods will check for an instance setting, then
1379
+ # try the class setting if no instance setting has been set.
1331
1380
  module InstanceMethods
1332
1381
  HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
1333
- AROUND_HOOKS.each{|h| class_eval("def #{h}; yield end", __FILE__, __LINE__)}
1382
+ [:around_create, :around_update, :around_save, :around_destroy, :around_validation].each{|h| class_eval("def #{h}; yield end", __FILE__, __LINE__)}
1334
1383
 
1335
1384
  # Define instance method(s) that calls class method(s) of the
1336
1385
  # same name. Replaces the construct:
@@ -1341,8 +1390,13 @@ module Sequel
1341
1390
  # Define instance method(s) that calls class method(s) of the
1342
1391
  # same name, caching the result in an instance variable. Define
1343
1392
  # standard attr_writer method for modifying that instance variable.
1344
- BOOLEAN_SETTINGS.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (frozen? ? self.class.#{meth} : (@#{meth} = self.class.#{meth})) : @#{meth} end", __FILE__, __LINE__)}
1345
- attr_writer(*BOOLEAN_SETTINGS)
1393
+ [:typecast_empty_string_to_nil, :typecast_on_assignment, :strict_param_setting, \
1394
+ :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :use_transactions,
1395
+ :use_after_commit_rollback # SEQUEL5: Remove
1396
+ ].each do |meth|
1397
+ class_eval("def #{meth}; !defined?(@#{meth}) ? (frozen? ? self.class.#{meth} : (@#{meth} = self.class.#{meth})) : @#{meth} end", __FILE__, __LINE__)
1398
+ attr_writer(meth)
1399
+ end
1346
1400
 
1347
1401
  # The hash of attribute values. Keys are symbols with the names of the
1348
1402
  # underlying database columns. The returned hash is a reference to the
@@ -1889,7 +1943,7 @@ module Sequel
1889
1943
 
1890
1944
  # Clear the setter_methods cache when a method is added
1891
1945
  def singleton_method_added(meth)
1892
- @singleton_setter_added = true if meth.to_s =~ SETTER_METHOD_REGEXP
1946
+ @singleton_setter_added = true if meth.to_s.end_with?('=')
1893
1947
  super
1894
1948
  end
1895
1949
 
@@ -1902,6 +1956,7 @@ module Sequel
1902
1956
  raise Error, "No dataset for model #{model}" unless ds = model.instance_dataset
1903
1957
 
1904
1958
  cond = if ds.joined_dataset?
1959
+ # SEQUEL5: Remove as joined model datasets are now allowed
1905
1960
  qualified_pk_hash
1906
1961
  else
1907
1962
  pk_hash
@@ -2143,7 +2198,7 @@ module Sequel
2143
2198
  sh = {:server=>this_server}
2144
2199
  uacr = use_after_commit_rollback
2145
2200
  if uacr.nil? ? (method(:after_rollback).owner != InstanceMethods) : uacr
2146
- Sequel::Deprecation.deprecate("Model#after_rollback", "Instead, call db.after_rollback in Model#before_save.")
2201
+ Sequel::Deprecation.deprecate("Model#after_rollback", "Instead, call db.after_rollback in Model#before_save")
2147
2202
  db.after_rollback(sh){after_rollback}
2148
2203
  end
2149
2204
  pk = nil
@@ -2210,7 +2265,7 @@ module Sequel
2210
2265
  raise_hook_failure(:around_save) unless called_save
2211
2266
  _after_save(pk) # SEQUEL5: Remove
2212
2267
  if uacr.nil? ? (method(:after_commit).owner != InstanceMethods) : uacr
2213
- Sequel::Deprecation.deprecate("Model#after_commit", "Instead, call db.after_commit in Model#after_save.")
2268
+ Sequel::Deprecation.deprecate("Model#after_commit", "Instead, call db.after_commit in Model#after_save")
2214
2269
  db.after_commit(sh){after_commit}
2215
2270
  end
2216
2271
  self
@@ -2443,7 +2498,7 @@ module Sequel
2443
2498
  if type.is_a?(Array)
2444
2499
  type.map{|x| "#{x}="}
2445
2500
  else
2446
- meths = methods.collect(&:to_s).grep(SETTER_METHOD_REGEXP) - RESTRICTED_SETTER_METHODS
2501
+ meths = methods.map(&:to_s).select{|l| l.end_with?('=')} - RESTRICTED_SETTER_METHODS
2447
2502
  meths -= Array(primary_key).map{|x| "#{x}="} if type != :all && primary_key && model.restrict_primary_key?
2448
2503
  meths
2449
2504
  end
@@ -2607,15 +2662,15 @@ module Sequel
2607
2662
  end
2608
2663
  end
2609
2664
 
2610
- # This allows you to call +to_hash+ without any arguments, which will
2665
+ # This allows you to call +as_hash+ without any arguments, which will
2611
2666
  # result in a hash with the primary key value being the key and the
2612
2667
  # model object being the value.
2613
2668
  #
2614
- # Artist.dataset.to_hash # SELECT * FROM artists
2669
+ # Artist.dataset.as_hash # SELECT * FROM artists
2615
2670
  # # => {1=>#<Artist {:id=>1, ...}>,
2616
2671
  # # 2=>#<Artist {:id=>2, ...}>,
2617
2672
  # # ...}
2618
- def to_hash(key_column=nil, value_column=nil, opts=OPTS)
2673
+ def as_hash(key_column=nil, value_column=nil, opts=OPTS)
2619
2674
  if key_column
2620
2675
  super
2621
2676
  else
@@ -2624,6 +2679,11 @@ module Sequel
2624
2679
  end
2625
2680
  end
2626
2681
 
2682
+ # Alias of as_hash for backwards compatibility.
2683
+ def to_hash(*a)
2684
+ as_hash(*a)
2685
+ end
2686
+
2627
2687
  # Return an array of all rows matching the given filter condition, also
2628
2688
  # yielding each row to the given block. Basically the same as where(cond).all(&block),
2629
2689
  # except it can be optimized to not create an intermediate dataset.
@@ -2727,6 +2787,10 @@ module Sequel
2727
2787
  where(cond).limit(1)
2728
2788
  end
2729
2789
  end
2790
+
2791
+ def non_sql_option?(key)
2792
+ super || key == :model
2793
+ end
2730
2794
  end
2731
2795
 
2732
2796
  extend ClassMethods