in_order 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +759 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/in_order_manifest.js +2 -0
  6. data/app/assets/javascripts/in_order/application.js +15 -0
  7. data/app/assets/stylesheets/in_order/application.css +15 -0
  8. data/app/controllers/in_order/application_controller.rb +5 -0
  9. data/app/controllers/in_order/concerns/response_helpers.rb +33 -0
  10. data/app/controllers/in_order/elements_controller.rb +47 -0
  11. data/app/controllers/in_order/lists_controller.rb +86 -0
  12. data/app/helpers/in_order/application_helper.rb +4 -0
  13. data/app/jobs/in_order/application_job.rb +4 -0
  14. data/app/mailers/in_order/application_mailer.rb +6 -0
  15. data/app/models/in_order/add.rb +51 -0
  16. data/app/models/in_order/application_record.rb +5 -0
  17. data/app/models/in_order/aux/create_element.rb +17 -0
  18. data/app/models/in_order/aux/element_iterator.rb +39 -0
  19. data/app/models/in_order/aux/get_element.rb +17 -0
  20. data/app/models/in_order/aux/get_keys.rb +27 -0
  21. data/app/models/in_order/aux/keys.rb +58 -0
  22. data/app/models/in_order/aux/poly_find.rb +24 -0
  23. data/app/models/in_order/aux/poly_key.rb +117 -0
  24. data/app/models/in_order/aux/position_base.rb +25 -0
  25. data/app/models/in_order/aux/repair.rb +71 -0
  26. data/app/models/in_order/aux/sort_elements.rb +32 -0
  27. data/app/models/in_order/aux/var_keys.rb +20 -0
  28. data/app/models/in_order/create.rb +62 -0
  29. data/app/models/in_order/element.rb +119 -0
  30. data/app/models/in_order/fetch.rb +33 -0
  31. data/app/models/in_order/insert.rb +42 -0
  32. data/app/models/in_order/move.rb +15 -0
  33. data/app/models/in_order/purge.rb +71 -0
  34. data/app/models/in_order/queue.rb +65 -0
  35. data/app/models/in_order/remove.rb +29 -0
  36. data/app/models/in_order/stack.rb +12 -0
  37. data/app/models/in_order/trim.rb +74 -0
  38. data/app/models/in_order/update.rb +83 -0
  39. data/app/models/in_order.rb +9 -0
  40. data/app/views/in_order/lists/_list.html.erb +16 -0
  41. data/app/views/in_order/lists/index.html.erb +9 -0
  42. data/app/views/layouts/in_order/application.html.erb +16 -0
  43. data/config/routes.rb +10 -0
  44. data/db/migrate/20190814101433_create_in_order_elements.rb +15 -0
  45. data/lib/in_order/engine.rb +5 -0
  46. data/lib/in_order/version.rb +3 -0
  47. data/lib/in_order.rb +5 -0
  48. data/lib/tasks/in_order_tasks.rake +4 -0
  49. metadata +119 -0
data/README.md ADDED
@@ -0,0 +1,759 @@
1
+
2
+ In Order
3
+ ========
4
+
5
+ Overview
6
+ --------
7
+
8
+ This is a (small simple single-purpose) Rails engine that,
9
+ in technical terms,
10
+ provides a uni-directional persistent linked list (presently SQL-based).
11
+
12
+ It's to establish one-to-many relationships between a composite *key*
13
+ and an ordered collection of *ActiveRecord* models.
14
+
15
+ The *key* is a combination of a single *ActiveRecord* model and a
16
+ free-text string, one of which is mandatory.
17
+
18
+ It allows you to introduce ad hoc relationships between arbitrary records
19
+ without changing anything in the participating records themselves.
20
+
21
+ Uses
22
+ ----
23
+
24
+ Its potential uses are manifold, in fact, there are too many to mention.
25
+ In some guise or other, the basic functionality is used everywhere,
26
+ and it's a foundation of many patterns.
27
+
28
+ In everyday web development, the API will mainly be used to
29
+ store particular users' preferences, selections and actions.
30
+ Especially if preserving the order of the related elements is important.
31
+
32
+ Some Use-Cases
33
+ --------------
34
+
35
+ The API could be used to support the development of the following:
36
+
37
+ - Listing a user's favoured or recently accessed records.
38
+ So that they may, for example,
39
+ appear at the top of various lists used in HTML elements,
40
+ such as *select options* or *table* rows or menu links.
41
+
42
+ - For session storage of things like search terms, bookmarked links,
43
+ current records and notifications.
44
+
45
+ - Displaying user-specific recommendations, linking users to groups,
46
+ intermediate storage for multi-page wizards, tagging, contextual lists,
47
+ queuing jobs, auditing, tracking changes, etc..
48
+
49
+ - To log things like page clicks, form submissions, login attempts,
50
+ API calls, record edits & IP addresses by user, etc..
51
+
52
+ - Recording messages or conversations.
53
+
54
+ - Drag'n'drop lists, for which a rudementary *Stimumlus* module is included.
55
+ This is for things like to-do lists,
56
+ task or page ordering,
57
+ allowing users to specify menu items,
58
+ and so on.
59
+
60
+ Actually, the API can be used for all sorts of aggregation, notably,
61
+ if the underlying associations need to be kept in a particular sequence
62
+ and/or are volatile or temporary or experimental.
63
+
64
+ Benefits and Shortcomings
65
+ -------------------------
66
+
67
+ Although this facility carries an inherent inefficiency -
68
+ it creates a new *join table* row for each linkage -
69
+ it has the advantage of unobtrusiveness, that is,
70
+ of not affecting, in any way, the records it links.
71
+ They remain fully intact.
72
+
73
+ Note that there are plenty of other ways
74
+ to keep a discrete group of models in a specific order,
75
+ for example *ActiveRecord scopes* and third-party
76
+ facilities like *acts\_as\_list*.
77
+ In the majority of cases,
78
+ one of these will be more suitable than this facility,
79
+ which requires that each sequence be explicitly set up,
80
+ element by element.
81
+
82
+ An installation of this engine requires only one major change to your
83
+ existing environment, namely, the addition of an SQL migration
84
+ to create a table, *in\_order\_elements*.
85
+ The API is accessible from within its own engine,
86
+ which has no external dependencies apart from Rails.
87
+ This means that you can give it a try with
88
+ minimal setup and an easy rollback.
89
+
90
+ By far, the thorniest issue in using this facility
91
+ is getting rid of redundant associations.
92
+ A few ways to avert and rectify this are explained below.
93
+
94
+ Contents
95
+ --------
96
+
97
+ This engine consists of the following:
98
+
99
+ 1. A single SQL table, *in\_order\_elements*.
100
+ 2. An extensive API, which is composed of around two dozen Ruby classes.
101
+ 3. Two REST controllers providing limited API access.
102
+ 4. A prototype *Stimulus* module for a drag'n'drop list.
103
+
104
+ The last two items are quite specialised,
105
+ so are less likely to satisfy any specific requirements you have.
106
+
107
+ It was developed with *Ruby 2.6.0* and *Rails 5.2.3*.
108
+
109
+ API Introduction
110
+ ----------------
111
+
112
+ The public API comprises of a dozen Ruby classes that provide
113
+ a full gamut of ways to manipulate the elements of a list.
114
+
115
+ Most of these classes perform a simple atomic operation,
116
+ and have only a few input parameters and options.
117
+
118
+ Sometimes, you may only need to call two of the supplied classes -
119
+ one to update and one to retrieve - *both short one-line calls*.
120
+
121
+ In more complicated cases,
122
+ you may need to make a few calls to these classes in succession.
123
+
124
+ > The *minitest* unit tests cover the API usage comprehensively,
125
+ > but these tests go into more detail than you'll need!
126
+
127
+ API Calls
128
+ ---------
129
+
130
+ Although the API has lots of classes,
131
+ for the most part, they work in similar ways,
132
+ i.e. in the parameters they accept,
133
+ and in the tasks they perform (there is a fair degree of overlap).
134
+
135
+ Nearly all of the calls can be made in isolation
136
+ with a single (Ruby) statement,
137
+ and for most requirements,
138
+ you'll only need to use a small subset of the available classes.
139
+
140
+ They fall into three main categories:
141
+
142
+ 1. To retrieve the elements of an existing list, by supplying a composite *key*.
143
+
144
+ 2. To create or update a list as a whole, by supplying a composite *key*.
145
+
146
+ 3. To change a list by dealing with individual elements.
147
+
148
+ Identifying Lists
149
+ -----------------
150
+
151
+ As said, many of the API calls require a special unique *key*,
152
+ which is used to access individual lists.
153
+
154
+ This *key* has two components:
155
+
156
+ 1. An instance of an *ActiveRecord* model,
157
+ or else a reference to one by some sort of pairing of
158
+ a *type* name and an (SQL) *id*.
159
+
160
+ 2. A string, which, in practice, will be a general classifier,
161
+ such as the name of a section, group, branch, label, tag, etc..
162
+
163
+ You must give either one or both.
164
+
165
+ > Internally, the composite *key* is represented by the class,
166
+ > *InOrder::Aux::Keys* (aka *InOrder::Key*),
167
+ > but you shouldn't have to refer to this directly.
168
+ > Instead, you specify the actual *key* values as one or two parameters,
169
+ > which are passed to the constructor of the aforesaid class internally.
170
+
171
+ The Element Class
172
+ -----------------
173
+
174
+ This is an *ActiveRecord* model, which links the *key*
175
+ to an associated record.
176
+
177
+ #### Fields
178
+
179
+ Its name is *InOrder::Element*, and it has the fields:
180
+
181
+ 1. *owner* which is the initial key component,
182
+ it's a polymorphically referenced model.
183
+
184
+ 2. *scope* which is the final key component,
185
+ it's a string designating some kind of category.
186
+
187
+ 3. *subject* which is a related record (usually one of many),
188
+ it's also a polymorphically referenced model.
189
+
190
+ 4. *element\_id* which points to the next Element of the list.
191
+
192
+ #### Scopes
193
+
194
+ For these four fields, there are corresponding
195
+ *(ActiveRecord) scope* definitions,
196
+ which are prefixed with **by_**.
197
+ So, for example, you can find elements that have a certain linked record with:
198
+
199
+ - `InOrder::Element.by_subject(subject)`
200
+
201
+ #### Class methods
202
+
203
+ Also, there are a number of class methods that
204
+ that return details about a list,
205
+ and others, with wider usefulness,
206
+ that delete an entire list in one go.
207
+
208
+ _What you're allowed to give as the **key** is explained below._
209
+
210
+ - To see a list's length, use something like:
211
+
212
+ - `InOrder::Element.find_by_key(key).count`
213
+
214
+ - To retrieve a list's first or last item:
215
+
216
+ - `InOrder::Element.first_element(key)`
217
+ - `InOrder::Element.last_element(key)`
218
+
219
+ - To check if a list includes a particular model:
220
+
221
+ - `InOrder::Element.has_subject?(key) { a_model_to_check }`
222
+
223
+ You specify the model to look for in a block,
224
+ this is because the *key* can be given as varied arguments.
225
+ You can also specify a reference to a model with a *type & id*
226
+ partnership, these formats are shown below.
227
+
228
+ - Finally, to delete a complete list, you have a few choices:
229
+
230
+ - `InOrder::Element.delete_elements(key)`
231
+ - `InOrder::Element.delete_list(key)`
232
+ - `InOrder::Element.by_keys(key).delete_all`
233
+
234
+ The first deletes the elements one at a time,
235
+ the last two are equivalent.
236
+
237
+ #### Iterator
238
+
239
+ Additionally, there is another class, `InOrder::Iterator.new(key)`,
240
+ that behaves like a regular Ruby *Enumerable*,
241
+ that is, the method *each* yields an *Element* in turn.
242
+ You could use this for random access with, say, an integer index (offset).
243
+ _Be restrained in using this, though, because it hits the database hard._
244
+
245
+ Retrieve List Items
246
+ -------------------
247
+
248
+ The principal API class to access a list is *InOrder::Fetch*,
249
+ which returns an Array of models, obviously, in the stated order.
250
+
251
+ _This does eager fetching of the linked models,
252
+ so reducing the request to a couple of SQL queries._
253
+
254
+ This API call takes just a *key* as input,
255
+ the following examples show the differing ways of specifying this.
256
+
257
+ **These different formats also apply to the other
258
+ API calls requiring a *key* as an input parameter.**
259
+
260
+ These invocations are all valid,
261
+ and, except for the last four, would all produce the same results:
262
+
263
+ - `InOrder::Fetch.new(User.find(999), 'friends').call`
264
+ - `InOrder::Fetch.new([ 'User', 999 ], 'friends').call`
265
+ - `InOrder::Fetch.new('User-999', 'friends').call`
266
+ - `InOrder::Fetch.new({ type: 'User', id: '999' }, 'friends').call`
267
+ - `InOrder::Fetch.new(owner: User.find(999), scope: 'friends').call`
268
+ - `InOrder::Fetch.new(InOrder::Key.new(User.find(999), 'friends')).elements`
269
+ - `InOrder::Fetch.new('User 999').call`
270
+ - `InOrder::Fetch.new(owner: 'User:999').call`
271
+ - `InOrder::Fetch.new(owner: { type: 'User', id: '999' }).call`
272
+ - `InOrder::Fetch.new(scope: 'friends').call`
273
+
274
+ The method *call* returns the linked records themselves.
275
+
276
+ If you want the linking Element records instead,
277
+ you use the method, *elements*, in place of *call*.
278
+
279
+ As said, the Element class has an instance method, *subject*,
280
+ which returns the linked record.
281
+
282
+ Create a List
283
+ -------------
284
+
285
+ When adding records to a list, you can give actual instances,
286
+ or give references like:
287
+
288
+ - `[ 'Type', 999 ]`
289
+ - `{ type: 'Type', id: 999 }`
290
+ - `"Type-999"`
291
+
292
+ ### Adding multiple records at once
293
+
294
+ There are two classes for creating a list
295
+ with more than one associated record.
296
+ These are *InOrder::Create* and *InOrder::Update*,
297
+ the latter performs other tasks as well, and is explained later on.
298
+
299
+ - `InOrder::Create.new(User.find(999), 'friends').call(a_prepared_list_of_friends)`
300
+
301
+ If you make either of these API calls (*Create* or *Update*)
302
+ when a list (with an identical key) pre-exists,
303
+ then the new records will, by default, be appended to the original items,
304
+ that is, docked onto the end.
305
+ If the option, *append*, is set to *false*,
306
+ then the new items will be prepended instead, as in:
307
+
308
+ - `InOrder::Create.new(User.find(999), 'friends').call(a_prepared_list_of_friends, append: false)`
309
+
310
+ These methods return the linking Element records,
311
+ not the records that are actually linked.
312
+
313
+ And, importantly, for *Create* (but not *Update*) the returned list will not
314
+ include any of the items from a previous list - only the ones you just added.
315
+
316
+ > Be aware that you can only add *ActiveRecord* models as list item subjects.
317
+
318
+ ### Adding a single record to a list
319
+
320
+ To add one new record to either the top or bottom of a list:
321
+
322
+ - `InOrder::Add.new(User.find(999), 'friends').call(a_friend)`
323
+
324
+ This will append *a\_friend* to an existing list.
325
+ If there's no list present already,
326
+ it'll become the first (and last) element.
327
+
328
+ To prepend the record, i.e. put it at the beginning:
329
+
330
+ - `InOrder::Add.new(User.find(999), 'friends').call(a_friend, at: top)`
331
+
332
+ You can also use the methods: *prepend* and *append*, in place of *call*.
333
+ The *at* option is omitted when invoking *prepend*, e.g.
334
+
335
+ - `InOrder::Add.new(User.find(999), 'friends').prepend(a_friend)`
336
+
337
+ These *Add* calls return the new *Element*, which wraps the added record,
338
+ but, by itself, this won't normally be useful.
339
+
340
+ Although you can add just one item in the *Create* call, mentioned above,
341
+ using *Add* is a bit more efficient,
342
+ and it has the following extra feature.
343
+
344
+ #### Adding a record in the middle
345
+
346
+ Also, but with less efficiency and more difficulty,
347
+ you can put a new item at any position by adding a block of custom code,
348
+ in which you step through the list to find a particular place to go.
349
+ Here's a rough contrived example:
350
+
351
+ ```
352
+ InOrder::Add.new(a_key).insert(a_record) do |iterator, a_record|
353
+ # This returns the element that a_record will be put after
354
+ iterator.find(iterator.first) do |element|
355
+ element.subject == a_record
356
+ end
357
+ end
358
+ ```
359
+ _As you can see, this is convoluted, so in nearly all cases
360
+ it'll be simpler to position a new item using *Insert*, shown below._
361
+
362
+ ### *Singing and dancing* record addition
363
+
364
+ If you want, for instance,
365
+ to add one or more models to an existing list at the beginning,
366
+ to ensure that the list has a length no bigger than *6*,
367
+ and that no items re-occur, use:
368
+
369
+ - `InOrder::Update.new(User.find(999), 'friends').call(an_array_of_some_fiends, append: false, max: 6, uniq: true)`
370
+
371
+ This *Update* call will return the whole new list, as elements.
372
+ If you want the linked records instead, use *subjects* in place of *call*.
373
+
374
+ If *uniq* is given as *false* then any re-occurrences (in the original list)
375
+ of the added models will remain in place, (i.e. not be removed from the list).
376
+ The default setting is *true*, so be sure to specify (*uniq: false*) if
377
+ you want to keep repetitions.
378
+
379
+ Remove List Items
380
+ -----------------
381
+
382
+ As you'd expect, all of the following deletion operations only remove
383
+ the linking (wrapper) records, **never** the (underlying) records
384
+ that are linked together.
385
+
386
+ Likewise, if you delete a model that had been previously linked,
387
+ the (surviving) list *Element* will be left with a *dangling reference*,
388
+ i.e. it'll be an orphan, and be worse than useless,
389
+ as it may give rise to malfunctions or even fatal errors.
390
+
391
+ There are a number of straight-forward ways of preventing this,
392
+ (e.g. with *dependent: :destroy*, or inside of an *after\_destroy* callback),
393
+ but they do involve changing code of the model that's being linked.
394
+
395
+ In emergencies, you can search for and destroy these erroneous records, with:
396
+
397
+ - `InOrder::Repair.new.call`
398
+
399
+ If you wish to root them out without deleting, try:
400
+
401
+ - `InOrder::Repair.ic`
402
+
403
+ For ultra-caution, you can bolster your fetch requests
404
+ by sticking an integrity fix into the mix.
405
+ It'd go like something like this:
406
+
407
+ - `InOrder::Fetch.new(User.find(999)).repair.call`
408
+
409
+ But this (*repair*) invocation is slow and only
410
+ really suggested as a stop-gap measure.
411
+
412
+ > A *subscribe and publish* mechanism, (activated on a model's destruction)
413
+ > would resolve this issue. *This may be added in future.*
414
+
415
+ ### Deleting elements
416
+
417
+ Take note that removing an Element is not so simple as just deleting a
418
+ single row from the (elements) table.
419
+ Since there'll be a reference to the deleted Element
420
+ if a previous Element exists, and conversely,
421
+ the deleted Element might have a reference to a further element.
422
+
423
+ #### Delete by subject(s)
424
+
425
+ To drop elements from a *keyed* list that have an existing link
426
+ to one or more given models, (in this example, *enemies*):
427
+
428
+ - `InOrder::Purge.new(User.find(999), 'friends').remove(some_enemies)`
429
+
430
+ #### Mass destruction of elements
431
+
432
+ For the reason given directly above - on orphan eradication
433
+ *(Please don't presume I advocate this as social policy!)* -
434
+ when you delete the subject of a link,
435
+ you may want to make the following call alongside:
436
+
437
+ - `InOrder::Purge.delete_by_subject(a_deleted_model)`
438
+
439
+ This will unlink (and remove) all Elements that wrap *a\_deleted\_model*,
440
+ regardless of the lists the (deleted model's) Elements belong to.
441
+
442
+ ### Discarding duplicates
443
+
444
+ To ensure that there are no two (linked) models the same in a given list:
445
+
446
+ - `InOrder::Purge.new(User.find(999), 'friends').uniq(keep_last: true)`
447
+
448
+ If the *keep_last* option is left out, or is *false*,
449
+ it'll retain the first occurrence of a repeated model in a given list.
450
+ The method *call* is an alias for *uniq*.
451
+
452
+ ### Cutting a list down to size
453
+
454
+ Ensures that a list does not exceed a given maximum size (ceiling).
455
+
456
+ In this example, the maximum's *12*, the unwanted items are removed towards
457
+ the start, and not permanently dropped from the list:
458
+
459
+ - `InOrder::Trim.new(12, destroy: false, take_from: top).call(a_list_of_elements)`
460
+
461
+ Note that this call does not take an identifying *key* as input,
462
+ rather, it takes a list of *Elements*,
463
+ obtained by calling *InOrder::Fetch.new(a_key).elements*.
464
+
465
+ And also that the default is to remove the excess elements for good,
466
+ so you must add **destroy: false** to avoid this.
467
+
468
+ This call returns a new (duplicated and abridged) list of Elements.
469
+
470
+ #### Cutting down on the number of calls
471
+
472
+ To fetch and trim a list (temporarily), returning the linked records,
473
+ you may be tempted to try something like this:
474
+
475
+ ```
476
+ MAX = 12
477
+
478
+ key = InOrder::Key.new(current_user, 'friends')
479
+
480
+ elements = InOrder::Fetch.new(key).elements
481
+
482
+ InOrder::Trim.new(MAX).call(elements, destroy: false).map(&:subject)
483
+ ```
484
+
485
+ But you can achieve the same, with less verbosity *(sidestepping RSI)*,
486
+ using the shortcuts:
487
+
488
+ - `InOrder::Trim.set_max(12).call(current_user, 'friends')`
489
+
490
+ Whereupon the maximum will be temporarily stored,
491
+ obviating the need to call *set_max* again,
492
+ if you, soon afterwards, make a similar call, as in:
493
+
494
+ - `InOrder::Trim.(current_user, 'best-friends')`
495
+
496
+ Alternatively, to trim from the beginning
497
+ and with permanent element removal, you may use:
498
+
499
+ - `InOrder::Trim.call(current_user, 'friends') { [ 12, take_from: :top, destroy: true ] }`
500
+
501
+ Here, the default for *destroy* is flipped
502
+ (in the longer form it's *true*),
503
+ and the block returns the argument list given to Trim's constructor.
504
+
505
+ > These (Trim) calls raise an exception if you attempt to 'destroy',
506
+ > and you also give a list of items that aren't *Element* types.
507
+
508
+ Stacks and Queues
509
+ -----------------
510
+
511
+ These are typically data structures with a short lifespan.
512
+
513
+ In both of them, there is also a *peek* method that shows the next
514
+ item without making a removal.
515
+
516
+ ### First in, first out
517
+
518
+ To add an item:
519
+
520
+ - `InOrder::Stack.new(User.find(999), 'friends').push(a_model)`
521
+
522
+ To retrieve an item:
523
+
524
+ - `InOrder::Stack.new(User.find(999), 'friends').pop
525
+
526
+ ### First in, last out
527
+
528
+ To add an item:
529
+
530
+ - `InOrder::Queue.new(User.find(999), 'friends').join(Club.find(999))`
531
+
532
+ The method *join* has an alias of *add*.
533
+
534
+ To retrieve an item:
535
+
536
+ - `InOrder::Queue.new(User.find(999), 'friends').call
537
+
538
+ The method *call* has an alias of *leave* and *pull*.
539
+
540
+ ### Fixed size
541
+
542
+ When you put a new item onto these lists,
543
+ you may specify, as a second argument,
544
+ a length that the list will not go beyond.
545
+ For example, `InOrder::Stack.new(key).push(model, 7)`.
546
+
547
+ > The behaviour of these two classes can be replicated
548
+ > using other API calls. They are for convenience.
549
+
550
+ Operations on List Elements
551
+ ---------------------------
552
+
553
+ The following calls do not take a *key* as input to the constructor.
554
+ Here, you supply references to the individual Elements that make up a list.
555
+
556
+ Unlike the preceding, these calls give you full control over
557
+ the positioning of Elements in an existing list.
558
+
559
+ There are three parameters used to add or move a certain item:
560
+
561
+ 1. *target* which is an actual Element that'll be moved or added.
562
+
563
+ 2. *marker* is an Element that indicates the new position.
564
+
565
+ 3. *adjacency* has a value of 'after' (default) or 'before',
566
+ and says whether the *target* will go ahead of,
567
+ or behind, the *marker* Element.
568
+
569
+ Both *target* and *marker* need to be instances of *InOrder::Element*,
570
+ or else be (SQL) *id's* of such.
571
+
572
+ ### Moving an existing item
573
+
574
+ - `InOrder::Move.new(target, marker, adjacency).call`
575
+
576
+ ### Positioning a new item
577
+
578
+ - `InOrder::Insert.new(target, marker, adjacency).call`
579
+
580
+ If you don't have the new record already wrapped in an Element,
581
+ which will be the most common case, use:
582
+
583
+ - `InOrder::Insert.call(a_model_to_be_linked, marker, adjacency)`
584
+
585
+ ### Deleting an item
586
+
587
+ - `InOrder::Remove.new(target).call`
588
+
589
+ Controllers
590
+ -----------
591
+
592
+ There are two REST controllers included as well.
593
+ You can can use them for JSON output,
594
+ or to alter a list from an *Ajax* request.
595
+
596
+ However, at present the views they have,
597
+ in the engine's *dummy* Rails app,
598
+ are inaccessible from your own app, and besides they're crude,
599
+ but they may potentially be used as rough starter templates.
600
+
601
+ - *InOrder::ListsController* this accepts the said composite *key*
602
+ as parameters, and will fetch a list and add & delete items.
603
+
604
+ - *InOrder::ElementsController* this accepts Element id's as parameters
605
+ and allows more control over the placement of Elements.
606
+ The *drag'n'drop* list, outlined just below, uses this controller.
607
+
608
+ Generally, these controllers won't be need to be used as much as the,
609
+ already described, Ruby API, which is more direct, concise and flexible.
610
+
611
+ A Drag'n'Drop List
612
+ ------------------
613
+
614
+ The engine has, hidden away,
615
+ a little *Stimulus* module that implements a *drag'n'drop* list.
616
+
617
+ This consists of three new files:
618
+
619
+ 1. *test/dummy/app/javascript/controllers/drag\_drop\_controller.js*
620
+ 2. *test/dummy/app/javascript/controllers/list\_controller.js*
621
+ 3. *app/views/in\_order/lists/\_list.html.erb*
622
+
623
+ This code is minimal and will need changing.
624
+ The third probably a lot.
625
+ The second a bit as it shows debugging info.
626
+ The first probably won't need altering at all.
627
+
628
+ This list works in the engine's *dummy* testing Rails app,
629
+ which, for now, is where you'll have to go to extract these files.
630
+ If you decide to do this, get the source from:
631
+ [github.com/srycyk/in_order](https://github.com/srycyk/in_order)
632
+
633
+ > It's hard to package this up at present,
634
+ > since Rails is in flux over the way that JS assets are prepared,
635
+ > that is, whether they be handled by Sprockets or Webpacker.
636
+ > Also, it needs *Stimulus* installed - but not *jQuery*.
637
+
638
+ General Remarks
639
+ ---------------
640
+
641
+ #### What's a linked list?
642
+
643
+ A broad definition of a *linked list* may go thus:
644
+ it's is a chain of elements,
645
+ each of which contain both a reference to an opaque data item,
646
+ and also a pointer to the next element in the sequence.
647
+ The final element typically has its pointer valued as null.
648
+
649
+ However, in this particular implementation,
650
+ the element also contains a *key*.
651
+
652
+ #### Polymorphic reliance
653
+
654
+ Purists may demur at the unabashed use of *polymorphic* types.
655
+
656
+ But since these (associative) records are, in this case,
657
+ the leaves of a tree, (never part of a branch),
658
+ there is next to no chance of untoward issues surfacing.
659
+ And the alternative of crafting every particular relationship with
660
+ literal *table* names is too long-winded,
661
+ and much more bother to develop and maintain.
662
+
663
+ #### Demonstration
664
+
665
+ Inside of the engine, in the sub-directory *test/dummy/*,
666
+ there's a Raila app.
667
+ Apart from supporting the tests, this app has some scaffolding code
668
+ added that allows you to link records together manually in web pages.
669
+ To have a go, `cd test/dummy/`, run migrations & seed, `rails s`, etc..
670
+
671
+ #### Multiple databases
672
+
673
+ The table behind this model, *in\_order\_elements*,
674
+ could be put on a separate database,
675
+ but it'd make the eager fetching tricky.
676
+
677
+ But if you're going to use this facility just to gather statistics,
678
+ doing this would certainly boost the overall performance.
679
+
680
+ Getting Started
681
+ ---------------
682
+
683
+ Add a *Gemfile* entry, with one of these:
684
+
685
+ ```ruby
686
+ gem 'in_order'
687
+
688
+ gem 'in_order', git: 'https://github.com/srycyk/in_order'
689
+ ```
690
+
691
+ And then execute:
692
+
693
+ ```bash
694
+ $ bundle
695
+ ```
696
+
697
+ Mount the engine in *config/routes.rb* with:
698
+
699
+ ```ruby
700
+ mount InOrder::Engine, at: "/in_order"
701
+ ```
702
+
703
+ Copy the migration, from the engine, over into your own app:
704
+
705
+ ```bash
706
+ $ rake in_order:install:migrations
707
+ ```
708
+
709
+ And create the table, *in\_order\_elements*:
710
+
711
+ ```bash
712
+ $ rake db:migrate
713
+ ```
714
+
715
+ Getting Finished
716
+ ----------------
717
+
718
+ Rollback migration (just for the engine):
719
+
720
+ ```bash
721
+ $ rails db:migrate SCOPE=in_order VERSION=0
722
+ ```
723
+
724
+ Delete the engine's migration file:
725
+
726
+ ```bash
727
+ $ git rm db/migrate/*elements.in_order.rb
728
+ ```
729
+
730
+ Remove the *Gemfile* entry.
731
+
732
+ Remove the *config/routes.rb* entry.
733
+
734
+ Then, to get back to where you started:
735
+
736
+ ```bash
737
+ $ bundle
738
+ ```
739
+
740
+ Licence
741
+ -------
742
+
743
+ The gem is available as open source under the terms of
744
+ the [MIT License](https://opensource.org/licenses/MIT).
745
+
746
+
747
+ Contributing
748
+ ------------
749
+
750
+ Since this facility provides just one basic function,
751
+ (frankly, *it's a one-trick pony, flogged for all it's worth*),
752
+ it's unlikely that it will need extending or forking.
753
+ It's more a utility component to be used a building-block.
754
+
755
+
756
+ If you spot any bugs,
757
+ or if you have any issues, queries or suggestions,
758
+ please mail me at stephen.rycyk@googlemail.com
759
+