sequel 3.12.1 → 3.13.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +42 -0
- data/README.rdoc +137 -118
- data/Rakefile +21 -66
- data/doc/active_record.rdoc +9 -9
- data/doc/advanced_associations.rdoc +59 -188
- data/doc/association_basics.rdoc +15 -2
- data/doc/cheat_sheet.rdoc +38 -33
- data/doc/dataset_filtering.rdoc +16 -7
- data/doc/prepared_statements.rdoc +7 -7
- data/doc/querying.rdoc +5 -4
- data/doc/release_notes/3.13.0.txt +210 -0
- data/doc/sharding.rdoc +1 -1
- data/doc/sql.rdoc +5 -5
- data/doc/validations.rdoc +11 -11
- data/lib/sequel/adapters/ado.rb +1 -1
- data/lib/sequel/adapters/do.rb +3 -3
- data/lib/sequel/adapters/firebird.rb +3 -3
- data/lib/sequel/adapters/jdbc/h2.rb +39 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +5 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +3 -3
- data/lib/sequel/adapters/mysql.rb +7 -4
- data/lib/sequel/adapters/oracle.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +10 -1
- data/lib/sequel/adapters/shared/mysql.rb +63 -0
- data/lib/sequel/adapters/shared/postgres.rb +61 -3
- data/lib/sequel/adapters/sqlite.rb +105 -18
- data/lib/sequel/connection_pool.rb +31 -30
- data/lib/sequel/core.rb +58 -58
- data/lib/sequel/core_sql.rb +52 -43
- data/lib/sequel/database/misc.rb +11 -0
- data/lib/sequel/database/query.rb +55 -17
- data/lib/sequel/dataset/actions.rb +2 -1
- data/lib/sequel/dataset/query.rb +2 -3
- data/lib/sequel/dataset/sql.rb +24 -11
- data/lib/sequel/extensions/schema_dumper.rb +1 -1
- data/lib/sequel/metaprogramming.rb +4 -0
- data/lib/sequel/model.rb +37 -19
- data/lib/sequel/model/associations.rb +33 -25
- data/lib/sequel/model/base.rb +2 -2
- data/lib/sequel/model/plugins.rb +7 -2
- data/lib/sequel/plugins/active_model.rb +1 -1
- data/lib/sequel/plugins/association_pks.rb +2 -2
- data/lib/sequel/plugins/association_proxies.rb +1 -1
- data/lib/sequel/plugins/boolean_readers.rb +2 -2
- data/lib/sequel/plugins/class_table_inheritance.rb +10 -2
- data/lib/sequel/plugins/identity_map.rb +3 -3
- data/lib/sequel/plugins/instance_hooks.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +212 -0
- data/lib/sequel/plugins/lazy_attributes.rb +1 -1
- data/lib/sequel/plugins/list.rb +174 -0
- data/lib/sequel/plugins/many_through_many.rb +2 -2
- data/lib/sequel/plugins/rcte_tree.rb +6 -7
- data/lib/sequel/plugins/tree.rb +118 -0
- data/lib/sequel/plugins/xml_serializer.rb +321 -0
- data/lib/sequel/sql.rb +315 -206
- data/lib/sequel/timezones.rb +40 -17
- data/lib/sequel/version.rb +8 -2
- data/spec/adapters/firebird_spec.rb +2 -2
- data/spec/adapters/informix_spec.rb +1 -1
- data/spec/adapters/mssql_spec.rb +2 -2
- data/spec/adapters/mysql_spec.rb +2 -2
- data/spec/adapters/oracle_spec.rb +1 -1
- data/spec/adapters/postgres_spec.rb +36 -6
- data/spec/adapters/spec_helper.rb +2 -2
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +3 -3
- data/spec/core/core_sql_spec.rb +31 -13
- data/spec/core/database_spec.rb +39 -2
- data/spec/core/dataset_spec.rb +24 -12
- data/spec/core/expression_filters_spec.rb +5 -1
- data/spec/core/object_graph_spec.rb +1 -1
- data/spec/core/schema_generator_spec.rb +1 -1
- data/spec/core/schema_spec.rb +1 -1
- data/spec/core/spec_helper.rb +1 -1
- data/spec/core/version_spec.rb +1 -1
- data/spec/extensions/active_model_spec.rb +82 -67
- data/spec/extensions/association_dependencies_spec.rb +1 -1
- data/spec/extensions/association_pks_spec.rb +1 -1
- data/spec/extensions/association_proxies_spec.rb +1 -1
- data/spec/extensions/blank_spec.rb +1 -1
- data/spec/extensions/boolean_readers_spec.rb +1 -1
- data/spec/extensions/caching_spec.rb +1 -1
- data/spec/extensions/class_table_inheritance_spec.rb +3 -2
- data/spec/extensions/composition_spec.rb +2 -5
- data/spec/extensions/force_encoding_spec.rb +3 -1
- data/spec/extensions/hook_class_methods_spec.rb +1 -1
- data/spec/extensions/identity_map_spec.rb +1 -1
- data/spec/extensions/inflector_spec.rb +1 -1
- data/spec/extensions/instance_filters_spec.rb +1 -1
- data/spec/extensions/instance_hooks_spec.rb +1 -1
- data/spec/extensions/json_serializer_spec.rb +154 -0
- data/spec/extensions/lazy_attributes_spec.rb +1 -2
- data/spec/extensions/list_spec.rb +251 -0
- data/spec/extensions/looser_typecasting_spec.rb +1 -1
- data/spec/extensions/many_through_many_spec.rb +3 -3
- data/spec/extensions/migration_spec.rb +1 -1
- data/spec/extensions/named_timezones_spec.rb +5 -6
- data/spec/extensions/nested_attributes_spec.rb +1 -1
- data/spec/extensions/optimistic_locking_spec.rb +1 -1
- data/spec/extensions/pagination_spec.rb +1 -1
- data/spec/extensions/pretty_table_spec.rb +1 -1
- data/spec/extensions/query_spec.rb +1 -1
- data/spec/extensions/rcte_tree_spec.rb +1 -1
- data/spec/extensions/schema_dumper_spec.rb +3 -2
- data/spec/extensions/schema_spec.rb +1 -1
- data/spec/extensions/serialization_spec.rb +6 -2
- data/spec/extensions/sharding_spec.rb +1 -1
- data/spec/extensions/single_table_inheritance_spec.rb +1 -1
- data/spec/extensions/skip_create_refresh_spec.rb +1 -1
- data/spec/extensions/spec_helper.rb +7 -3
- data/spec/extensions/sql_expr_spec.rb +1 -1
- data/spec/extensions/string_date_time_spec.rb +1 -1
- data/spec/extensions/string_stripper_spec.rb +1 -1
- data/spec/extensions/subclasses_spec.rb +1 -1
- data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
- data/spec/extensions/thread_local_timezones_spec.rb +1 -1
- data/spec/extensions/timestamps_spec.rb +1 -1
- data/spec/extensions/touch_spec.rb +1 -1
- data/spec/extensions/tree_spec.rb +119 -0
- data/spec/extensions/typecast_on_load_spec.rb +1 -1
- data/spec/extensions/update_primary_key_spec.rb +1 -1
- data/spec/extensions/validation_class_methods_spec.rb +1 -1
- data/spec/extensions/validation_helpers_spec.rb +1 -1
- data/spec/extensions/xml_serializer_spec.rb +142 -0
- data/spec/integration/associations_test.rb +1 -1
- data/spec/integration/database_test.rb +1 -1
- data/spec/integration/dataset_test.rb +29 -14
- data/spec/integration/eager_loader_test.rb +1 -1
- data/spec/integration/migrator_test.rb +1 -1
- data/spec/integration/model_test.rb +1 -1
- data/spec/integration/plugin_test.rb +316 -1
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/schema_test.rb +8 -8
- data/spec/integration/spec_helper.rb +1 -1
- data/spec/integration/timezone_test.rb +1 -1
- data/spec/integration/transaction_test.rb +35 -20
- data/spec/integration/type_test.rb +1 -1
- data/spec/model/association_reflection_spec.rb +1 -1
- data/spec/model/associations_spec.rb +49 -34
- data/spec/model/base_spec.rb +1 -1
- data/spec/model/dataset_methods_spec.rb +4 -4
- data/spec/model/eager_loading_spec.rb +1 -1
- data/spec/model/hooks_spec.rb +1 -1
- data/spec/model/inflector_spec.rb +1 -1
- data/spec/model/model_spec.rb +7 -1
- data/spec/model/plugins_spec.rb +1 -1
- data/spec/model/record_spec.rb +1 -3
- data/spec/model/spec_helper.rb +2 -2
- data/spec/model/validations_spec.rb +1 -1
- 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
|
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
|
-
#
|
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
|
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
|
-
#
|
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()
|
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
|
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
|
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
|
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
|
-
#
|
84
|
-
# it could be
|
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
|
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
|
-
|
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
|
-
|
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)},
|
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)}=>
|
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)}=>
|
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
|
|
data/lib/sequel/model/base.rb
CHANGED
@@ -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
|
data/lib/sequel/model/plugins.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
4
|
-
# for boolean columns, which
|
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 =
|
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 =
|
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
|
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
|
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
|