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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fa6907a0507b7978c58578596c4e6ca16b7e334eb98b3fa42cba47a0d0b89b5
4
- data.tar.gz: ed8f3126f69cb0f8780244a05d48e46abd0b64468ad47838f0d5827b4fa943ff
3
+ metadata.gz: 3287b3ffda224e17c32b5601cc2f14aba284bcac50d7627be46300f1c7473521
4
+ data.tar.gz: 9027f931d4dc6781241cd80faacf056d320307e94fb0a1ded2e8563bc7d86b59
5
5
  SHA512:
6
- metadata.gz: 5d35b26bf717252dc30d828b2fff2b5b36c82ccd5b33d5480798cc57e38bacf9434120d2a478d448998c368286272a9dc2604cb8d3ebd071da0a55c8fa4ad5ba
7
- data.tar.gz: 3b9c21bb1a3fe42c0799ffe6e991b29f7c17fd4efec8ff3b159afa41cd8a6b8b1e975e47c8e69e22dfd673352b105f2c55d85887a07d90e528b2cd0c54b3a318
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. 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` 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.
@@ -1,3 +1,3 @@
1
1
  module Positioning
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
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
- extend ActiveSupport::Concern
10
+ RelativePosition = Struct.new(:before, :after, keyword_init: true)
11
11
 
12
- class_methods do
13
- def positioning_columns
14
- @positioning_columns ||= {}
15
- end
12
+ module Behaviour
13
+ extend ActiveSupport::Concern
16
14
 
17
- def positioned(on: [], column: :position)
18
- unless base_class?
19
- raise Error.new "can't be called on an abstract class or STI subclass."
15
+ class_methods do
16
+ def positioning_columns
17
+ @positioning_columns ||= {}
20
18
  end
21
19
 
22
- column = column.to_sym
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
- if positioning_columns.key? column
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
- (reflection && reflection.belongs_to?) ? reflection.foreign_key : scope_component
32
- end
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
- define_method(:"prior_#{column}") { Mechanisms.new(self, column).prior }
35
- define_method(:"subsequent_#{column}") { Mechanisms.new(self, column).subsequent }
34
+ (reflection && reflection.belongs_to?) ? reflection.foreign_key : scope_component
35
+ end
36
36
 
37
- redefine_method(:"#{column}=") do |position|
38
- send :"#{column}_will_change!"
39
- super(position)
40
- end
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
- before_create { Mechanisms.new(self, column).create_position }
43
- before_update { Mechanisms.new(self, column).update_position }
44
- after_destroy { Mechanisms.new(self, column).destroy_position }
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: positioning
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brendon Muir