sequel 5.83.1 → 5.84.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/shared/sqlite.rb +3 -1
  3. data/lib/sequel/database/schema_methods.rb +2 -0
  4. data/lib/sequel/extensions/pg_json_ops.rb +328 -1
  5. data/lib/sequel/sql.rb +8 -5
  6. data/lib/sequel/version.rb +2 -2
  7. metadata +2 -236
  8. data/CHANGELOG +0 -1397
  9. data/README.rdoc +0 -936
  10. data/doc/advanced_associations.rdoc +0 -884
  11. data/doc/association_basics.rdoc +0 -1859
  12. data/doc/bin_sequel.rdoc +0 -146
  13. data/doc/cheat_sheet.rdoc +0 -255
  14. data/doc/code_order.rdoc +0 -104
  15. data/doc/core_extensions.rdoc +0 -405
  16. data/doc/dataset_basics.rdoc +0 -96
  17. data/doc/dataset_filtering.rdoc +0 -222
  18. data/doc/extensions.rdoc +0 -77
  19. data/doc/fork_safety.rdoc +0 -84
  20. data/doc/mass_assignment.rdoc +0 -98
  21. data/doc/migration.rdoc +0 -660
  22. data/doc/model_dataset_method_design.rdoc +0 -129
  23. data/doc/model_hooks.rdoc +0 -254
  24. data/doc/model_plugins.rdoc +0 -270
  25. data/doc/mssql_stored_procedures.rdoc +0 -43
  26. data/doc/object_model.rdoc +0 -563
  27. data/doc/opening_databases.rdoc +0 -439
  28. data/doc/postgresql.rdoc +0 -611
  29. data/doc/prepared_statements.rdoc +0 -144
  30. data/doc/querying.rdoc +0 -1070
  31. data/doc/reflection.rdoc +0 -120
  32. data/doc/release_notes/5.0.0.txt +0 -159
  33. data/doc/release_notes/5.1.0.txt +0 -31
  34. data/doc/release_notes/5.10.0.txt +0 -84
  35. data/doc/release_notes/5.11.0.txt +0 -83
  36. data/doc/release_notes/5.12.0.txt +0 -141
  37. data/doc/release_notes/5.13.0.txt +0 -27
  38. data/doc/release_notes/5.14.0.txt +0 -63
  39. data/doc/release_notes/5.15.0.txt +0 -39
  40. data/doc/release_notes/5.16.0.txt +0 -110
  41. data/doc/release_notes/5.17.0.txt +0 -31
  42. data/doc/release_notes/5.18.0.txt +0 -69
  43. data/doc/release_notes/5.19.0.txt +0 -28
  44. data/doc/release_notes/5.2.0.txt +0 -33
  45. data/doc/release_notes/5.20.0.txt +0 -89
  46. data/doc/release_notes/5.21.0.txt +0 -87
  47. data/doc/release_notes/5.22.0.txt +0 -48
  48. data/doc/release_notes/5.23.0.txt +0 -56
  49. data/doc/release_notes/5.24.0.txt +0 -56
  50. data/doc/release_notes/5.25.0.txt +0 -32
  51. data/doc/release_notes/5.26.0.txt +0 -35
  52. data/doc/release_notes/5.27.0.txt +0 -21
  53. data/doc/release_notes/5.28.0.txt +0 -16
  54. data/doc/release_notes/5.29.0.txt +0 -22
  55. data/doc/release_notes/5.3.0.txt +0 -121
  56. data/doc/release_notes/5.30.0.txt +0 -20
  57. data/doc/release_notes/5.31.0.txt +0 -148
  58. data/doc/release_notes/5.32.0.txt +0 -46
  59. data/doc/release_notes/5.33.0.txt +0 -24
  60. data/doc/release_notes/5.34.0.txt +0 -40
  61. data/doc/release_notes/5.35.0.txt +0 -56
  62. data/doc/release_notes/5.36.0.txt +0 -60
  63. data/doc/release_notes/5.37.0.txt +0 -30
  64. data/doc/release_notes/5.38.0.txt +0 -28
  65. data/doc/release_notes/5.39.0.txt +0 -19
  66. data/doc/release_notes/5.4.0.txt +0 -80
  67. data/doc/release_notes/5.40.0.txt +0 -40
  68. data/doc/release_notes/5.41.0.txt +0 -25
  69. data/doc/release_notes/5.42.0.txt +0 -136
  70. data/doc/release_notes/5.43.0.txt +0 -98
  71. data/doc/release_notes/5.44.0.txt +0 -32
  72. data/doc/release_notes/5.45.0.txt +0 -34
  73. data/doc/release_notes/5.46.0.txt +0 -87
  74. data/doc/release_notes/5.47.0.txt +0 -59
  75. data/doc/release_notes/5.48.0.txt +0 -14
  76. data/doc/release_notes/5.49.0.txt +0 -59
  77. data/doc/release_notes/5.5.0.txt +0 -61
  78. data/doc/release_notes/5.50.0.txt +0 -78
  79. data/doc/release_notes/5.51.0.txt +0 -47
  80. data/doc/release_notes/5.52.0.txt +0 -87
  81. data/doc/release_notes/5.53.0.txt +0 -23
  82. data/doc/release_notes/5.54.0.txt +0 -27
  83. data/doc/release_notes/5.55.0.txt +0 -21
  84. data/doc/release_notes/5.56.0.txt +0 -51
  85. data/doc/release_notes/5.57.0.txt +0 -23
  86. data/doc/release_notes/5.58.0.txt +0 -31
  87. data/doc/release_notes/5.59.0.txt +0 -73
  88. data/doc/release_notes/5.6.0.txt +0 -31
  89. data/doc/release_notes/5.60.0.txt +0 -22
  90. data/doc/release_notes/5.61.0.txt +0 -43
  91. data/doc/release_notes/5.62.0.txt +0 -132
  92. data/doc/release_notes/5.63.0.txt +0 -33
  93. data/doc/release_notes/5.64.0.txt +0 -50
  94. data/doc/release_notes/5.65.0.txt +0 -21
  95. data/doc/release_notes/5.66.0.txt +0 -24
  96. data/doc/release_notes/5.67.0.txt +0 -32
  97. data/doc/release_notes/5.68.0.txt +0 -61
  98. data/doc/release_notes/5.69.0.txt +0 -26
  99. data/doc/release_notes/5.7.0.txt +0 -108
  100. data/doc/release_notes/5.70.0.txt +0 -35
  101. data/doc/release_notes/5.71.0.txt +0 -21
  102. data/doc/release_notes/5.72.0.txt +0 -33
  103. data/doc/release_notes/5.73.0.txt +0 -66
  104. data/doc/release_notes/5.74.0.txt +0 -45
  105. data/doc/release_notes/5.75.0.txt +0 -35
  106. data/doc/release_notes/5.76.0.txt +0 -86
  107. data/doc/release_notes/5.77.0.txt +0 -63
  108. data/doc/release_notes/5.78.0.txt +0 -67
  109. data/doc/release_notes/5.79.0.txt +0 -28
  110. data/doc/release_notes/5.8.0.txt +0 -170
  111. data/doc/release_notes/5.80.0.txt +0 -40
  112. data/doc/release_notes/5.81.0.txt +0 -31
  113. data/doc/release_notes/5.82.0.txt +0 -61
  114. data/doc/release_notes/5.83.0.txt +0 -56
  115. data/doc/release_notes/5.9.0.txt +0 -99
  116. data/doc/schema_modification.rdoc +0 -679
  117. data/doc/security.rdoc +0 -443
  118. data/doc/sharding.rdoc +0 -286
  119. data/doc/sql.rdoc +0 -648
  120. data/doc/testing.rdoc +0 -204
  121. data/doc/thread_safety.rdoc +0 -15
  122. data/doc/transactions.rdoc +0 -250
  123. data/doc/validations.rdoc +0 -558
  124. data/doc/virtual_rows.rdoc +0 -265
@@ -1,129 +0,0 @@
1
- = Model Dataset Method Design Guide
2
-
3
- How you design your model dataset methods can significantly affect the flexibility of your API for your model classes, as well as the performance. The goal of this guide is to provide an example of how to design your model dataset methods for maximum flexibility and performance.
4
-
5
- == Flexibility: Use Single Method Per Task
6
-
7
- In general, it is recommended that you have a single method per task for maximum flexibility. For example, let's say you need to retrieve all albums released in a given year, ordered by number of units sold descending, and only care about the id, name and number of units sold. One way to do this is in your application code (outside the model), you can
8
- call the dataset methods directly:
9
-
10
- Album.
11
- select(:id, :name, :copies_sold).
12
- where(release_year: params[:year].to_i).
13
- order(Sequel.desc(:copies_sold)).
14
- all
15
-
16
- One issue with this design is that it ties you to your current database schema, and will make it necessary to change your application code if your schema changes. In general, it is better to encapsulate your code into a dataset method (or a class method, but a dataset method is more flexible):
17
-
18
- class Album < Sequel::Model
19
- dataset_module do
20
- def all_albums_released_in_year(year)
21
- select(:id, :name, :copies_sold).
22
- where(release_year: year).
23
- order(Sequel.desc(:copies_sold)).
24
- all
25
- end
26
- end
27
- end
28
-
29
- Then your application code just needs to call your dataset method:
30
-
31
- Album.all_albums_released_in_year(params[:year].to_i)
32
-
33
- The advantage of this approach is that you can change your schema at any point in the future, and you should only need to change your model code, you should never need to change other application code.
34
-
35
- == Performance
36
-
37
- After designing your dataset methods for flexibility, stop. Don't worry about performance until you need to worry about performance. However, assuming you have profiled your application and profiling shows you can benefit from optimizing the above method, you can then consider the performance impact of future design choices.
38
-
39
- First, considering that the root cause of the performance issue may not be at the Sequel level, it may be at the database itself. Use +EXPLAIN+ or the equivalent to analyze the query plan for the query in use, and see if there is something you can do to optimize it, such as adding an appropriate index.
40
-
41
- Second, assuming the performance issue is at the Sequel level, you need to understand that one of the best ways to improve performance in most ruby code is to reduce the number of objects allocated. Here is the above code with comments showing datasets allocated:
42
-
43
- def all_albums_released_in_year(year)
44
- select(:id, :name, :copies_sold). # new dataset allocated
45
- where(release_year: year). # new dataset allocated
46
- order(Sequel.desc(:copies_sold)). # new dataset allocated
47
- all
48
- end
49
-
50
- Third, you need to understand that Sequel has optimizations specifically designed to reduce the number of objects allocated, by caching intermediate datasets. Unfortunately, those optimizations do not apply in this case. The reason for this is that +select+, +where+, and +order+ can potentially receive arbitrary arguments, and enabling caching for them could easily lead to unbounded cache size (denial of service due to memory exhaustion).
51
-
52
- To allow intermediate dataset caching to work, you need to signal to Sequel that particular arguments to these methods should be cached, and you can do that by calling methods inside +dataset_module+ blocks such as +select+ and +order+. These methods will add dataset methods to the model that can cache the returned dataset to optimize performance. Here is an example using these methods:
53
-
54
- class Album < Sequel::Model
55
- dataset_module do
56
- select :with_name_and_units, :id, :name, :copies_sold
57
- order :by_units_sold, Sequel.desc(:copies_sold)
58
-
59
- def all_albums_released_in_year(year)
60
- with_name_and_units.
61
- by_units_sold.
62
- where(release_year: year).
63
- all
64
- end
65
- end
66
- end
67
-
68
- Performance aside, this does provide a slightly nicer and more readable internal API, though naming such methods can be problematic.
69
-
70
- By calling +select+ and +order+ here, Sequel expects that the created dataset methods may be called more than once on the same dataset, and it knows that the arguments to the underlying +select+ and +order+ methods are fixed, so it can cache the resulting datasets. Let's comment the above example with dataset allocations:
71
-
72
- def all_albums_released_in_year(year)
73
- with_name_and_units. # cached dataset returned
74
- by_units_sold. # cached dataset returned
75
- where(release_year: year). # new dataset allocated
76
- all
77
- end
78
-
79
- Note that the order of methods here is important. If you instead change the method chain to filter the dataset first, then no caching happens:
80
-
81
- def all_albums_released_in_year(year)
82
- where(release_year: year). # new dataset allocated
83
- with_name_and_units. # new dataset allocated
84
- by_units_sold. # new dataset allocated
85
- all
86
- end
87
-
88
- This is because any time a new, uncached dataset is returned by a dataset method, all subsequent methods in the method chain cannot benefit from caching.
89
-
90
- Usually, when you are designing methods to process data based on user input, the user input affects the rows selected, and not the columns selected or the order in which the rows are returned. Sequel is aware of this and has dataset methods that specifically take user input (arguments), interpret them as a filter condition and either:
91
-
92
- * Return all matching rows in an array (+where_all+)
93
- * Iterate over all matching rows (+where_each+)
94
- * Return first matching row (+first+)
95
- * Return first column in first matching row, assumes only a single column is selected (+where_single_value+)
96
-
97
- After calling these methods on a cached dataset a number of times (currently 3), Sequel will automatically build an optimized loader, cache it, and use it for future loads. So the above example changes to:
98
-
99
- def all_albums_released_in_year(year)
100
- with_name_and_units. # cached dataset returned
101
- by_units_sold. # cached dataset returned
102
- where_all(release_year: year) # cached loader used
103
- end
104
-
105
- This can significantly improve performance, up to 3x for complex method chains that only return a few rows.
106
-
107
- So the general advice on designing dataset methods for performance is:
108
-
109
- * Use +dataset_module+ methods to create named dataset methods that return cached datasets
110
- * If any filtering is to be done, have it done last using +where_all+, +where_each+, +first+, or +where_single_value+.
111
-
112
- By following this advice, you can significantly increase the performance of your model dataset code.
113
-
114
- === Further Increasing Performance
115
-
116
- The best way to further increase performance at the Sequel level is to switch to using prepared statements. This does require more significant changes to the API. Here's an example using prepared statements:
117
-
118
- class Album < Sequel::Model
119
- ALBUMS_RELEASED_IN_YEAR = select(:id, :name, :copies_sold).
120
- where(release_year: :$year).
121
- order(Sequel.desc(:copies_sold)).
122
- prepare(:all, :all_albums_released_in_year)
123
-
124
- def self.all_albums_released_in_year(year)
125
- ALBUMS_RELEASED_IN_YEAR.call(year: year)
126
- end
127
- end
128
-
129
- Note that when using prepared statements, you need to use a class method instead of a dataset method, as the SQL for the prepared statement must be fixed for the class. This limits the flexibility of the method, since you can no longer call it on arbitrary datasets on the class.
data/doc/model_hooks.rdoc DELETED
@@ -1,254 +0,0 @@
1
- = Model Hooks
2
-
3
- This guide is based on http://guides.rubyonrails.org/activerecord_validations_callbacks.html
4
-
5
- == Overview
6
-
7
- Model hooks are used to specify actions that occur at a given point in a model instance's lifecycle, such as before or after the model object is saved, created, updated, destroyed, or validated. There are also around hooks for all types, which wrap the before hooks, the behavior, and the after hooks.
8
-
9
- == Basic Usage
10
-
11
- <tt>Sequel::Model</tt> uses instance methods for hooks. To define a hook on a model, you just add an instance method to the model class:
12
-
13
- class Album < Sequel::Model
14
- def before_create
15
- self.created_at ||= Time.now
16
- super
17
- end
18
- end
19
-
20
- The one important thing to note here is the call to +super+ inside the hook. Whenever you override one of Sequel::Model's methods, you should be calling +super+ to get the default behavior. Many of the plugins that ship with Sequel work by overriding the hook methods and calling +super+. If you use these plugins and override the hook methods but do not call +super+, it's likely the plugins will not work correctly.
21
-
22
- == Available Hooks
23
-
24
- Sequel calls hooks in the following order when saving/creating a new object (one that does not already exist in the database):
25
-
26
- * +around_validation+
27
- * +before_validation+
28
- * +validate+ method called
29
- * +after_validation+
30
- * +around_save+
31
- * +before_save+
32
- * +around_create+
33
- * +before_create+
34
- * INSERT QUERY
35
- * +after_create+
36
- * +after_save+
37
-
38
- Sequel calls hooks in the following order when saving an existing object:
39
-
40
- * +around_validation+
41
- * +before_validation+
42
- * +validate+ method called
43
- * +after_validation+
44
- * +around_save+
45
- * +before_save+
46
- * +around_update+
47
- * +before_update+
48
- * UPDATE QUERY
49
- * +after_update+
50
- * +after_save+
51
-
52
- Note that all of the hook calls are the same, except that +around_create+, +before_create+ and +after_create+ are used for a new object, and +around_update+, +before_update+ and +after_update+ are used for an existing object. Note that +around_save+, +before_save+, and +after_save+ are called in both cases.
53
-
54
- Note that the validation hooks are still called if <tt>validate: false</tt> option is passed to save. If you call <tt>Model#valid?</tt> manually, then only the validation hooks are called:
55
-
56
- * +around_validation+
57
- * +before_validation+
58
- * +validate+ method called
59
- * +after_validation+
60
-
61
- Sequel calls hooks in the following order when destroying an existing object:
62
-
63
- * +around_destroy+
64
- * +before_destroy+
65
- * DELETE QUERY
66
- * +after_destroy+
67
-
68
- Note that these hooks are only called when using <tt>Model#destroy</tt>, they are not called if you use <tt>Model#delete</tt>.
69
-
70
- == Transaction-related Hooks
71
-
72
- Sequel::Model no longer offers transaction hooks for model instances. However, you can use the database transaction hooks inside model +before_save+ and +after_save+ hooks:
73
-
74
- class Album < Sequel::Model
75
- def before_save
76
- db.after_rollback{rollback_action}
77
- super
78
- end
79
-
80
- def after_save
81
- super
82
- db.after_commit{commit_action}
83
- end
84
- end
85
-
86
- == Running Hooks
87
-
88
- Sequel does not provide a simple way to turn off the running of save/create/update hooks. If you attempt to save a model object, the save hooks are always called. All model instance methods that modify the database call save in some manner, so you can be sure that if you define the hooks, they will be called when you save the object.
89
-
90
- However, you should note that there are plenty of ways to modify the database without saving a model object. One example is by using plain datasets, or one of the model's dataset methods:
91
-
92
- Album.where(name: 'RF').update(copies_sold: Sequel.+(:copies_sold, 1))
93
- # UPDATE albums SET copies_sold = copies_sold + 1 WHERE name = 'RF'
94
-
95
- In this case, the +update+ method is called on the dataset returned by <tt>Album.where</tt>. Even if there is only a single object with the name RF, this will not call any hooks. If you want model hooks to be called, you need to make sure to operate on a model object:
96
-
97
- album = Album.first(name: 'RF')
98
- album.update(copies_sold: album.copies_sold + 1)
99
- # UPDATE albums SET copies_sold = 2 WHERE id = 1
100
-
101
- For the destroy hooks, you need to make sure you call +destroy+ on the object:
102
-
103
- album.destroy # runs destroy hooks
104
-
105
- == Skipping Hooks
106
-
107
- Sequel makes it easy to skip destroy hooks by calling +delete+ instead of +destroy+:
108
-
109
- album.delete # does not run destroy hooks
110
-
111
- However, skipping hooks is a bad idea in general and should be avoided. As mentioned above, Sequel doesn't allow you to turn off the running of save hooks. If you know what you are doing and really want to skip them, you need to drop down to the dataset level to do so. This can be done for a specific model object by using the +this+ method for a dataset that represents a single object:
112
-
113
- album.this # dataset
114
-
115
- The +this+ dataset works just like any other dataset, so you can call +update+ on it to modify it:
116
-
117
- album.this.update(copies_sold: album.copies_sold + 1)
118
-
119
- If you want to insert a row into the model's table without running the creation hooks, you can use <tt>Model.insert</tt> instead of <tt>Model.create</tt>:
120
-
121
- Album.insert(name: 'RF') # does not run hooks
122
-
123
- == Canceling Actions in Hooks
124
-
125
- Sometimes want to cancel an action in a before hook, so the action is not performed. For example, you may want to not allow destroying or saving a record in certain cases. In those cases, you can call +cancel_action+ inside the <tt>before_*</tt> hook, which will stop processing the hook and will either raise a <tt>Sequel::HookFailed</tt> exception (the default), or return +nil+ if +raise_on_save_failure+ is +false+). You can use this to implement validation-like behavior, that will run even if validations are skipped:
126
-
127
- class Album < Sequel::Model
128
- def before_save
129
- cancel_action if name == ''
130
- super
131
- end
132
- end
133
-
134
- For around hooks, neglecting to call +super+ halts hook processing in the same way as calling +cancel_action+ in a before hook. It's probably a bad idea to use +cancel_action+ hook processing in after hooks, or after yielding in around hooks, since by then the main processing has already taken place.
135
-
136
- By default, Sequel runs hooks other than validation hooks inside a transaction, so if you cancel the action by calling +cancel_action+ in any hook, Sequel will rollback the transaction. However, note that the implicit use of transactions when saving and destroying model objects is conditional (it depends on the model instance's +use_transactions+ setting and the <tt>:transaction</tt> option passed to save).
137
-
138
- == Conditional Hooks
139
-
140
- Sometimes you only take to take a certain action in a hook if the object meets a certain condition. For example, let's say you only want to make sure a timestamp is set when updating if the object is at a certain status level:
141
-
142
- class Album < Sequel::Model
143
- def before_update
144
- self.timestamp ||= Time.now if status_id > 3
145
- super
146
- end
147
- end
148
-
149
- Note how this hook action is made conditional just be using the standard ruby +if+ conditional. Sequel makes it easy to handle conditional hook actions by using standard ruby conditionals inside the instance methods.
150
-
151
- == Using Hooks in Multiple Classes
152
-
153
- If you want all your model classes to use the same hook, you can just define that hook in Sequel::Model:
154
-
155
- class Sequel::Model
156
- def before_create
157
- self.created_at ||= Time.now
158
- super
159
- end
160
- end
161
-
162
- Just remember to call +super+ whenever you override the method in a subclass. Note that +super+ is also used when overriding the hook in <tt>Sequel::Model</tt> itself. This is important as if you add any plugins to Sequel::Model itself, if you override a hook in <tt>Sequel::Model</tt> and do not call +super+, the plugin may not work correctly.
163
-
164
- If you don't want all classes to use the same hook, but want to reuse hooks in multiple classes, you should use a plugin or a simple module:
165
-
166
- === Plugin
167
-
168
- module SetCreatedAt
169
- module InstanceMethods
170
- def before_create
171
- self.created_at ||= Time.now
172
- super
173
- end
174
- end
175
- end
176
- Album.plugin(SetCreatedAt)
177
- Artist.plugin(SetCreatedAt)
178
-
179
- === Simple Module
180
-
181
- module SetCreatedAt
182
- def before_create
183
- self.created_at ||= Time.now
184
- super
185
- end
186
- end
187
- Album.send(:include, SetCreatedAt)
188
- Artist.send(:include, SetCreatedAt)
189
-
190
- == +super+ Ordering
191
-
192
- While it's not enforced anywhere, it's a good idea to make +super+ the last expression when you override a before hook, and the first expression when you override an after hook:
193
-
194
- class Album < Sequel::Model
195
- def before_save
196
- self.updated_at ||= Time.now
197
- super
198
- end
199
-
200
- def after_save
201
- super
202
- AuditLog.create(log: "Album #{name} created")
203
- end
204
- end
205
-
206
- This allows the following general principles to be true:
207
-
208
- * before hooks are run in reverse order of inclusion
209
- * after hooks are run in order of inclusion
210
-
211
- So if you define the same before hook in both a model and a plugin that the model uses, the hooks will be called in this order:
212
-
213
- * model before hook
214
- * plugin before hook
215
- * plugin after hook
216
- * model after hook
217
-
218
- Again, Sequel does not enforce that, and you are free to call +super+ in an order other than the recommended one (just make sure that you call it).
219
-
220
- == Around Hooks
221
-
222
- Around hooks should only be used if you cannot accomplish the same results with before and after hooks. For example, if you want to catch database errors caused by the +INSERT+ or +UPDATE+ query when saving a model object and raise them as validation errors, you cannot use a before or after hook. You have use an +around_save+ hook:
223
-
224
- class Album < Sequel::Model
225
- def around_save
226
- super
227
- rescue Sequel::DatabaseError => e
228
- # parse database error, set error on self, and reraise a Sequel::ValidationFailed
229
- end
230
- end
231
-
232
- Likewise, let's say that upon retrieval, you associate an object with a file descriptor, and you want to ensure that the file descriptor is closed after the object is saved to the database. Let's assume you are always saving the object and you are not using validations. You could not use an +after_save+ hook safely, since if the database raises an error, the +after_save+ method will not be called. In this case, an +around_save+ hook is also the correct choice:
233
-
234
- class Album < Sequel::Model
235
- def around_save
236
- super
237
- ensure
238
- @file_descriptor.close
239
- end
240
- end
241
-
242
- == Hook related plugins
243
-
244
- === +instance_hooks+
245
-
246
- Sequel also ships with an +instance_hooks+ plugin that allows you to define before and after hooks on a per instance basis. It's very useful as it allows you to delay action on an instance until before or after saving. This can be important if you want to modify a group of related objects together (which is how the +nested_attributes+ plugin uses +instance_hooks+).
247
-
248
- === +hook_class_methods+
249
-
250
- While it's recommended to write your hooks as instance methods, Sequel ships with a +hook_class_methods+ plugin that allows you to define hooks via class methods. It exists mostly for legacy compatibility, but is still supported. However, it does not implement around hooks.
251
-
252
- === +after_initialize+
253
-
254
- The after_initialize plugin adds an after_initialize hook, that is called for all model instances on creation (both new instances and instances retrieved from the database). It exists mostly for legacy compatibility, but it is still supported.
@@ -1,270 +0,0 @@
1
- = Model Plugins
2
-
3
- Sequel::Model (and Sequel in general) is designed around the idea of a small core, to which application-specific behavior can easily be added. Sequel::Model implements this design using a plugin system. Plugins are modules that include submodules for model class methods, model instance methods, and model dataset methods. All plugins can override the class, instance, and dataset methods added by earlier plugins, and call super to get the behavior before the plugin was added.
4
-
5
- == Default Plugins
6
-
7
- The Sequel::Model class is completely empty by default, in that it has no class methods or instance methods. Sequel::Model is itself a plugin, and it is the first plugin loaded, and it is loaded into itself (meta!). So methods in Sequel::Model::ClassMethods become Sequel::Model class methods, methods in Sequel::Model::InstanceMethods become Sequel::Model instance methods, and methods in Sequel::Model::DatasetMethods become Sequel::Model dataset methods. The Sequel::Model plugin is often referred to as the base plugin.
8
-
9
- By default, the Sequel::Model class also has the Sequel::Model::Associations plugin loaded by default, though it is possible to disable this.
10
-
11
- == Loading Plugins
12
-
13
- Loading a plugin into a model class is generally as simple as calling the Sequel::Model.plugin method with the name of the plugin, for example:
14
-
15
- Sequel::Model.plugin :subclasses
16
-
17
- What is does is require the <tt>sequel/plugins/subclasses</tt> file, and then assumes that that file defines the <tt>Sequel::Plugins::Subclasses</tt> plugin module.
18
-
19
- It's possible to pass module instances to the plugin method to load plugins that are stored in arbitrary files or namespaces:
20
-
21
- Sequel::Model.plugin MyApp::Plugins::Foo
22
-
23
- In the examples shown above, the plugin is loaded into Sequel::Model, which means it is loaded into all subclasses that are created afterward. With many plugins, you are not going to want to add them to Sequel::Model, but to a specific subclass:
24
-
25
- class Node < Sequel::Model
26
- plugin :tree
27
- end
28
-
29
- Doing this, only Node and future subclasses of Node will have the tree plugin loaded.
30
-
31
- == Plugin Arguments/Options
32
-
33
- Some plugins require arguments and/or support options. For example, the single_table_inheritance plugin requires an argument containing the column that specifies the class to use, and options:
34
-
35
- class Employee < Sequel::Model
36
- plugin :single_table_inheritance, :type_id, model_map: {1=>:Staff, 2=>:Manager}
37
- end
38
-
39
- You should read the documentation for the plugin to determine if it requires arguments and what if any options are supported.
40
-
41
- == Creating Plugins
42
-
43
- The simplest possible plugin is an empty module in a file stored in <tt>sequel/plugins/plugin_name</tt> somewhere in ruby's load path:
44
-
45
- module Sequel
46
- module Plugins
47
- module PluginName
48
- end
49
- end
50
- end
51
-
52
- Well, technically, that's not the simplest possible plugin, but it is the simplest one you can load by name. The absolute simplest plugin would be an empty module:
53
-
54
- Sequel::Model.plugin Module.new
55
-
56
- == Example Formatting
57
-
58
- In general, loading plugins by module instead of by name is not recommended, so this guide will assume that plugins are loaded by name. For simplicity, we'll also use the following format for example plugin code (and assume a plugin named Foo stored in <tt>sequel/plugins/foo</tt>):
59
-
60
- module Sequel::Plugins::Foo
61
- end
62
-
63
- This saves 4 lines per example. However, it's recommended that you use the nested example displayed earlier for production code.
64
-
65
- The examples also assume that the following model class exists:
66
-
67
- class Bar < Sequel::Model
68
- end
69
-
70
- == Adding Class Methods
71
-
72
- If you want your plugin to add class methods to the model class it is loaded into, define a ClassMethods module under the plugin module:
73
-
74
- module Sequel::Plugins::Foo
75
- module ClassMethods
76
- def a
77
- 1
78
- end
79
- end
80
- end
81
-
82
- This allows a plugin user to do:
83
-
84
- Bar.plugin :foo
85
- Bar.a # => 1
86
-
87
- == Adding Instance Methods
88
-
89
- If you want your plugin to add instance methods to the model class it is loaded into, define an InstanceMethods module under the plugin module:
90
-
91
- module Sequel::Plugins::Foo
92
- module InstanceMethods
93
- def a
94
- 1
95
- end
96
- end
97
- end
98
-
99
- This allows a plugin user to do:
100
-
101
- Bar.plugin :foo
102
- Bar.new.a # => 1
103
-
104
- == Adding Dataset Methods
105
-
106
- If you want your plugin to add methods to the dataset of the model class it is loaded into, define a DatasetMethods module under the plugin module:
107
-
108
- module Sequel::Plugins::Foo
109
- module DatasetMethods
110
- def a
111
- 1
112
- end
113
- end
114
- end
115
-
116
- This allows a plugin user to do:
117
-
118
- Bar.plugin :foo
119
- Bar.dataset.a # => 1
120
-
121
- == Calling super to get Previous Behavior
122
-
123
- No matter if you are dealing with class, instance, or dataset methods, you can call super inside the method to get the previous behavior. This makes it easy to hook into the method, add your own behavior, but still get the previous behavior:
124
-
125
- module Sequel::Plugins::Foo
126
- module InstanceMethods
127
- def save
128
- if allow_saving?
129
- super
130
- else
131
- raise Sequel::Error, 'saving not allowed for this object'
132
- end
133
- end
134
-
135
- private
136
-
137
- def allow_saving?
138
- moon =~ /Waxing/
139
- end
140
- end
141
- end
142
-
143
- == Running Code When the Plugin is Loaded
144
-
145
- Some plugins require more than just adding methods. Any plugin that requires state is going to have to initialize that state and store it somewhere (generally in the model class itself). If you want to run code when a plugin is loaded (usually to initialize state, but possibly for other reasons), there are two methods you can define to do so. The first method is apply, and it is called only the first time the plugin is loaded into the class, before it is loaded into the class. This is generally only used if a plugin depends on another plugin or for initializing state. You define this method as a singleton method of the plugin module:
146
-
147
- module Sequel::Plugins::Foo
148
- def self.apply(model)
149
- model.instance_eval do
150
- plugin :plugin_that_foo_depends_on
151
- @foo_states = {}
152
- end
153
- end
154
- end
155
-
156
- The other method is called configure, and it is called everytime the plugin is loaded into the class, after it is loaded into the class:
157
-
158
- module Sequel::Plugins::Foo
159
- def self.configure(model)
160
- model.instance_eval do
161
- @foo_states[:initial] ||= :baz
162
- end
163
- end
164
- end
165
-
166
- Note that in the configure method, you know apply has already been called at least once (so @foo_state will definitely exist).
167
-
168
- If you want your plugin to take arguments and/or support options, you handle that by making your apply and configure methods take arguments and/or an options hash. For example, if you want the user to be able to set the initial state via an option, you can do:
169
-
170
- module Sequel::Plugins::Foo
171
- def self.apply(model, opts={})
172
- model.instance_eval do
173
- plugin :plugin_foo_depends_on
174
- @foo_states = {}
175
- end
176
- end
177
-
178
- def self.configure(model, opts={})
179
- model.instance_eval do
180
- @foo_states[:initial] = opts[:initial_state] || @foo_states[:initial] || :baz
181
- end
182
- end
183
- end
184
-
185
- This allows a user of the plugin to do either of the following
186
-
187
- Bar.plugin :foo
188
- Bar.plugin :foo, initial_state: :quux
189
-
190
- If you want to require the initial state to be provided as an argument:
191
-
192
- module Sequel::Plugins::Foo
193
- def self.apply(model, initial_state)
194
- model.instance_eval do
195
- plugin :plugin_foo_depends_on
196
- @foo_states = {}
197
- end
198
- end
199
-
200
- def self.configure(model, initial_state)
201
- model.instance_eval do
202
- @foo_states[:initial] = initial_state
203
- end
204
- end
205
- end
206
-
207
- This requires that the user of the plugin specify the argument:
208
-
209
- Bar.plugin :foo, :quux
210
-
211
- In general you should only require plugin arguments if you absolutely must have a value and there is no good default.
212
-
213
- == Handling Subclasses
214
-
215
- Sequel::Model uses a copy-on-subclassing approach to model state. So instead of having a model subclass ask its superclass for a value if the subclass don't have the value defined, the value should be copied from the parent class to the subclass when the subclass is created. While this can be implemented by overriding the +inherited+ class method, there is an available shortcut that handles most cases:
216
-
217
- module Sequel::Plugins::Foo
218
- module ClassMethods
219
- Sequel::Plugins.inherited_instance_variables(self, :@foo_states => :dup)
220
- end
221
- end
222
-
223
- Inside the ClassMethods submodule, you call the Sequel::Plugins.inherited_instance_variables method with the first argument being self. The second argument should be a hash describing how to copy the value from the parent class into the subclass. The keys of this hash are instance variable names, including the @ symbol (e.g. :@foo_state). The values of this hash describe how to copy it:
224
-
225
- nil :: Use the value directly.
226
- :dup :: Call dup on the value.
227
- :hash_dup :: Create a new hash with the same keys, but a dup of all the values.
228
- Proc :: An arbitrary proc that is called with the parent class value and should return the value to set into the subclass.
229
-
230
- == Handling Changes to the Model's Dataset
231
-
232
- In many plugins, if the model class changes the dataset, you need to change the state for the plugin. While you can do this by overriding the set_dataset class method, there is an available shortcut:
233
-
234
- module Sequel::Plugins::Foo
235
- module ClassMethods
236
- Sequel::Plugins.after_set_dataset(self, :set_foo_table)
237
-
238
- private
239
-
240
- def set_foo_table
241
- @foo_states[:table] = table_name
242
- end
243
- end
244
- end
245
-
246
- With this code, any time the model's dataset changes, the state of the plugin will be updated to set the correct table name. This is also called when creating a new model class with a dataset.
247
-
248
- == Making Dataset Methods Callable as Class Methods
249
-
250
- In some cases, when dataset methods are added, you want to also create a model class method that will call the dataset method, so you can write:
251
-
252
- Model.method
253
-
254
- instead of:
255
-
256
- Model.dataset.method
257
-
258
- There is an available shortcut that automatically creates the class methods:
259
-
260
- module Sequel::Plugins::Foo
261
- module ClassMethods
262
- Sequel::Plugins.def_dataset_methods(self, :quux)
263
- end
264
-
265
- module DatasetMethods
266
- def quux
267
- 2
268
- end
269
- end
270
- end