sequel 3.12.1 → 3.13.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 (150) hide show
  1. data/CHANGELOG +42 -0
  2. data/README.rdoc +137 -118
  3. data/Rakefile +21 -66
  4. data/doc/active_record.rdoc +9 -9
  5. data/doc/advanced_associations.rdoc +59 -188
  6. data/doc/association_basics.rdoc +15 -2
  7. data/doc/cheat_sheet.rdoc +38 -33
  8. data/doc/dataset_filtering.rdoc +16 -7
  9. data/doc/prepared_statements.rdoc +7 -7
  10. data/doc/querying.rdoc +5 -4
  11. data/doc/release_notes/3.13.0.txt +210 -0
  12. data/doc/sharding.rdoc +1 -1
  13. data/doc/sql.rdoc +5 -5
  14. data/doc/validations.rdoc +11 -11
  15. data/lib/sequel/adapters/ado.rb +1 -1
  16. data/lib/sequel/adapters/do.rb +3 -3
  17. data/lib/sequel/adapters/firebird.rb +3 -3
  18. data/lib/sequel/adapters/jdbc/h2.rb +39 -0
  19. data/lib/sequel/adapters/jdbc/mysql.rb +5 -0
  20. data/lib/sequel/adapters/jdbc/oracle.rb +3 -3
  21. data/lib/sequel/adapters/mysql.rb +7 -4
  22. data/lib/sequel/adapters/oracle.rb +3 -3
  23. data/lib/sequel/adapters/shared/mssql.rb +10 -1
  24. data/lib/sequel/adapters/shared/mysql.rb +63 -0
  25. data/lib/sequel/adapters/shared/postgres.rb +61 -3
  26. data/lib/sequel/adapters/sqlite.rb +105 -18
  27. data/lib/sequel/connection_pool.rb +31 -30
  28. data/lib/sequel/core.rb +58 -58
  29. data/lib/sequel/core_sql.rb +52 -43
  30. data/lib/sequel/database/misc.rb +11 -0
  31. data/lib/sequel/database/query.rb +55 -17
  32. data/lib/sequel/dataset/actions.rb +2 -1
  33. data/lib/sequel/dataset/query.rb +2 -3
  34. data/lib/sequel/dataset/sql.rb +24 -11
  35. data/lib/sequel/extensions/schema_dumper.rb +1 -1
  36. data/lib/sequel/metaprogramming.rb +4 -0
  37. data/lib/sequel/model.rb +37 -19
  38. data/lib/sequel/model/associations.rb +33 -25
  39. data/lib/sequel/model/base.rb +2 -2
  40. data/lib/sequel/model/plugins.rb +7 -2
  41. data/lib/sequel/plugins/active_model.rb +1 -1
  42. data/lib/sequel/plugins/association_pks.rb +2 -2
  43. data/lib/sequel/plugins/association_proxies.rb +1 -1
  44. data/lib/sequel/plugins/boolean_readers.rb +2 -2
  45. data/lib/sequel/plugins/class_table_inheritance.rb +10 -2
  46. data/lib/sequel/plugins/identity_map.rb +3 -3
  47. data/lib/sequel/plugins/instance_hooks.rb +1 -1
  48. data/lib/sequel/plugins/json_serializer.rb +212 -0
  49. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  50. data/lib/sequel/plugins/list.rb +174 -0
  51. data/lib/sequel/plugins/many_through_many.rb +2 -2
  52. data/lib/sequel/plugins/rcte_tree.rb +6 -7
  53. data/lib/sequel/plugins/tree.rb +118 -0
  54. data/lib/sequel/plugins/xml_serializer.rb +321 -0
  55. data/lib/sequel/sql.rb +315 -206
  56. data/lib/sequel/timezones.rb +40 -17
  57. data/lib/sequel/version.rb +8 -2
  58. data/spec/adapters/firebird_spec.rb +2 -2
  59. data/spec/adapters/informix_spec.rb +1 -1
  60. data/spec/adapters/mssql_spec.rb +2 -2
  61. data/spec/adapters/mysql_spec.rb +2 -2
  62. data/spec/adapters/oracle_spec.rb +1 -1
  63. data/spec/adapters/postgres_spec.rb +36 -6
  64. data/spec/adapters/spec_helper.rb +2 -2
  65. data/spec/adapters/sqlite_spec.rb +1 -1
  66. data/spec/core/connection_pool_spec.rb +3 -3
  67. data/spec/core/core_sql_spec.rb +31 -13
  68. data/spec/core/database_spec.rb +39 -2
  69. data/spec/core/dataset_spec.rb +24 -12
  70. data/spec/core/expression_filters_spec.rb +5 -1
  71. data/spec/core/object_graph_spec.rb +1 -1
  72. data/spec/core/schema_generator_spec.rb +1 -1
  73. data/spec/core/schema_spec.rb +1 -1
  74. data/spec/core/spec_helper.rb +1 -1
  75. data/spec/core/version_spec.rb +1 -1
  76. data/spec/extensions/active_model_spec.rb +82 -67
  77. data/spec/extensions/association_dependencies_spec.rb +1 -1
  78. data/spec/extensions/association_pks_spec.rb +1 -1
  79. data/spec/extensions/association_proxies_spec.rb +1 -1
  80. data/spec/extensions/blank_spec.rb +1 -1
  81. data/spec/extensions/boolean_readers_spec.rb +1 -1
  82. data/spec/extensions/caching_spec.rb +1 -1
  83. data/spec/extensions/class_table_inheritance_spec.rb +3 -2
  84. data/spec/extensions/composition_spec.rb +2 -5
  85. data/spec/extensions/force_encoding_spec.rb +3 -1
  86. data/spec/extensions/hook_class_methods_spec.rb +1 -1
  87. data/spec/extensions/identity_map_spec.rb +1 -1
  88. data/spec/extensions/inflector_spec.rb +1 -1
  89. data/spec/extensions/instance_filters_spec.rb +1 -1
  90. data/spec/extensions/instance_hooks_spec.rb +1 -1
  91. data/spec/extensions/json_serializer_spec.rb +154 -0
  92. data/spec/extensions/lazy_attributes_spec.rb +1 -2
  93. data/spec/extensions/list_spec.rb +251 -0
  94. data/spec/extensions/looser_typecasting_spec.rb +1 -1
  95. data/spec/extensions/many_through_many_spec.rb +3 -3
  96. data/spec/extensions/migration_spec.rb +1 -1
  97. data/spec/extensions/named_timezones_spec.rb +5 -6
  98. data/spec/extensions/nested_attributes_spec.rb +1 -1
  99. data/spec/extensions/optimistic_locking_spec.rb +1 -1
  100. data/spec/extensions/pagination_spec.rb +1 -1
  101. data/spec/extensions/pretty_table_spec.rb +1 -1
  102. data/spec/extensions/query_spec.rb +1 -1
  103. data/spec/extensions/rcte_tree_spec.rb +1 -1
  104. data/spec/extensions/schema_dumper_spec.rb +3 -2
  105. data/spec/extensions/schema_spec.rb +1 -1
  106. data/spec/extensions/serialization_spec.rb +6 -2
  107. data/spec/extensions/sharding_spec.rb +1 -1
  108. data/spec/extensions/single_table_inheritance_spec.rb +1 -1
  109. data/spec/extensions/skip_create_refresh_spec.rb +1 -1
  110. data/spec/extensions/spec_helper.rb +7 -3
  111. data/spec/extensions/sql_expr_spec.rb +1 -1
  112. data/spec/extensions/string_date_time_spec.rb +1 -1
  113. data/spec/extensions/string_stripper_spec.rb +1 -1
  114. data/spec/extensions/subclasses_spec.rb +1 -1
  115. data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
  116. data/spec/extensions/thread_local_timezones_spec.rb +1 -1
  117. data/spec/extensions/timestamps_spec.rb +1 -1
  118. data/spec/extensions/touch_spec.rb +1 -1
  119. data/spec/extensions/tree_spec.rb +119 -0
  120. data/spec/extensions/typecast_on_load_spec.rb +1 -1
  121. data/spec/extensions/update_primary_key_spec.rb +1 -1
  122. data/spec/extensions/validation_class_methods_spec.rb +1 -1
  123. data/spec/extensions/validation_helpers_spec.rb +1 -1
  124. data/spec/extensions/xml_serializer_spec.rb +142 -0
  125. data/spec/integration/associations_test.rb +1 -1
  126. data/spec/integration/database_test.rb +1 -1
  127. data/spec/integration/dataset_test.rb +29 -14
  128. data/spec/integration/eager_loader_test.rb +1 -1
  129. data/spec/integration/migrator_test.rb +1 -1
  130. data/spec/integration/model_test.rb +1 -1
  131. data/spec/integration/plugin_test.rb +316 -1
  132. data/spec/integration/prepared_statement_test.rb +1 -1
  133. data/spec/integration/schema_test.rb +8 -8
  134. data/spec/integration/spec_helper.rb +1 -1
  135. data/spec/integration/timezone_test.rb +1 -1
  136. data/spec/integration/transaction_test.rb +35 -20
  137. data/spec/integration/type_test.rb +1 -1
  138. data/spec/model/association_reflection_spec.rb +1 -1
  139. data/spec/model/associations_spec.rb +49 -34
  140. data/spec/model/base_spec.rb +1 -1
  141. data/spec/model/dataset_methods_spec.rb +4 -4
  142. data/spec/model/eager_loading_spec.rb +1 -1
  143. data/spec/model/hooks_spec.rb +1 -1
  144. data/spec/model/inflector_spec.rb +1 -1
  145. data/spec/model/model_spec.rb +7 -1
  146. data/spec/model/plugins_spec.rb +1 -1
  147. data/spec/model/record_spec.rb +1 -3
  148. data/spec/model/spec_helper.rb +2 -2
  149. data/spec/model/validations_spec.rb +1 -1
  150. metadata +29 -5
@@ -118,7 +118,7 @@ END_MIG
118
118
  # database type is not recognized, return it as a String type.
119
119
  def column_schema_to_ruby_type(schema)
120
120
  case t = schema[:db_type].downcase
121
- when /\A(?:medium|small)?int(?:eger)?(?:\((?:\d+)\))?\z/o
121
+ when /\A(?:medium|small)?int(?:eger)?(?:\((?:\d+)\))?(?: unsigned)?\z/o
122
122
  {:type=>Integer}
123
123
  when /\Atinyint(?:\((\d+)\))?\z/o
124
124
  {:type =>schema[:type] == :boolean ? TrueClass : Integer}
@@ -2,6 +2,10 @@ module Sequel
2
2
  # Contains meta_def method for adding methods to objects via blocks, used by some of Sequel's classes and objects.
3
3
  module Metaprogramming
4
4
  # Define a method with the given name and block body on the receiver.
5
+ #
6
+ # ds = DB[:items]
7
+ # ds.meta_def(:x){42}
8
+ # ds.x # => 42
5
9
  def meta_def(name, &block)
6
10
  (class << self; self end).send(:define_method, name, &block)
7
11
  end
data/lib/sequel/model.rb CHANGED
@@ -2,20 +2,38 @@ require 'sequel/core'
2
2
 
3
3
  module Sequel
4
4
  # Lets you create a Model subclass with its dataset already set.
5
- # source can be an existing dataset or a symbol (in which case
6
- # it will create a dataset using the default database with
7
- # the given symbol as the table name).
5
+ # +source+ should be an instance of one of the following classes:
8
6
  #
9
- # The purpose of this method is to set the dataset automatically
7
+ # Database :: Sets the database for this model to +source+.
8
+ # Generally only useful when subclassing directly
9
+ # from the returned class, where the name of the
10
+ # subclass sets the table name (which is combined
11
+ # with the +Database+ in +source+ to create the
12
+ # dataset to use)
13
+ # Dataset :: Sets the dataset for this model to +source+.
14
+ # Symbol :: Sets the table name for this model to +source+. The
15
+ # class will use the default database for model
16
+ # classes in order to create the dataset.
17
+ #
18
+ # The purpose of this method is to set the dataset/database automatically
10
19
  # for a model class, if the table name doesn't match the implicit
11
20
  # name. This is neater than using set_dataset inside the class,
12
- # doesn't require a bogus query for the schema, and allows
13
- # it to work correctly in a system that uses code reloading.
21
+ # doesn't require a bogus query for the schema.
14
22
  #
15
- # Example:
23
+ # # Using a symbol
16
24
  # class Comment < Sequel::Model(:something)
17
25
  # table_name # => :something
18
26
  # end
27
+ #
28
+ # # Using a dataset
29
+ # class Comment < Sequel::Model(DB1[:something])
30
+ # dataset # => DB1[:something]
31
+ # end
32
+ #
33
+ # # Using a database
34
+ # class Comment < Sequel::Model(DB1)
35
+ # dataset # => DB1[:comments]
36
+ # end
19
37
  def self.Model(source)
20
38
  Model::ANONYMOUS_MODEL_CLASSES[source] ||= if source.is_a?(Database)
21
39
  c = Class.new(Model)
@@ -26,20 +44,20 @@ module Sequel
26
44
  end
27
45
  end
28
46
 
29
- # Sequel::Model is an object relational mapper built on top of Sequel core. Each
47
+ # <tt>Sequel::Model</tt> is an object relational mapper built on top of Sequel core. Each
30
48
  # model class is backed by a dataset instance, and many dataset methods can be
31
49
  # called directly on the class. Model datasets return rows as model instances,
32
50
  # which have fairly standard ORM instance behavior.
33
51
  #
34
- # Sequel::Model is built completely out of plugins, the only method not part of a
52
+ # <tt>Sequel::Model</tt> is built completely out of plugins, the only method not part of a
35
53
  # plugin is the plugin method itself. Plugins can override any class, instance, or
36
54
  # dataset method defined by a previous plugin and call super to get the default
37
55
  # behavior.
38
56
  #
39
- # You can set the SEQUEL_NO_ASSOCIATIONS constant or environment variable to
57
+ # You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
40
58
  # make Sequel not load the associations plugin by default.
41
59
  class Model
42
- # Map that stores model classes created with Sequel::Model(), to allow the reopening
60
+ # Map that stores model classes created with <tt>Sequel::Model()</tt>, to allow the reopening
43
61
  # of classes when dealing with code reloading.
44
62
  ANONYMOUS_MODEL_CLASSES = {}
45
63
 
@@ -54,24 +72,24 @@ module Sequel
54
72
  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]
55
73
 
56
74
  # Hooks that are called before an action. Can return false to not do the action. When
57
- # overriding these, it is recommended to call super as the last line of your method,
75
+ # overriding these, it is recommended to call +super+ as the last line of your method,
58
76
  # so later hooks are called before earlier hooks.
59
77
  BEFORE_HOOKS = [:before_create, :before_update, :before_save, :before_destroy, :before_validation]
60
78
 
61
79
  # Hooks that are called after an action. When overriding these, it is recommended to call
62
- # super on the first line of your method, so later hooks are called before earlier hooks.
80
+ # +super+ on the first line of your method, so later hooks are called after earlier hooks.
63
81
  AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy, :after_validation]
64
82
 
65
83
  # Empty instance methods to create that the user can override to get hook/callback behavior.
66
84
  # Just like any other method defined by Sequel, if you override one of these, you should
67
- # call super to get the default behavior (while empty by default, they can also be defined
85
+ # call +super+ to get the default behavior (while empty by default, they can also be defined
68
86
  # by plugins). See the {"Model Hooks" guide}[link:files/doc/model_hooks_rdoc.html] for
69
87
  # more detail on hooks.
70
88
  HOOKS = BEFORE_HOOKS + AFTER_HOOKS
71
89
 
72
- # Class instance variables that are inherited in subclasses. If the value is :dup, dup is called
90
+ # Class instance variables that are inherited in subclasses. If the value is <tt>:dup</tt>, dup is called
73
91
  # on the superclass's instance variable when creating the instance variable in the subclass.
74
- # If the value is nil, the superclass's instance variable is used directly in the subclass.
92
+ # If the value is +nil+, the superclass's instance variable is used directly in the subclass.
75
93
  INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@dataset_methods=>:dup,
76
94
  :@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
77
95
  :@raise_on_save_failure=>nil, :@require_modification=>nil,
@@ -80,8 +98,8 @@ module Sequel
80
98
  :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
81
99
  :@raise_on_typecast_failure=>nil, :@plugins=>:dup}
82
100
 
83
- # Regexp that determines if a method name is normal in the sense that
84
- # it could be called directly in ruby code without using send. Used to
101
+ # Regular expression that determines if a method name is normal in the sense that
102
+ # it could be used literally in ruby code without using send. Used to
85
103
  # avoid problems when using eval with a string to define methods.
86
104
  NORMAL_METHOD_NAME_REGEXP = /\A[A-Za-z_][A-Za-z0-9_]*\z/
87
105
 
@@ -116,7 +134,7 @@ module Sequel
116
134
  end
117
135
 
118
136
  # The setter methods (methods ending with =) that are never allowed
119
- # to be called automatically via set/update/new/etc..
137
+ # to be called automatically via +set+/+update+/+new+/etc..
120
138
  RESTRICTED_SETTER_METHODS = instance_methods.map{|x| x.to_s}.grep(SETTER_METHOD_REGEXP)
121
139
  end
122
140
  end
@@ -550,6 +550,8 @@ module Sequel
550
550
  # columns in the associated table.
551
551
  # - :limit - Limit the number of records to the provided value. Use
552
552
  # an array with two arguments for the value to specify a limit and an offset.
553
+ # - :methods_module - The module that methods the association creates will be placed into. Defaults
554
+ # to the module containing the model's columns.
553
555
  # - :order - the column(s) by which to order the association dataset. Can be a
554
556
  # singular column or an array.
555
557
  # - :order_eager_graph - Whether to add the order to the dataset's order when graphing
@@ -710,37 +712,43 @@ module Sequel
710
712
 
711
713
  private
712
714
 
715
+ # The module to use for the association's methods. Defaults to
716
+ # the overridable_methods_module.
717
+ def association_module(opts={})
718
+ opts.fetch(:methods_module, overridable_methods_module)
719
+ end
720
+
713
721
  # Add a method to the module included in the class, so the method
714
722
  # can be easily overridden in the class itself while allowing for
715
723
  # super to be called.
716
- def association_module_def(name, &block)
717
- overridable_methods_module.module_eval{define_method(name, &block)}
724
+ def association_module_def(name, opts={}, &block)
725
+ association_module(opts).module_eval{define_method(name, &block)}
718
726
  end
719
727
 
720
728
  # Add a private method to the module included in the class.
721
- def association_module_private_def(name, &block)
722
- association_module_def(name, &block)
723
- overridable_methods_module.send(:private, name)
729
+ def association_module_private_def(name, opts={}, &block)
730
+ association_module_def(name, opts, &block)
731
+ association_module(opts).send(:private, name)
724
732
  end
725
733
 
726
734
  # Add the add_ instance method
727
735
  def def_add_method(opts)
728
- association_module_def(opts.add_method){|o,*args| add_associated_object(opts, o, *args)}
736
+ association_module_def(opts.add_method, opts){|o,*args| add_associated_object(opts, o, *args)}
729
737
  end
730
738
 
731
739
  # Adds methods related to the association's dataset to the module included in the class.
732
740
  def def_association_dataset_methods(opts)
733
741
  # If a block is given, define a helper method for it, because it takes
734
742
  # an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
735
- association_module_private_def(opts.dataset_helper_method, &opts[:block]) if opts[:block]
736
- association_module_private_def(opts._dataset_method, &opts[:dataset])
737
- association_module_def(opts.dataset_method){_dataset(opts)}
743
+ association_module_private_def(opts.dataset_helper_method, opts, &opts[:block]) if opts[:block]
744
+ association_module_private_def(opts._dataset_method, opts, &opts[:dataset])
745
+ association_module_def(opts.dataset_method, opts){_dataset(opts)}
738
746
  def_association_method(opts)
739
747
  end
740
748
 
741
749
  # Adds method for retrieving the associated objects to the module included in the class.
742
750
  def def_association_method(opts)
743
- association_module_def(opts.association_method){|*reload| load_associated_objects(opts, reload[0])}
751
+ association_module_def(opts.association_method, opts){|*reload| load_associated_objects(opts, reload[0])}
744
752
  end
745
753
 
746
754
  # Adds many_to_many association instance methods
@@ -770,7 +778,7 @@ module Sequel
770
778
  h = eo[:key_hash][left_pk]
771
779
  eo[:rows].each{|object| object.associations[name] = []}
772
780
  r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
773
- l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, SQL::SQLArray.new(h.keys)]] : [[left, h.keys]]
781
+ l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
774
782
  model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), eo[:associations], eo).all do |assoc_record|
775
783
  hash_key = if uses_lcks
776
784
  left_key_alias.map{|k| assoc_record.values.delete(k)}
@@ -801,16 +809,16 @@ module Sequel
801
809
 
802
810
  return if opts[:read_only]
803
811
 
804
- association_module_private_def(opts._add_method) do |o|
812
+ association_module_private_def(opts._add_method, opts) do |o|
805
813
  h = {}
806
814
  lcks.zip(lcpks).each{|k, pk| h[k] = send(pk)}
807
815
  rcks.zip(opts.right_primary_keys).each{|k, pk| h[k] = o.send(pk)}
808
816
  _join_table_dataset(opts).insert(h)
809
817
  end
810
- association_module_private_def(opts._remove_method) do |o|
818
+ association_module_private_def(opts._remove_method, opts) do |o|
811
819
  _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_keys.map{|k| o.send(k)})).delete
812
820
  end
813
- association_module_private_def(opts._remove_all_method) do
821
+ association_module_private_def(opts._remove_all_method, opts) do
814
822
  _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)})).delete
815
823
  end
816
824
 
@@ -841,7 +849,7 @@ module Sequel
841
849
  # Skip eager loading if no objects have a foreign key for this association
842
850
  unless keys.empty?
843
851
  klass = opts.associated_class
844
- model.eager_loading_dataset(opts, klass.filter(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, eo[:associations], eo).all do |assoc_record|
852
+ model.eager_loading_dataset(opts, klass.filter(uses_cks ? {opts.primary_keys.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>keys} : {SQL::QualifiedIdentifier.new(klass.table_name, opts.primary_key)=>keys}), opts.select, eo[:associations], eo).all do |assoc_record|
845
853
  hash_key = uses_cks ? opts.primary_keys.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key)
846
854
  next unless objects = h[hash_key]
847
855
  objects.each{|object| object.associations[name] = assoc_record}
@@ -863,8 +871,8 @@ module Sequel
863
871
 
864
872
  return if opts[:read_only]
865
873
 
866
- association_module_private_def(opts._setter_method){|o| cks.zip(opts.primary_keys).each{|k, pk| send(:"#{k}=", (o.send(pk) if o))}}
867
- association_module_def(opts.setter_method){|o| set_associated_object(opts, o)}
874
+ association_module_private_def(opts._setter_method, opts){|o| cks.zip(opts.primary_keys).each{|k, pk| send(:"#{k}=", (o.send(pk) if o))}}
875
+ association_module_def(opts.setter_method, opts){|o| set_associated_object(opts, o)}
868
876
  end
869
877
 
870
878
  # Adds one_to_many association instance methods
@@ -891,7 +899,7 @@ module Sequel
891
899
  end
892
900
  reciprocal = opts.reciprocal
893
901
  klass = opts.associated_class
894
- model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>SQL::SQLArray.new(h.keys)} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, eo[:associations], eo).all do |assoc_record|
902
+ model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>h.keys} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, eo[:associations], eo).all do |assoc_record|
895
903
  hash_key = uses_cks ? cks.map{|k| assoc_record.send(k)} : assoc_record.send(key)
896
904
  next unless objects = h[hash_key]
897
905
  if one_to_one
@@ -931,7 +939,7 @@ module Sequel
931
939
  validate = opts[:validate]
932
940
 
933
941
  if one_to_one
934
- association_module_private_def(opts._setter_method) do |o|
942
+ association_module_private_def(opts._setter_method, opts) do |o|
935
943
  up_ds = _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)})))
936
944
  if o
937
945
  up_ds = up_ds.exclude(o.pk_hash)
@@ -942,19 +950,19 @@ module Sequel
942
950
  o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save") if o
943
951
  end
944
952
  end
945
- association_module_def(opts.setter_method){|o| set_one_to_one_associated_object(opts, o)}
953
+ association_module_def(opts.setter_method, opts){|o| set_one_to_one_associated_object(opts, o)}
946
954
  else
947
- association_module_private_def(opts._add_method) do |o|
955
+ association_module_private_def(opts._add_method, opts) do |o|
948
956
  cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
949
957
  o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
950
958
  end
951
959
  def_add_method(opts)
952
960
 
953
- association_module_private_def(opts._remove_method) do |o|
961
+ association_module_private_def(opts._remove_method, opts) do |o|
954
962
  cks.each{|k| o.send(:"#{k}=", nil)}
955
963
  o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
956
964
  end
957
- association_module_private_def(opts._remove_all_method) do
965
+ association_module_private_def(opts._remove_all_method, opts) do
958
966
  _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)}))).update(ck_nil_hash)
959
967
  end
960
968
  def_remove_methods(opts)
@@ -969,8 +977,8 @@ module Sequel
969
977
 
970
978
  # Add the remove_ and remove_all instance methods
971
979
  def def_remove_methods(opts)
972
- association_module_def(opts.remove_method){|o,*args| remove_associated_object(opts, o, *args)}
973
- association_module_def(opts.remove_all_method){|*args| remove_all_associated_objects(opts, *args)}
980
+ association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
981
+ association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
974
982
  end
975
983
  end
976
984
 
@@ -175,8 +175,8 @@ module Sequel
175
175
 
176
176
  # Like find but invokes create with given conditions when record does not
177
177
  # exist.
178
- def find_or_create(cond)
179
- find(cond) || create(cond)
178
+ def find_or_create(cond, &block)
179
+ find(cond) || create(cond, &block)
180
180
  end
181
181
 
182
182
  # If possible, set the dataset for the model subclass as soon as it
@@ -59,8 +59,13 @@ module Sequel
59
59
  Sequel::Plugins.const_get(module_name) == Sequel.const_get(module_name))
60
60
  begin
61
61
  Sequel.tsk_require "sequel/plugins/#{plugin}"
62
- rescue LoadError
63
- Sequel.tsk_require "sequel_#{plugin}"
62
+ rescue LoadError => e
63
+ begin
64
+ Sequel.tsk_require "sequel_#{plugin}"
65
+ rescue LoadError => e2
66
+ e.message << "; #{e2.message}"
67
+ raise e
68
+ end
64
69
  end
65
70
  end
66
71
  Sequel::Plugins.const_get(module_name)
@@ -1,7 +1,7 @@
1
1
  require 'active_model'
2
2
  module Sequel
3
3
  module Plugins
4
- # The ActiveModel plugin makes Sequel::Model objects the
4
+ # The ActiveModel plugin makes Sequel::Model objects
5
5
  # pass the ActiveModel::Lint tests, which should
6
6
  # hopefully mean full ActiveModel compliance. This should
7
7
  # allow the full support of Sequel::Model objects in Rails 3.
@@ -39,13 +39,13 @@ module Sequel
39
39
 
40
40
  # Define a association_pks method using the block for the association reflection
41
41
  def def_association_pks_getter(opts, &block)
42
- association_module_def(:"#{singularize(opts[:name])}_pks", &block)
42
+ association_module_def(:"#{singularize(opts[:name])}_pks", opts, &block)
43
43
  end
44
44
 
45
45
  # Define a association_pks= method using the block for the association reflection,
46
46
  # if the association is not read only.
47
47
  def def_association_pks_setter(opts, &block)
48
- association_module_def(:"#{singularize(opts[:name])}_pks=", &block) unless opts[:read_only]
48
+ association_module_def(:"#{singularize(opts[:name])}_pks=", opts, &block) unless opts[:read_only]
49
49
  end
50
50
 
51
51
  # Add a getter that checks the join table for matching records and
@@ -41,7 +41,7 @@ module Sequel
41
41
  # Changes the association method to return a proxy instead of the associated objects
42
42
  # directly.
43
43
  def def_association_method(opts)
44
- opts.returns_array? ? association_module_def(opts.association_method){|*r| AssociationProxy.new(self, opts, r[0])} : super
44
+ opts.returns_array? ? association_module_def(opts.association_method, opts){|*r| AssociationProxy.new(self, opts, r[0])} : super
45
45
  end
46
46
  end
47
47
  end
@@ -1,7 +1,7 @@
1
1
  module Sequel
2
2
  module Plugins
3
- # The BooleaReaders plugin allows for the creation of attribute? methods
4
- # for boolean columns, which provide a nicer API. By default, the accessors
3
+ # The BooleanReaders plugin allows for the creation of attribute? methods
4
+ # for boolean columns, which provides a nicer API. By default, the accessors
5
5
  # are created for all columns of type :boolean. However, you can provide a
6
6
  # block to the plugin to change the criteria used to determine if a
7
7
  # column is boolean. The block is yielded with the column symbol for each
@@ -89,7 +89,11 @@ module Sequel
89
89
  @cti_tables = [table_name]
90
90
  @cti_columns = {table_name=>columns}
91
91
  @cti_table_map = opts[:table_map] || {}
92
- dataset.row_proc = lambda{|r| (m.call(r[key]) rescue model).load(r)}
92
+ dataset.row_proc = if key
93
+ lambda{|r| (m.call(r[key]) rescue model).load(r)}
94
+ else
95
+ lambda{|r| model.load(r)}
96
+ end
93
97
  end
94
98
  end
95
99
 
@@ -147,7 +151,11 @@ module Sequel
147
151
  super
148
152
  subclass.instance_eval do
149
153
  m = method(:constantize)
150
- dataset.row_proc = lambda{|r| (m.call(r[ck]) rescue subclass).load(r)}
154
+ dataset.row_proc = if cti_key
155
+ lambda{|r| (m.call(r[ck]) rescue subclass).load(r)}
156
+ else
157
+ lambda{|r| subclass.load(r)}
158
+ end
151
159
  (columns - [cbm.primary_key]).each{|a| define_lazy_attribute_getter(a)}
152
160
  cti_tables.reverse.each do |table|
153
161
  db.schema(table).each{|k,v| db_schema[k] = v}
@@ -10,17 +10,17 @@ module Sequel
10
10
  # Album.filter{(id > 0) & (id < 2)}.first.object_id == Album.first(:id=>1).object_id
11
11
  # end
12
12
  #
13
- # In additional to providing a 1-1 correspondence, the identity_map plugin
13
+ # In addition to providing a 1-1 correspondence, the identity_map plugin
14
14
  # also provides a cached looked up of records in two cases:
15
15
  # * Model.[] (e.g. Album[1])
16
16
  # * Model.many_to_one accessor methods (e.g. album.artist)
17
17
  #
18
- # If the object you are looking up using one of those two methods is already
18
+ # If the object you are looking up, using one of those two methods, is already
19
19
  # in the identity map, the record is returned without a database query being
20
20
  # issued.
21
21
  #
22
22
  # Identity maps are thread-local and only presist for the duration of the block,
23
- # so they should be should only be considered as a possible performance enhancer.
23
+ # so they should only be considered as a possible performance enhancer.
24
24
  #
25
25
  # Usage:
26
26
  #
@@ -2,7 +2,7 @@ module Sequel
2
2
  module Plugins
3
3
  # The instance_hooks plugin allows you to add hooks to specific instances,
4
4
  # by passing a block to a _hook method (e.g. before_save_hook{do_something}).
5
- # The block executed when the hook is called (e.g. before_save).
5
+ # The block is executed when the hook is called (e.g. before_save).
6
6
  #
7
7
  # All of the standard hooks are supported, except for after_initialize.
8
8
  # Instance level before hooks are executed in reverse order of addition before
@@ -0,0 +1,212 @@
1
+ module Sequel
2
+ tsk_require 'json'
3
+
4
+ module Plugins
5
+ # The json_serializer plugin handles serializing entire Sequel::Model
6
+ # objects to JSON, as well as support for deserializing JSON directly
7
+ # into Sequel::Model objects. It requires the json library, and can
8
+ # work with either the pure ruby version or the C extension.
9
+ #
10
+ # Basic Example:
11
+ #
12
+ # album = Album[1]
13
+ # album.to_json
14
+ # # => '{"json_class"=>"Album","id"=>1,"name"=>"RF","artist_id"=>2}'
15
+ #
16
+ # In addition, you can provide options to control the JSON output:
17
+ #
18
+ # album.to_json(:only=>:name)
19
+ # album.to_json(:except=>[:id, :artist_id])
20
+ # # => '{"json_class"="Album","name"=>"RF"}'
21
+ #
22
+ # album.to_json(:include=>:artist)
23
+ # # => '{"json_class":"Album","id":1,"name":"RF","artist_id":2,
24
+ # "artist":{"json_class":"Artist","id":2,"name":"YJM"}}'
25
+ #
26
+ # You can use a hash value with <tt>:include</tt> to pass options
27
+ # to associations:
28
+ #
29
+ # album.to_json(:include=>{:artist=>{:only=>:name}})
30
+ # # => '{"json_class":"Album","id":1,"name":"RF","artist_id":2,
31
+ # "artist":{"json_class":"Artist","name":"YJM"}}'
32
+ #
33
+ # You can specify the <tt>:root</tt> option to nest the JSON under the
34
+ # name of the model:
35
+ #
36
+ # album.to_json(:root => true)
37
+ # # => '{"album":{"id":1,"name":"RF","artist_id":2}}'
38
+ #
39
+ # In addition to creating JSON, this plugin also enables Sequel::Model
40
+ # objects to be automatically created when JSON is parsed:
41
+ #
42
+ # json = album.to_json
43
+ # album = JSON.parse(json)
44
+ #
45
+ # In addition, you can update existing model objects directly from JSON
46
+ # using +from_json+:
47
+ #
48
+ # album.from_json(json)
49
+ #
50
+ # This works by parsing the JSON (which should return a hash), and then
51
+ # calling +set+ with the returned hash.
52
+ #
53
+ # Additionally, +to_json+ also exists as a class and dataset method, both
54
+ # of which return all objects in the dataset:
55
+ #
56
+ # Album.to_json
57
+ # Album.filter(:artist_id=>1).to_json(:include=>:tags)
58
+ #
59
+ # Usage:
60
+ #
61
+ # # Add JSON output capability to all model subclass instances (called before loading subclasses)
62
+ # Sequel::Model.plugin :json_serializer
63
+ #
64
+ # # Add JSON output capability to Album class instances
65
+ # Album.plugin :json_serializer
66
+ module JsonSerializer
67
+ # Set up the column readers to do deserialization and the column writers
68
+ # to save the value in deserialized_values.
69
+ def self.configure(model, opts={})
70
+ model.instance_eval do
71
+ @json_serializer_opts = (@json_serializer_opts || {}).merge(opts)
72
+ end
73
+ end
74
+
75
+ # Helper class used for making sure that cascading options
76
+ # for model associations works correctly. Cascaded options
77
+ # work by creating instances of this class, which take a
78
+ # literal JSON string and have +to_json+ return it.
79
+ class Literal
80
+ # Store the literal JSON to use
81
+ def initialize(json)
82
+ @json = json
83
+ end
84
+
85
+ # Return the literal JSON to use
86
+ def to_json(*a)
87
+ @json
88
+ end
89
+ end
90
+
91
+ module ClassMethods
92
+ # The default opts to use when serializing model objects to JSON.
93
+ attr_reader :json_serializer_opts
94
+
95
+ # Create a new model object from the hash provided by parsing
96
+ # JSON. Handles column values (stored in +values+), associations
97
+ # (stored in +associations+), and other values (by calling a
98
+ # setter method). If an entry in the hash is not a column or
99
+ # an association, and no setter method exists, raises an Error.
100
+ def json_create(hash)
101
+ obj = new
102
+ cols = columns.map{|x| x.to_s}
103
+ assocs = associations.map{|x| x.to_s}
104
+ meths = obj.send(:setter_methods, nil, nil)
105
+ hash.delete(JSON.create_id)
106
+ hash.each do |k, v|
107
+ if assocs.include?(k)
108
+ obj.associations[k.to_sym] = v
109
+ elsif cols.include?(k)
110
+ obj.values[k.to_sym] = v
111
+ elsif meths.include?("#{k}=")
112
+ obj.send("#{k}=", v)
113
+ else
114
+ raise Error, "Entry in JSON hash not an association or column and no setter method exists: #{k}"
115
+ end
116
+ end
117
+ obj
118
+ end
119
+
120
+ # Call the dataset +to_json+ method.
121
+ def to_json(*a)
122
+ dataset.to_json(*a)
123
+ end
124
+
125
+ # Copy the current model object's default json options into the subclass.
126
+ def inherited(subclass)
127
+ super
128
+ opts = {}
129
+ json_serializer_opts.each{|k, v| opts[k] = (v.is_a?(Array) || v.is_a?(Hash)) ? v.dup : v}
130
+ subclass.instance_variable_set(:@json_serializer_opts, opts)
131
+ end
132
+ end
133
+
134
+ module InstanceMethods
135
+ # Parse the provided JSON, which should return a hash,
136
+ # and call +set+ with that hash.
137
+ def from_json(json)
138
+ set(JSON.parse(json))
139
+ end
140
+
141
+ # Return a string in JSON format. Accepts the following
142
+ # options:
143
+ #
144
+ # :except :: Symbol or Array of Symbols of columns not
145
+ # to include in the JSON output.
146
+ # :include :: Symbol, Array of Symbols, or a Hash with
147
+ # Symbol keys and Hash values specifying
148
+ # associations or other non-column attributes
149
+ # to include in the JSON output. Using a nested
150
+ # hash, you can pass options to associations
151
+ # to affect the JSON used for associated objects.
152
+ # :naked :: Not to add the JSON.create_id key to the JSON
153
+ # output hash, so when the JSON is parsed, it
154
+ # will yield a hash instead of a model object.
155
+ # :only :: Symbol or Array of Symbols of columns to only
156
+ # include in the JSON output, ignoring all other
157
+ # columns.
158
+ # :root :: Qualify the JSON with the name of the object.
159
+ # Implies :naked since the object name is explicit.
160
+ def to_json(*a)
161
+ if opts = a.first.is_a?(Hash)
162
+ opts = model.json_serializer_opts.merge(a.first)
163
+ a = []
164
+ else
165
+ opts = model.json_serializer_opts
166
+ end
167
+ vals = values
168
+ cols = if only = opts[:only]
169
+ Array(only)
170
+ else
171
+ vals.keys - Array(opts[:except])
172
+ end
173
+ h = (JSON.create_id && !opts[:naked] && !opts[:root]) ? {JSON.create_id=>model.name} : {}
174
+ cols.each{|c| h[c.to_s] = vals[c]}
175
+ if inc = opts[:include]
176
+ if inc.is_a?(Hash)
177
+ inc.each do |k, v|
178
+ v = v.empty? ? [] : [v]
179
+ h[k.to_s] = case objs = send(k)
180
+ when Array
181
+ objs.map{|obj| Literal.new(obj.to_json(*v))}
182
+ else
183
+ Literal.new(objs.to_json(*v))
184
+ end
185
+ end
186
+ else
187
+ Array(inc).each{|c| h[c.to_s] = send(c)}
188
+ end
189
+ end
190
+ h = {model.send(:underscore, model.to_s) => h} if opts[:root]
191
+ h.to_json(*a)
192
+ end
193
+ end
194
+
195
+ module DatasetMethods
196
+ # Return a JSON string representing an array of all objects in
197
+ # this dataset. Takes the same options as the the instance
198
+ # method, and passes them to every instance.
199
+ def to_json(*a)
200
+ if opts = a.first.is_a?(Hash)
201
+ opts = model.json_serializer_opts.merge(a.first)
202
+ a = []
203
+ else
204
+ opts = model.json_serializer_opts
205
+ end
206
+ res = row_proc ? all.map{|obj| Literal.new(obj.to_json(opts))} : all
207
+ opts[:root] ? {model.send(:pluralize, model.send(:underscore, model.to_s)) => res}.to_json(*a) : res.to_json(*a)
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end