Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## [Unreleased]

- Fix the healer not locking scopes without a `belongs_to` association (or with a nil one).
- A position writer defined in the model now composes with positioning's via `super`.

## [0.4.7] - 2025-08-09

Expand Down
12 changes: 8 additions & 4 deletions lib/positioning.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,14 @@ def positioned(on: [], column: :position)
define_method(:"prior_#{column}") { Mechanisms.new(self, column).prior }
define_method(:"subsequent_#{column}") { Mechanisms.new(self, column).subsequent }

redefine_method(:"#{column}=") do |position|
send :"#{column}_will_change!"
super(position)
end
# Defined in a module so that a writer defined in the model itself can call super to reach it,
# rather than replacing it (or being replaced by it).
include(Module.new do
define_method(:"#{column}=") do |position|
send :"#{column}_will_change!"
super(position)
end
end)

before_create { Mechanisms.new(self, column).create_position }
before_update { Mechanisms.new(self, column).update_position }
Expand Down
9 changes: 9 additions & 0 deletions test/models/custom_writer_item.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class CustomWriterItem < ActiveRecord::Base
belongs_to :list

positioned on: :list

def position=(position)
super((position == "top") ? :first : position)
end
end
8 changes: 8 additions & 0 deletions test/support/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ def configure_connection

add_index :products, :position, unique: true

create_table :custom_writer_items, force: true do |t|
t.string :name
t.integer :position, null: false
t.references :list, null: false
end

add_index :custom_writer_items, [:list_id, :position], unique: true

create_table :categorised_items, force: true do |t|
t.string :name
t.integer :position, null: false
Expand Down
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
require_relative "models/author/student"
require_relative "models/author/teacher"
require_relative "models/blog"
require_relative "models/custom_writer_item"
require_relative "models/post"
require_relative "models/entity"
16 changes: 16 additions & 0 deletions test/test_positioning.rb
Original file line number Diff line number Diff line change
Expand Up @@ -652,6 +652,22 @@ def test_that_the_default_list_scope_works
Positioning::Mechanisms.new(second_item, :position).send(:positioning_scope).order(:position)
end

def test_that_a_custom_position_writer_composes_with_ours
first_list = List.create name: "First List"
second_list = List.create name: "Second List"
first_item = CustomWriterItem.create list: first_list, name: "First Item"
second_item = CustomWriterItem.create list: first_list, name: "Second Item"
third_item = CustomWriterItem.create list: second_list, name: "Third Item"
fourth_item = CustomWriterItem.create list: second_list, name: "Fourth Item"

second_item.update position: "top"
assert_equal [1, 2], [second_item.reload, first_item.reload].map(&:position)

# Assigning the same position it already has still counts as an explicit position in the new scope
second_item.update list: second_list, position: 1
assert_equal [1, 2, 3], [second_item.reload, third_item.reload, fourth_item.reload].map(&:position)
end

def test_that_destroyed_via_positioning_scope_does_not_call_contract
list = List.create name: "First List"
list.items.create name: "First Item"
Expand Down
Loading