sequel 3.12.1 → 3.13.0

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