Skip to content

Commit 1768f61

Browse files
authored
Merge pull request #42 from pyromaniac/fix-nil-scope
Fix optional scope healing
2 parents 4ce54e4 + 403d73c commit 1768f61

File tree

9 files changed

+161
-122
lines changed

9 files changed

+161
-122
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@
88
/tmp/
99
/db/
1010
Gemfile.lock
11+
.ruby-version
12+
.ruby-gemset

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [Unreleased]
22

3+
- Fix healing for positioning with nullable scope (parent in trees, for example). Thanks @pyromaniac!
4+
35
## [0.4.5] - 2024-12-04
46

57
- Fix healing a list with a default scope `:order` and/or `:select`. Thanks @LukasSkywalker!

lib/positioning/healer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def heal
1212
@model.transaction do
1313
if scope_associations.present?
1414
scope_associations.each do |scope_association|
15-
scope_record.send(scope_association).lock!
15+
scope_record.send(scope_association)&.lock!
1616
end
1717
else
1818
@model.where(scope_record.slice(*scope_columns)).lock!

test/models/category.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
class Category < ActiveRecord::Base
2-
has_many :categorised_items, dependent: :destroy
2+
belongs_to :parent, class_name: "Category", optional: true
33

4-
positioned
5-
6-
default_scope { order(:position) }
4+
positioned on: :parent
75
end

test/models/product.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Product < ActiveRecord::Base
2+
positioned
3+
4+
default_scope { order(:position) }
5+
end

test/support/active_record.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require "logger"
12
require "active_record"
23

34
ENV["DB"] = "mysql" unless ENV["DB"]
@@ -57,9 +58,17 @@
5758
create_table :categories, force: true do |t|
5859
t.string :name
5960
t.integer :position, null: false
61+
t.references :parent
6062
end
6163

62-
add_index :categories, :position, unique: true
64+
add_index :categories, [:parent_id, :position], unique: true
65+
66+
create_table :products, force: true do |t|
67+
t.string :name
68+
t.integer :position, null: false
69+
end
70+
71+
add_index :products, :position, unique: true
6372

6473
create_table :categorised_items, force: true do |t|
6574
t.string :name

test/test_healing.rb

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
require "test_helper"
2+
3+
class TestHealing < Minitest::Test
4+
include Minitest::Hooks
5+
6+
def around
7+
ActiveRecord::Base.transaction do
8+
super
9+
raise ActiveRecord::Rollback
10+
end
11+
end
12+
13+
def test_heal_position
14+
first_list = List.create name: "First List"
15+
second_list = List.create name: "Second List"
16+
17+
first_item = first_list.new_items.create name: "First Item"
18+
second_item = first_list.new_items.create name: "Second Item"
19+
third_item = first_list.new_items.create name: "Third Item"
20+
21+
fourth_item = second_list.new_items.create name: "Fourth Item"
22+
fifth_item = second_list.new_items.create name: "Fifth Item"
23+
sixth_item = second_list.new_items.create name: "Sixth Item"
24+
25+
first_item.update_columns position: 9
26+
second_item.update_columns position: nil
27+
third_item.update_columns position: -42
28+
29+
fourth_item.update_columns position: 0
30+
fifth_item.update_columns position: 998
31+
sixth_item.update_columns position: 800
32+
33+
NewItem.heal_position_column!
34+
35+
if ENV["DB"] == "postgresql"
36+
assert_equal [1, 2, 3], [third_item.reload, first_item.reload, second_item.reload].map(&:position)
37+
else
38+
assert_equal [1, 2, 3], [second_item.reload, third_item.reload, first_item.reload].map(&:position)
39+
end
40+
41+
assert_equal [1, 2, 3], [fourth_item.reload, sixth_item.reload, fifth_item.reload].map(&:position)
42+
43+
NewItem.heal_position_column! name: :desc
44+
45+
assert_equal [1, 2, 3], [third_item.reload, second_item.reload, first_item.reload].map(&:position)
46+
assert_equal [1, 2, 3], [sixth_item.reload, fourth_item.reload, fifth_item.reload].map(&:position)
47+
end
48+
49+
def test_heal_position_on_a_tree
50+
first_category = Category.create name: "First Category"
51+
second_category = Category.create name: "Second Category"
52+
third_category = Category.create name: "Third Category", parent: first_category
53+
fourth_category = Category.create name: "Fourth Category", parent: second_category
54+
fifth_category = Category.create name: "Fifth Category", parent: second_category
55+
sixth_category = Category.create name: "Sixth Category", parent: second_category
56+
57+
first_category.update_columns position: 9
58+
second_category.update_columns position: 0
59+
third_category.update_columns position: -42
60+
fourth_category.update_columns position: 998
61+
fifth_category.update_columns position: 800
62+
sixth_category.update_columns position: 1000
63+
64+
Category.heal_position_column!
65+
66+
assert_equal [1, 2, 1], [second_category.reload, first_category.reload, third_category.reload].map(&:position)
67+
assert_equal [1, 2, 3], [fifth_category.reload, fourth_category.reload, sixth_category.reload].map(&:position)
68+
end
69+
70+
def test_heal_position_with_no_scope
71+
first_product = Product.create name: "First Product"
72+
second_product = Product.create name: "Second Product"
73+
third_product = Product.create name: "Third Product"
74+
75+
first_product.update_columns position: 9
76+
second_product.update_columns position: 0
77+
third_product.update_columns position: -42
78+
79+
Product.heal_position_column!
80+
81+
assert_equal [1, 2, 3], [third_product.reload, second_product.reload, first_product.reload].map(&:position)
82+
end
83+
84+
def test_heal_position_with_default_scope
85+
first_list = List.create name: "First List"
86+
87+
first_item = first_list.default_scope_items.create name: "First Item"
88+
second_item = first_list.default_scope_items.create name: "Second Item"
89+
third_item = first_list.default_scope_items.create name: "Third Item"
90+
91+
first_item.update_columns position: 10
92+
second_item.update_columns position: 15
93+
third_item.update_columns position: 5
94+
95+
DefaultScopeItem.heal_position_column!
96+
97+
assert_equal [1, 2, 3], [third_item.reload, first_item.reload, second_item.reload].map(&:position)
98+
end
99+
end

test/test_helper.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,18 @@
55
require "minitest/hooks/test"
66
require "minitest/autorun"
77
require "mocha/minitest"
8+
9+
require_relative "models/list"
10+
require_relative "models/item"
11+
require_relative "models/new_item"
12+
require_relative "models/default_scope_item"
13+
require_relative "models/composite_primary_key_item"
14+
require_relative "models/product"
15+
require_relative "models/category"
16+
require_relative "models/categorised_item"
17+
require_relative "models/author"
18+
require_relative "models/author/student"
19+
require_relative "models/author/teacher"
20+
require_relative "models/blog"
21+
require_relative "models/post"
22+
require_relative "models/entity"

0 commit comments

Comments
 (0)