positioning 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +43 -5
- data/lib/positioning/version.rb +1 -1
- data/lib/positioning.rb +31 -27
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3287b3ffda224e17c32b5601cc2f14aba284bcac50d7627be46300f1c7473521
|
4
|
+
data.tar.gz: 9027f931d4dc6781241cd80faacf056d320307e94fb0a1ded2e8563bc7d86b59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f0a3353940e24c88f89835fee413eef989a67e3ef1a984d8cba2611d377be6a68f0743568090cb5a58da2f6c8f2eb17d15cfa2f4880b4f0b209f4ad582b176f
|
7
|
+
data.tar.gz: 975fa2efc49eacb1dc5a96de826ccc9e6db72f8adb82a4d44f72dff57581cd551581c77458c40b06686c90232aebff127006503369cee3faed0ad704160e8c21
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.7] - 2024-03-06
|
4
|
+
|
5
|
+
- Seperated the Concern that is included into ActiveRecord::Base into its own submodule so that Mechanisms isn't also included.
|
6
|
+
- Added the RelativePosition Struct and documentation to make it easier to supply relative positions via form helpers.
|
7
|
+
|
3
8
|
## [0.1.6] - 2024-03-05
|
4
9
|
|
5
10
|
- Allow the position to be passed as a JSON object so that we can pass in complex positions from the browser more easily.
|
data/README.md
CHANGED
@@ -82,11 +82,13 @@ If you don't provide a position when creating a record, your record will be adde
|
|
82
82
|
|
83
83
|
To assign a specific position when creating or updating a record you can simply declare a specific value for the database column tracking the position of records (by default this is `position`). The valid options for this column are:
|
84
84
|
|
85
|
-
* A specific integer value
|
86
|
-
* `:first` places the record at the start of the list.
|
87
|
-
* `:last` places the record at the end of the list.
|
88
|
-
* `nil` also places the record at the end of the list.
|
89
|
-
* `before:` and `after:` allow you to define the position relative to other records in the list. You can define the relative record by its primary key (usually `id`) or by providing the record itself. You can also provide `nil` in which case the item will be placed at the start or end of the list (see below).
|
85
|
+
* A specific integer value as an `Integer` or a `String`. Values are automatically clamped to between `1` and the next available position at the end of the list (inclusive). You should use explicit position values as a last resort, instead you can use:
|
86
|
+
* `:first` or `"first"` places the record at the start of the list.
|
87
|
+
* `:last` or `"last"` places the record at the end of the list.
|
88
|
+
* `nil` and `""` also places the record at the end of the list.
|
89
|
+
* `before:` and `after:` allow you to define the position relative to other records in the list. You can define the relative record by its primary key (usually `id`) or by providing the record itself. You can also provide `nil` or `""` in which case the item will be placed at the start or end of the list (see below).
|
90
|
+
|
91
|
+
**You can provide the position value as a JSON string and it will be decoded first. This could be useful if you have no other way to provide `before:` or `after:` as a hash (e.g. `"{\"after\":33}"`). See below for a technique to provide `before:` and `after:` using form helpers.**
|
90
92
|
|
91
93
|
Position parameters can be strings or symbols, so you can provide them from the browser.
|
92
94
|
|
@@ -149,6 +151,42 @@ other_item.id # => 11
|
|
149
151
|
item.update position: {after: 11}
|
150
152
|
```
|
151
153
|
|
154
|
+
##### Relative Positining in Forms
|
155
|
+
|
156
|
+
It can be tricky to provide the hash forms of relative positining using Rails form helpers, but it is possible. We've declared a special `Struct` for you to use for this purpose.
|
157
|
+
|
158
|
+
Firstly you need to allow nested Strong Parameters for the `position` column like so:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
def item_params
|
162
|
+
params.require(:item).permit :name, position: [:before]
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
In the example above we're always declaring what item (by its `id`) we want to position our item **before**. You could change this to `:after` if you'd rather.
|
167
|
+
|
168
|
+
Next, in your `new` method you may wish to intialise the `position` column with a value supplied by incoming parameters:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
def new
|
172
|
+
item.position = { before: params[:before] }
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
You can now just pass the `before` parameter (the `id` of the item you want to add this record before) via the URL to the `new` action. For example: `items/new?before=22`.
|
177
|
+
|
178
|
+
In the form itself, so that your intended position survives a failed `create` attempt and form redisplay you can declare the `position` value like so:
|
179
|
+
|
180
|
+
```erb
|
181
|
+
<% if item.new_record? %>
|
182
|
+
<%= form.fields :position, model: Positioning::RelativePosition.new(item.position_before_type_cast) do |fields| %>
|
183
|
+
<%= fields.hidden_field :before %>
|
184
|
+
<% end %>
|
185
|
+
<% end %>
|
186
|
+
```
|
187
|
+
|
188
|
+
The key part here is `Positioning::RelativePosition.new(item.position_before_type_cast)`. `Positioning::RelativePosition` is a `Struct` that can take `before` and `after` as parameters. You should only provide one or the other. Because `position` is an `Integer` column, the hash structure is obliterated when it is assigned but we can still access it with `position_before_type_cast`. Remember to adjust the method if your position column has a different name (e.g. `category_position_before_type_cast`). The `Struct` provides the correct methods for `fields` to display the nested value.
|
189
|
+
|
152
190
|
#### Destroying
|
153
191
|
|
154
192
|
When a record is destroyed, the positions of relative items in the scope will be shuffled to close the gap left by the destroyed record. If we detect that records are being destroyed via a scope dependency (e.g. `has_many :items, dependent: :destroy`) then we skip closing the gaps because all records in the scope will eventually be destroyed anyway.
|
data/lib/positioning/version.rb
CHANGED
data/lib/positioning.rb
CHANGED
@@ -7,46 +7,50 @@ require "active_support/lazy_load_hooks"
|
|
7
7
|
module Positioning
|
8
8
|
class Error < StandardError; end
|
9
9
|
|
10
|
-
|
10
|
+
RelativePosition = Struct.new(:before, :after, keyword_init: true)
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
@positioning_columns ||= {}
|
15
|
-
end
|
12
|
+
module Behaviour
|
13
|
+
extend ActiveSupport::Concern
|
16
14
|
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
class_methods do
|
16
|
+
def positioning_columns
|
17
|
+
@positioning_columns ||= {}
|
20
18
|
end
|
21
19
|
|
22
|
-
|
20
|
+
def positioned(on: [], column: :position)
|
21
|
+
unless base_class?
|
22
|
+
raise Error.new "can't be called on an abstract class or STI subclass."
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
raise Error.new "The column `#{column}` has already been used by the scope `#{positioning_columns[column]}`."
|
26
|
-
else
|
27
|
-
positioning_columns[column] = Array.wrap(on).map do |scope_component|
|
28
|
-
scope_component = scope_component.to_s
|
29
|
-
reflection = reflections[scope_component]
|
25
|
+
column = column.to_sym
|
30
26
|
|
31
|
-
|
32
|
-
|
27
|
+
if positioning_columns.key? column
|
28
|
+
raise Error.new "The column `#{column}` has already been used by the scope `#{positioning_columns[column]}`."
|
29
|
+
else
|
30
|
+
positioning_columns[column] = Array.wrap(on).map do |scope_component|
|
31
|
+
scope_component = scope_component.to_s
|
32
|
+
reflection = reflections[scope_component]
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
(reflection && reflection.belongs_to?) ? reflection.foreign_key : scope_component
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
define_method(:"prior_#{column}") { Mechanisms.new(self, column).prior }
|
38
|
+
define_method(:"subsequent_#{column}") { Mechanisms.new(self, column).subsequent }
|
39
|
+
|
40
|
+
redefine_method(:"#{column}=") do |position|
|
41
|
+
send :"#{column}_will_change!"
|
42
|
+
super(position)
|
43
|
+
end
|
41
44
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
+
before_create { Mechanisms.new(self, column).create_position }
|
46
|
+
before_update { Mechanisms.new(self, column).update_position }
|
47
|
+
after_destroy { Mechanisms.new(self, column).destroy_position }
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
50
54
|
ActiveSupport.on_load :active_record do
|
51
|
-
ActiveRecord::Base.send :include, Positioning
|
55
|
+
ActiveRecord::Base.send :include, Positioning::Behaviour
|
52
56
|
end
|