pod4 0.8.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.hgignore +1 -0
- data/.hgtags +1 -0
- data/Gemfile +4 -4
- data/README.md +165 -161
- data/lib/pod4/basic_model.rb +1 -1
- data/lib/pod4/errors.rb +15 -0
- data/lib/pod4/interface.rb +4 -1
- data/lib/pod4/model.rb +34 -35
- data/lib/pod4/nebulous_interface.rb +2 -1
- data/lib/pod4/sequel_interface.rb +30 -6
- data/lib/pod4/version.rb +1 -1
- data/lib/pod4.rb +1 -0
- data/pod4.gemspec +1 -1
- data/spec/README.md +0 -5
- data/spec/common/basic_model_spec.rb +1 -1
- data/spec/common/model_new_validate_spec.rb +204 -0
- data/spec/common/model_spec.rb +53 -15
- data/spec/common/nebulous_interface_spec.rb +19 -4
- data/spec/jruby/sequel_interface_jdbc_ms_spec.rb +2 -1
- metadata +6 -5
- data/.ruby-version +0 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8a9606d06dd2634ff0382a7985c25e65c1dd21e0
|
|
4
|
+
data.tar.gz: 8a4dd824c41d8fc213dd1ebd3d29f53f2f2912d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c8cf85e89a6f2135e51712a6d5b06c1c233dfdc31c03852d9f4bfaf4ea3dbe7116bf1d0e81c425bdc2c91361a9811c0dee127092086c57fbf89f236e854bec34
|
|
7
|
+
data.tar.gz: 192f0df1a101076226c8574d53eb9dd5db353422e8f279f2e575f87e00df8c47e7a8f323cae8b226e6694a4acd5e23db33cb41074b2473e0ffe3573218b5c61d
|
data/.hgignore
CHANGED
data/.hgtags
CHANGED
data/Gemfile
CHANGED
|
@@ -6,14 +6,14 @@ gemspec
|
|
|
6
6
|
group :development, :test do
|
|
7
7
|
|
|
8
8
|
# for bundler, management, etc etc
|
|
9
|
-
gem "bundler", "~> 1.
|
|
10
|
-
gem "rake", "~> 12
|
|
11
|
-
gem "rspec", "~> 3.
|
|
9
|
+
gem "bundler", "~> 1.15"
|
|
10
|
+
gem "rake", "~> 12"
|
|
11
|
+
gem "rspec", "~> 3.7"
|
|
12
12
|
gem 'pry'
|
|
13
13
|
gem "pry-doc"
|
|
14
14
|
|
|
15
15
|
# For testing
|
|
16
|
-
gem "sequel", "~>
|
|
16
|
+
gem "sequel", "~> 5.3"
|
|
17
17
|
gem "nebulous_stomp", "~> 3"
|
|
18
18
|
|
|
19
19
|
platforms :ruby do
|
data/README.md
CHANGED
|
@@ -31,41 +31,37 @@ And here's a method that uses the model:
|
|
|
31
31
|
Seriously now
|
|
32
32
|
-------------
|
|
33
33
|
|
|
34
|
-
Pod4 is a very simple set of classes that sits on top of some other library
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
library, and you're not limited to databases.
|
|
34
|
+
Pod4 is a very simple set of classes that sits on top of some other library which gives access to
|
|
35
|
+
data -- for example, pg, tds, or Sequel (which _is_ an ORM...) It's relatively easy to get it to
|
|
36
|
+
talk to a new sort of data access library, and you're not limited to databases.
|
|
38
37
|
|
|
39
|
-
It provides a simple, common framework to talk to all these data sources, using
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
It provides a simple, common framework to talk to all these data sources, using model classes which
|
|
39
|
+
(to my mind at least) are clean, easy to understand and maintain, using a bare minimum of DSL and
|
|
40
|
+
vanilla Ruby inheritance.
|
|
42
41
|
|
|
43
|
-
This is the central motivation behind the project -- to provide a mechanism
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
This is the central motivation behind the project -- to provide a mechanism that allows for model
|
|
43
|
+
classes which actually represent your data to the rest of your code in a way that you are fully in
|
|
44
|
+
control of. Because it's your model classes, not the database, which are the canonical
|
|
45
|
+
representation of the data.
|
|
47
46
|
|
|
48
|
-
I don't want the people who maintain my code to have to know the differences
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
from Pod4::BasicModel instead, and do without even that.
|
|
47
|
+
I don't want the people who maintain my code to have to know the differences between ActiveRecord's
|
|
48
|
+
`update` and `update_all`, or Sequel's `dataset[]` and `dataset.where()`. Pod4::Model has a dozen
|
|
49
|
+
or so methods you need to worry about, and six of those are pretty much self-explanatory. Or, you
|
|
50
|
+
can inherit from Pod4::BasicModel instead, and do without even that.
|
|
53
51
|
|
|
54
|
-
I honestly don't think of it as an Object Relational Manager. I think of it as
|
|
55
|
-
a Way To Have Nice Models.
|
|
52
|
+
I honestly don't think of it as an Object Relational Manager. I think of it as a Way To Have Nice Models.
|
|
56
53
|
|
|
57
|
-
If you are looking for something with all the features of, say, ActiveRecord,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
established existing DSL within your model code -- SQL.
|
|
54
|
+
If you are looking for something with all the features of, say, ActiveRecord, then this isn't for
|
|
55
|
+
you. I provide basic access to and maintenance of records, with validation. For anything more, you
|
|
56
|
+
need to be willing to use a very well established existing DSL within your model code -- SQL.
|
|
61
57
|
|
|
62
58
|
|
|
63
59
|
Thanks
|
|
64
60
|
======
|
|
65
61
|
|
|
66
62
|
This code was developed, by me, during working hours at [James Hall & Co.
|
|
67
|
-
Ltd](https://www.jameshall.co.uk/). I'm incredibly greatful that they have
|
|
68
|
-
|
|
63
|
+
Ltd](https://www.jameshall.co.uk/). I'm incredibly greatful that they have permitted me to
|
|
64
|
+
open-source it.
|
|
69
65
|
|
|
70
66
|
|
|
71
67
|
Installation
|
|
@@ -73,8 +69,8 @@ Installation
|
|
|
73
69
|
|
|
74
70
|
gem install pod4
|
|
75
71
|
|
|
76
|
-
Of course you will also need to install whatever other gems you need in order
|
|
77
|
-
|
|
72
|
+
Of course you will also need to install whatever other gems you need in order to access the data
|
|
73
|
+
you want Pod4 to see. Currently there are interfaces for:
|
|
78
74
|
|
|
79
75
|
* Sequel (which itself of course talks to all manner of databases)
|
|
80
76
|
* Tiny_tds
|
|
@@ -87,11 +83,14 @@ to access the data you want Pod4 to see. Currently there are interfaces for:
|
|
|
87
83
|
A Short Tutorial
|
|
88
84
|
================
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
(Don't Worry About) Octothorpe
|
|
87
|
+
------------------------------
|
|
92
88
|
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
Pod4 uses my Octothorpe gem to pass information around. An Octothorpe is basically a Hash, except
|
|
90
|
+
the keys are always symbols, and it's read only.
|
|
91
|
+
|
|
92
|
+
But you don't really need to know that here. If you mentally substitute "Hash" every time I say
|
|
93
|
+
"Octothorpe", you'll be fine.
|
|
95
94
|
|
|
96
95
|
|
|
97
96
|
Model and Interface
|
|
@@ -99,20 +98,19 @@ Model and Interface
|
|
|
99
98
|
|
|
100
99
|
Note well that we distinguish between 'models' and 'interfaces':
|
|
101
100
|
|
|
102
|
-
The model represents the data to your application, in the format that makes
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
but we'll leave that alone for now).
|
|
101
|
+
The model represents the data to your application, in the format that makes most sense to your
|
|
102
|
+
application: that might be the same format that it is stored in on the database, or it might not.
|
|
103
|
+
The model doesn't care about where the data comes from. Models are all subclasses of Pod4::Model
|
|
104
|
+
(or Pod4::BasicModel, but we'll leave that alone for now).
|
|
107
105
|
|
|
108
|
-
An interface encapsulates the connection to whatever is providing the data. It
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
An interface encapsulates the connection to whatever is providing the data. It might be a wrapper
|
|
107
|
+
for calls to the Sequel ORM, for example. Or it could be a making a series of calls to a set of
|
|
108
|
+
Nebulous verbs. It only cares about dealing with the data source, and it is only called by the
|
|
109
|
+
model.
|
|
112
110
|
|
|
113
|
-
An interface is a seperate class, which is defined for each model. There are
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
An interface is a seperate class, which is defined for each model. There are parent classes for a
|
|
112
|
+
number of the sources you will need, but failing that, you can always create one from the ultimate
|
|
113
|
+
parent, Pod4::Interface.
|
|
116
114
|
|
|
117
115
|
|
|
118
116
|
Simple Model Usage
|
|
@@ -128,8 +126,8 @@ Simple Model Usage
|
|
|
128
126
|
y.set(params)
|
|
129
127
|
y.create unless y.model_status == :error
|
|
130
128
|
|
|
131
|
-
A model is a class, each instance of which represents a single record. on that
|
|
132
|
-
|
|
129
|
+
A model is a class, each instance of which represents a single record. on that instance you can
|
|
130
|
+
call the following for basic operation:
|
|
133
131
|
|
|
134
132
|
* `create` -- tells the data source to store this new "record"
|
|
135
133
|
* `read` -- obtains the "record" from the data source
|
|
@@ -139,25 +137,22 @@ instance you can call the following for basic operation:
|
|
|
139
137
|
* `to_ot` -- output an Octothorpe of the object's column attributes
|
|
140
138
|
* `alerts` -- return an array of Alerts (which I'll explain later)
|
|
141
139
|
|
|
142
|
-
(Note that we say "record" not record. The data source might not be a database.
|
|
143
|
-
|
|
144
|
-
or something else entirely.)
|
|
140
|
+
(Note that we say "record" not record. The data source might not be a database. Your model
|
|
141
|
+
instance might be represented on the data source as several records, or something else entirely.)
|
|
145
142
|
|
|
146
|
-
There is one more operation - `list`. Call this on the model class itself,
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
except a hash, like so:
|
|
143
|
+
There is one more operation - `list`. Call this on the model class itself, and it will return an
|
|
144
|
+
array of model instances that match the criteria you pass. What you can pass to list depends on
|
|
145
|
+
your model class (of course); by default it also depends on the interface the model uses. But
|
|
146
|
+
normally it should except a hash, like so:
|
|
151
147
|
|
|
152
148
|
ExampleModel.list(:one => "a") #-> Array of ExampleModel where one = "a"
|
|
153
149
|
|
|
154
|
-
Additionally, you can chain `or_die` onto any model method to get it to raise
|
|
155
|
-
|
|
156
|
-
|
|
150
|
+
Additionally, you can chain `or_die` onto any model method to get it to raise exceptions if
|
|
151
|
+
something is wrong on the model. If you don't want exceptions, you can check the model's
|
|
152
|
+
model_status attribute, or just look at the alerts.
|
|
157
153
|
|
|
158
|
-
Those eight (nine) methods are _all_ the methods given by Pod4::Model that you
|
|
159
|
-
|
|
160
|
-
model.
|
|
154
|
+
Those eight (nine) methods are _all_ the methods given by Pod4::Model that you are normally going
|
|
155
|
+
to want to use, outside of the code actually inside your model.
|
|
161
156
|
|
|
162
157
|
|
|
163
158
|
A Simple Model
|
|
@@ -180,39 +175,35 @@ Here is the model and interface definition that goes with the above example:
|
|
|
180
175
|
attr_columns :one, :two, :three
|
|
181
176
|
end
|
|
182
177
|
|
|
183
|
-
In this example we have a model that relies on the Pg gem to talk to a
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
or error control.
|
|
178
|
+
In this example we have a model that relies on the Pg gem to talk to a table 'example'. The table
|
|
179
|
+
has a primary key field 'id' and columns which correspond to our three attributes one, two and
|
|
180
|
+
three. There is no validation or error control.
|
|
187
181
|
|
|
188
|
-
Note that we have to require pg_interface and pg seperately. I won't bother to
|
|
189
|
-
|
|
182
|
+
Note that we have to require pg_interface and pg seperately. I won't bother to show this in any
|
|
183
|
+
more model examples.
|
|
190
184
|
|
|
191
185
|
### Interface ###
|
|
192
186
|
|
|
193
|
-
Let's start with the interface definition. Remember, the interface class is
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
fine with that.
|
|
187
|
+
Let's start with the interface definition. Remember, the interface class is only there to
|
|
188
|
+
represent the data source to the model. Yours will most likely be no more complex than the one
|
|
189
|
+
above. Since they are only accessed by the model, my preference is to define them in an internal
|
|
190
|
+
class, but if that makes you back away slowly waving your hands placatingly, put it in another
|
|
191
|
+
file. Pod4 is fine with that.
|
|
199
192
|
|
|
200
|
-
Inside your interface class you must call some DSLish methods to tell the
|
|
201
|
-
|
|
202
|
-
the ones for PgInterface are pretty common:
|
|
193
|
+
Inside your interface class you must call some DSLish methods to tell the interface how to talk to
|
|
194
|
+
the data. What they are depends on the interface, but the ones for PgInterface are pretty common:
|
|
203
195
|
|
|
204
196
|
* `set_schema` -- optional -- the name of the schema to find the table in
|
|
205
197
|
* `set_table` -- mandatory -- the name of the database table to use
|
|
206
198
|
* `set_id_fld` -- mandatory -- the name of the column that makes the record unique
|
|
207
199
|
|
|
208
|
-
Actually, _every_ interface defines `set_id_fld`. Instances of a model _must_ be
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
makes it unique, that's good enough.
|
|
200
|
+
Actually, _every_ interface defines `set_id_fld`. Instances of a model _must_ be represented by a
|
|
201
|
+
single ID field that provides a unique identifier. Pod4 does not care what it's called or what data
|
|
202
|
+
type it is -- if you say that's what makes it unique, that's good enough.
|
|
212
203
|
|
|
213
|
-
Internally, Interfaces talk the same basic language of list / create / read /
|
|
214
|
-
|
|
215
|
-
|
|
204
|
+
Internally, Interfaces talk the same basic language of list / create / read / update / delete that
|
|
205
|
+
models do. But I'm not finding the need to subclass these much. So that's probably going to be it
|
|
206
|
+
for your Interface definition.
|
|
216
207
|
|
|
217
208
|
### Model ###
|
|
218
209
|
|
|
@@ -221,16 +212,14 @@ Models have two of their own DSLish methods:
|
|
|
221
212
|
* `set_interface` -- here is where you instantiate your Interface class
|
|
222
213
|
* `attr_columns` -- like `attr_accessor`, but letting the model know to care.
|
|
223
214
|
|
|
224
|
-
You can see that interfaces are instantiated when the model is required.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
only want connection hashes.
|
|
215
|
+
You can see that interfaces are instantiated when the model is required. Exactly what you need to
|
|
216
|
+
pass to the interface to instantiate it depends on the interface. SequelInterface wants the Sequel
|
|
217
|
+
DB object (which means you have to require sequel, connect, and *then* require your models); the
|
|
218
|
+
other interfaces only want connection hashes.
|
|
229
219
|
|
|
230
|
-
Any attributes you define using `attr_columns` are treated specially by
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
and from your external code, by the standard model methods.
|
|
220
|
+
Any attributes you define using `attr_columns` are treated specially by Pod4::Model. You get all
|
|
221
|
+
the effect of the standard Ruby `attr_accessor` call, but in addition, the attribute will be passed
|
|
222
|
+
to and from the interface, and to and from your external code, by the standard model methods.
|
|
234
223
|
|
|
235
224
|
In addition to the ones above, we have:
|
|
236
225
|
|
|
@@ -250,20 +239,18 @@ We'll deal with all these below.
|
|
|
250
239
|
Adding Validation
|
|
251
240
|
-----------------
|
|
252
241
|
|
|
253
|
-
Built into the model is an array of alerts (Pod4::Alert) which are messages
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
`model_status` attribute.
|
|
242
|
+
Built into the model is an array of alerts (Pod4::Alert) which are messages that have been raised
|
|
243
|
+
against the instance of the model class. Each alert can have a status of :error, :warning, :info or
|
|
244
|
+
:success. If any alert has a status of :error :warning or :success then that is reflected in the
|
|
245
|
+
model's `model_status` attribute.
|
|
258
246
|
|
|
259
|
-
(
|
|
247
|
+
(In fact, there are two other possible statuses -- models are :empty when first created
|
|
260
248
|
and :deleted after a call to delete.)
|
|
261
249
|
|
|
262
|
-
You can raise alerts yourself, and you normally do so by overriding `validate`.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
application.
|
|
250
|
+
You can raise alerts yourself, and you normally do so by overriding `validate`. This method is
|
|
251
|
+
called after a read as well as when you write to the database; so that a model object should always
|
|
252
|
+
have a model_status reflecting its "correctness" regardless of whether it came from the data source
|
|
253
|
+
or your application.
|
|
267
254
|
|
|
268
255
|
Here's a model with some validation:
|
|
269
256
|
|
|
@@ -294,29 +281,50 @@ Here's a model with some validation:
|
|
|
294
281
|
(Note: as a general principal, you should always call super when overriding a
|
|
295
282
|
method in Pod4 model, unless you have good reason not to.)
|
|
296
283
|
|
|
297
|
-
If the model has a status of :error, then an
|
|
298
|
-
delete, however, will succeed -- if you want to create validation
|
|
299
|
-
delete operation, you should override the `delete` method and only call super
|
|
300
|
-
|
|
284
|
+
Validation is run on create, read, update and delete. If the model has a status of :error, then an
|
|
285
|
+
update or create will fail. A delete, however, will succeed -- if you want to create validation
|
|
286
|
+
that aborts a delete operation, you should override the `delete` method and only call super if the
|
|
287
|
+
validation passes.
|
|
301
288
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
validate it before that, you must call `validate` by hand.
|
|
289
|
+
In passing I should note that validation is _not_ run on list: every record that list returns
|
|
290
|
+
should be complete, but the `model_status` will be :empty because validation has not been run.
|
|
291
|
+
(This is partly for the sake of speed.)
|
|
306
292
|
|
|
293
|
+
You should be aware that validation is not called on `set`, either. Because of that, it's entirely
|
|
294
|
+
possible to set a model to an invalid state and not raise any alerts against it until you go to
|
|
295
|
+
commit to the database. If you want to change the state of the model and then validate it before
|
|
296
|
+
that, you must call `validate` yourself.
|
|
307
297
|
|
|
308
|
-
|
|
298
|
+
### Conditional Validation for CRUD modes ###
|
|
299
|
+
|
|
300
|
+
If you want to write validation that only fires on some of :create, :read, :update or :delete --
|
|
301
|
+
for example, to stop deletion if a foreign key points to another record that exists -- then you
|
|
302
|
+
have two options. The recomended way to do this is to add a parameter to your `validate()` method:
|
|
303
|
+
|
|
304
|
+
def validate(vmode)
|
|
305
|
+
super
|
|
306
|
+
add_alert(:error, "foo") if vmode == :delete && bar
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
There's a little bit of magic here; when you override `validate()` you can choose to give it a
|
|
310
|
+
parameter or not; either way will work. The value passed to the parameter will either be :create,
|
|
311
|
+
:read, :update or :delete.
|
|
312
|
+
|
|
313
|
+
Your second option is to override the create/read/update/delete method, instead. Just remember to return
|
|
314
|
+
self, and only call super if you want the operation to go ahead.
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
Changine How a Model Represents Data
|
|
309
318
|
------------------------------------
|
|
310
319
|
|
|
311
|
-
Pod4 will do the basic work for you when it comes to data types. integers,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
aren't you?) But maybe you want more than that.
|
|
320
|
+
Pod4 will do the basic work for you when it comes to data types. integers, decimals, dates and
|
|
321
|
+
datatimes should all end up as the right type in the model. (It depends on the Interface. You're
|
|
322
|
+
going to get tired of me saying that, aren't you?) But maybe you want more than that.
|
|
315
323
|
|
|
316
|
-
Let's imagine you have a database table in PostreSQL with a column called cost
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
324
|
+
Let's imagine you have a database table in PostreSQL with a column called cost that uses the money
|
|
325
|
+
type. And you want it to be a `BigDecimal` in the model. Well, Pod4 won't do that for you -- for
|
|
326
|
+
all I know someone might have a problem with my requiring BigDecimal -- but it's not hard to do
|
|
327
|
+
yourself.
|
|
320
328
|
|
|
321
329
|
class Product < Pod4::Model
|
|
322
330
|
|
|
@@ -340,17 +348,17 @@ with my requiring BigDecimal -- but it's not hard to do yourself.
|
|
|
340
348
|
|
|
341
349
|
end
|
|
342
350
|
|
|
343
|
-
`map_to_model` gets called when the model wants to write data from the interface
|
|
344
|
-
|
|
345
|
-
|
|
351
|
+
`map_to_model` gets called when the model wants to write data from the interface on the model; it
|
|
352
|
+
takes an Octothorpe from the interface as a parameter. By default it behaves as `set` does.
|
|
353
|
+
|
|
354
|
+
`map_to_interface` is the opposite: it gets called when the model wants to write data on the
|
|
355
|
+
interface from the model. It _returns_ an Octothorpe to the interface. By default it behaves as
|
|
356
|
+
`to_ot` does. (Since OTs are read only, you must modify it using merge.)
|
|
346
357
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
interface. By default it behaves as `to_ot` does. (Since OTs are read only, you
|
|
350
|
-
must modify it using merge.)
|
|
358
|
+
You might also want to ensure that your data types are honoured when your application updates a
|
|
359
|
+
model object; in which case you will need to override `set` as well.
|
|
351
360
|
|
|
352
|
-
|
|
353
|
-
fine to put a call to `add_alert` in `map_to_model`.
|
|
361
|
+
At some point in the future, the Pod4::TypeCasting mixin will do most of this for you.
|
|
354
362
|
|
|
355
363
|
|
|
356
364
|
Relations
|
|
@@ -374,7 +382,7 @@ Pod4 does not provide relations. But, I'm not sure that it needs to. Look:
|
|
|
374
382
|
|
|
375
383
|
class Comment < Pod4::Model
|
|
376
384
|
|
|
377
|
-
class
|
|
385
|
+
class CommentInterface < Pod4::PgInterface
|
|
378
386
|
set_table :comment
|
|
379
387
|
set_id_fld :id
|
|
380
388
|
end
|
|
@@ -385,23 +393,22 @@ Pod4 does not provide relations. But, I'm not sure that it needs to. Look:
|
|
|
385
393
|
def blog_post; BlogPost.new(@post_id).read.or_die; end
|
|
386
394
|
end
|
|
387
395
|
|
|
388
|
-
So the BlogPost model has a comments method that returns an array of Comments,
|
|
389
|
-
|
|
390
|
-
|
|
396
|
+
So the BlogPost model has a comments method that returns an array of Comments, and the Comments
|
|
397
|
+
model has a blog_post method that returns the BlogPost. (You would probably want to add validation
|
|
398
|
+
to enforce relational integrity.)
|
|
391
399
|
|
|
392
|
-
Is this approach inefficient? Possibly. But if you don't like it, you can
|
|
393
|
-
always try:
|
|
400
|
+
Is this approach inefficient? Possibly. But if you don't like it, you can always try:
|
|
394
401
|
|
|
395
402
|
|
|
396
403
|
Beyond CRUD (& List)
|
|
397
404
|
--------------------
|
|
398
405
|
|
|
399
|
-
Sooner or later you will want to do something more than Pod4::Model will give
|
|
400
|
-
|
|
401
|
-
|
|
406
|
+
Sooner or later you will want to do something more than Pod4::Model will give you automatically.
|
|
407
|
+
There is a perfectly well documented, very popular DSL with lots of examples to solve this problem.
|
|
408
|
+
It's called SQL.
|
|
402
409
|
|
|
403
|
-
If your interface is connected to a SQL database, it should provide two more
|
|
404
|
-
|
|
410
|
+
If your interface is connected to a SQL database, it should provide two more methods: `execute` and
|
|
411
|
+
`select`.
|
|
405
412
|
|
|
406
413
|
class BlogPost < Pod4::Model
|
|
407
414
|
|
|
@@ -436,33 +443,30 @@ methods: `execute` and `select`.
|
|
|
436
443
|
|
|
437
444
|
end
|
|
438
445
|
|
|
439
|
-
Neither `execute` nor `select` care about the table or ID field you passed to the
|
|
440
|
-
|
|
441
|
-
|
|
446
|
+
Neither `execute` nor `select` care about the table or ID field you passed to the interface. They
|
|
447
|
+
only run pure SQL. The only difference between them is that select expects to return an array of
|
|
448
|
+
results.
|
|
442
449
|
|
|
443
|
-
To my way of thinking, there is absolutely nothing wrong about using SQL in a
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
revisit the effected models anyway...
|
|
450
|
+
To my way of thinking, there is absolutely nothing wrong about using SQL in a model. It will
|
|
451
|
+
certainly need revisiting if you change database. But how often does that happen, really? And if
|
|
452
|
+
it ever does, you are likely to need to revisit the effected models anyway...
|
|
447
453
|
|
|
448
454
|
|
|
449
455
|
BasicModel
|
|
450
456
|
----------
|
|
451
457
|
|
|
452
|
-
Sometimes your model needs to represent data in a way which is so radically
|
|
453
|
-
|
|
454
|
-
|
|
458
|
+
Sometimes your model needs to represent data in a way which is so radically different from the data
|
|
459
|
+
source that the whole list, create, read, update, delete thing that Pod4::Model gives you is no
|
|
460
|
+
use. Enter Pod4::BasicModel.
|
|
455
461
|
|
|
456
|
-
A real world example: at James Hall my intranet system has a User model, where
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
user is the most sensible thing.
|
|
462
|
+
A real world example: at James Hall my intranet system has a User model, where each attribute is a
|
|
463
|
+
parameter that controls how the system behaves for that user -- email address, security settings,
|
|
464
|
+
etc. Having one object to represent the user is the most sensible thing.
|
|
460
465
|
|
|
461
|
-
But I don't want to have to add a column to the database each time I change the
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
name.
|
|
466
|
+
But I don't want to have to add a column to the database each time I change the intranet system and
|
|
467
|
+
add a user parameter. The logical place to change the parameter is in the User model, not in the
|
|
468
|
+
database, and certainly not both. So on the database, I have a settings table where the key runs:
|
|
469
|
+
userid, setting name.
|
|
466
470
|
|
|
467
471
|
Pod4::BasicModel gives you:
|
|
468
472
|
|
|
@@ -470,12 +474,12 @@ Pod4::BasicModel gives you:
|
|
|
470
474
|
* the `model_id`, `model_status` and `alerts` attributes
|
|
471
475
|
* `add_alert`
|
|
472
476
|
|
|
473
|
-
...and nothing else. But that's enough to make a model, your way, using the
|
|
474
|
-
|
|
475
|
-
|
|
477
|
+
...and nothing else. But that's enough to make a model, your way, using the methods on the
|
|
478
|
+
interface. These are the same CRUDL methods that Pod4::Model provides -- except that the CRUD
|
|
479
|
+
methods take a record id as a key.
|
|
476
480
|
|
|
477
|
-
Here's a simplified version of my User model. This one is read only, but it's
|
|
478
|
-
|
|
481
|
+
Here's a simplified version of my User model. This one is read only, but it's hopefully enough to
|
|
482
|
+
get the idea:
|
|
479
483
|
|
|
480
484
|
class User < Pod4::BasicModel
|
|
481
485
|
|
data/lib/pod4/basic_model.rb
CHANGED
|
@@ -87,7 +87,7 @@ module Pod4
|
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
##
|
|
90
|
-
# Raise a
|
|
90
|
+
# Raise a Pod4 exception for the model if any alerts are status :error; otherwise do
|
|
91
91
|
# nothing.
|
|
92
92
|
#
|
|
93
93
|
# Note the alias of or_die for this method, which means that if you have kept to the idiom of
|
data/lib/pod4/errors.rb
CHANGED
|
@@ -62,6 +62,20 @@ module Pod4
|
|
|
62
62
|
##
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
##
|
|
66
|
+
# Raised by an interface if it would like Model to stop and create an Alert, but not actually
|
|
67
|
+
# fall over in any way.
|
|
68
|
+
#
|
|
69
|
+
class WeakError < Pod4Error
|
|
70
|
+
|
|
71
|
+
def initialize(msg=nil)
|
|
72
|
+
super(msg || $! && $!.message)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
##
|
|
77
|
+
|
|
78
|
+
|
|
65
79
|
##
|
|
66
80
|
# Raised if validation fails (and you wanted an exception...)
|
|
67
81
|
#
|
|
@@ -80,5 +94,6 @@ module Pod4
|
|
|
80
94
|
end
|
|
81
95
|
|
|
82
96
|
|
|
97
|
+
|
|
83
98
|
end
|
|
84
99
|
|
data/lib/pod4/interface.rb
CHANGED
|
@@ -19,12 +19,15 @@ module Pod4
|
|
|
19
19
|
# The methods below are the required ones. Interfaces will likely implement other,
|
|
20
20
|
# interface-specific, ways of accessing data.
|
|
21
21
|
#
|
|
22
|
-
# In Normal use, the interface classes
|
|
22
|
+
# In Normal use, the interface classes may in turn be subclassed as inner classes within each
|
|
23
23
|
# model, in order to customise them for the specific entity that they are drawing data from.
|
|
24
24
|
#
|
|
25
25
|
# Note that your Interface subclass probably returns an Octothorpe rather than a Hash, q.v..
|
|
26
26
|
# (But you should be able to treat the former as if it were the latter in most cases.)
|
|
27
27
|
#
|
|
28
|
+
# Note that if an Interface raises a Pod4::WeakError, then Pod4::Model will catch that and turn
|
|
29
|
+
# it into a Pod4::Alert.
|
|
30
|
+
#
|
|
28
31
|
class Interface
|
|
29
32
|
extend Metaxing
|
|
30
33
|
|