oort 0.1.0 → 0.1.2

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: 2482601efe735a7063d6d22932bc2026ee1cdd456e29d6adf1e875d52845b3ed
4
- data.tar.gz: 3020190a7db7c6eae9088046952e42dac96dbacc48eb7ffe64990191f3802182
3
+ metadata.gz: 9d13ddbadfac1d2c1ae4f77c6c81ebac6339ddfdb14b3e41a40da8ba93e62ae2
4
+ data.tar.gz: fae5d70f39bc682f322c16f6e969b64e01794f9fdd87ed50264fb44ce3beb54a
5
5
  SHA512:
6
- metadata.gz: 215ba707002ebe3b5732f071eabd62e093f2480814736d10ae59f79f9b6b7c8fc20cb3a46efd2858fcc855fffa1c400e1fdf75c82c40e3e7351106d53aa4faa6
7
- data.tar.gz: 73df1b6f0e2ec22d2b6fc66d1158e47bd863af85ef3b344ef2bd1a12955a5ea365555bf63afd2ee731c86618cbc3827e5e8987a6e80d0a4eeae90ce4d9eddf73
6
+ metadata.gz: aa7621267ccc7d99735bd5548eb7dbdcce7c4b3e280540dd393d4cd45707b0b671f9213eb55c01023c42a710afed5f31163213eead118b0db35d22365cc9fac1
7
+ data.tar.gz: e0e6d7d5a3d2752ac7747439f28256aceb7183e773418836c05896b3f8ade18dbed010b41a54f05a08360f76c8039ac23c87e5900444867491e7405582552bd6
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ gem "rubocop", require: false
10
10
 
11
11
  group :development, :test do
12
12
  gem "activerecord", "~> 7.0", ">= 7.0.1"
13
+ gem "benchmark"
13
14
  gem "pg"
14
15
  gem "zeitwerk", "~> 2.5"
15
16
  end
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- oort (0.1.0)
4
+ oort (0.1.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -24,6 +24,7 @@ GEM
24
24
  tzinfo (~> 2.0)
25
25
  ast (2.4.2)
26
26
  base64 (0.2.0)
27
+ benchmark (0.2.1)
27
28
  bigdecimal (3.1.6)
28
29
  concurrent-ruby (1.2.3)
29
30
  connection_pool (2.4.1)
@@ -68,9 +69,11 @@ GEM
68
69
 
69
70
  PLATFORMS
70
71
  x86_64-darwin-20
72
+ x86_64-darwin-22
71
73
 
72
74
  DEPENDENCIES
73
75
  activerecord (~> 7.0, >= 7.0.1)
76
+ benchmark
74
77
  minitest
75
78
  oort!
76
79
  pg
data/README.md CHANGED
@@ -1,6 +1,94 @@
1
1
  # Oort
2
2
 
3
- Ordering your collections without deadlocks.
3
+ #### Rails sorting and ordering without deadlocks.
4
+
5
+ *Rails and PostgreSQL only (for now).*
6
+
7
+ Typically, ordering involves adding a position column to records and rearranging the entire collection when altering the sort order. However, this approach is prone to deadlocks and places a heavy load on the database, especially when modifying multiple records simultaneously.
8
+
9
+ Oort provides an alternative solution by allowing the order to be stored in an array column on the parent object. Any changes to the sort order become a simple modification to a single column.
10
+
11
+
12
+ ## Instructions
13
+
14
+ Let's begin with a basic schema involving a User and a Post. You can substitute these entities as needed; just ensure that handles_ordering_of has a corresponding has_many association. (Replace instances of post with your own association in the following examples.)
15
+
16
+ ### Migration
17
+
18
+ Firstly, you will also need the following migration to users (postgresql only for now):
19
+
20
+ ```RUBY
21
+ def change
22
+ add_column(:users, :posts_ordering, :integer, array: true, default: [], using: 'ARRAY[benefit_type]::INTEGER[]')
23
+
24
+ add_check_constraint :users, '(array_position(posts_ordering, null) is null)', name: 'posts_ordering'
25
+ end
26
+ ```
27
+
28
+ This will store the ids of posts in an array on the user, and will only accept integers to prevent any nasty surprises.
29
+
30
+ ### Model
31
+
32
+ Include the `Oort::Ordered` module in the parent object:
33
+
34
+ ```RUBY
35
+ class User < ActiveRecord::Base
36
+ include Oort::Ordered
37
+ handles_ordering_of :posts
38
+
39
+ has_many :posts
40
+ end
41
+
42
+ class Post < ActiveRecord::Base
43
+ belongs_to :user
44
+ end
45
+
46
+ ```
47
+
48
+ This inclusion adds `update_posts_ordering` to the user model and `insert_at` to the posts model. It also introduces removal methods: `remove_from_posts_ordering` for the user model and `remove_from_reorderable` for the posts model.
49
+
50
+
51
+ ### Callbacks
52
+
53
+ The following callbacks are also added to post:
54
+
55
+ ```RUBY
56
+ after_create_commit :insert_at
57
+ after_destroy :remove_from_reorderable
58
+ ```
59
+
60
+ These callbacks automatically insert a new post at the first position and remove a destroyed post from the user's list.
61
+
62
+ ### Usage
63
+ To change to order of a post, simply call `post.insert_at(12)`
64
+ To remove a post, simply call `post.remove_from_reorderable`
65
+
66
+ ### Scope
67
+ The `ordered_with` scope is also added to the post model. This allows a `user` object to have the following query:
68
+
69
+ ```RUBY
70
+ user.posts.ordered_with(user.posts_ordering)
71
+
72
+ # or
73
+
74
+ Post.where(user_id: user.id).ordered_with(user.posts_ordering)
75
+ ```
76
+
77
+ ### Customization
78
+
79
+ `handles_ordering_of` can allow for new records to be the top of the order or the bottom of the order. By default it will push any new records to the top of the order, but you can specify the bottom of the order like so:
80
+
81
+ ```ruby
82
+
83
+ class User < ActiveRecord::Base
84
+ include Oort::Ordered
85
+ handles_ordering_of :posts, default: :bottom
86
+
87
+ has_many :posts
88
+ end
89
+
90
+ ```
91
+
4
92
 
5
93
  ## Installation
6
94
 
@@ -2,22 +2,24 @@
2
2
 
3
3
  module Oort
4
4
  class Callbacks
5
- def self.call(association_class:, remove_from_method_name:, insert_method_name:, instance_name:)
5
+ def self.call(association_class:, remove_from_method_name:, insert_method_name:, instance_name:, default:)
6
6
  new(
7
7
  association_class: association_class,
8
8
  remove_from_method_name: remove_from_method_name,
9
9
  insert_method_name: insert_method_name,
10
- instance_name: instance_name
10
+ instance_name: instance_name,
11
+ default: default
11
12
  ).call
12
13
  end
13
14
 
14
- attr_reader :association_class, :remove_from_method_name, :insert_method_name, :instance_name
15
+ attr_reader :association_class, :remove_from_method_name, :insert_method_name, :instance_name, :default
15
16
 
16
- def initialize(association_class:, remove_from_method_name:, insert_method_name:, instance_name:)
17
+ def initialize(association_class:, remove_from_method_name:, insert_method_name:, instance_name:, default:)
17
18
  @association_class = association_class
18
19
  @remove_from_method_name = remove_from_method_name
19
20
  @insert_method_name = insert_method_name
20
21
  @instance_name = instance_name
22
+ @default = default
21
23
  end
22
24
 
23
25
  def call
@@ -29,7 +31,7 @@ module Oort
29
31
 
30
32
  def add_callbacks
31
33
  association_class.class_eval do
32
- after_create_commit :insert_at
34
+ after_create_commit :initial_insert_at
33
35
  after_destroy :remove_from_reorderable
34
36
  end
35
37
  end
@@ -45,9 +47,13 @@ module Oort
45
47
  # public_send(:user).public_send(:remove_from_posts_ordering, id)
46
48
  # end
47
49
  <<-RUBY, __FILE__, __LINE__ + 1
48
- def insert_at(position = 0)
50
+ def insert_at(position = 0, initial: nil)
49
51
  public_send(#{instance_name.inspect})
50
- .public_send(#{insert_method_name.inspect}, insert: id, at: position)
52
+ .public_send(#{insert_method_name.inspect}, insert: id, at: position, initial: initial)
53
+ end
54
+
55
+ def initial_insert_at
56
+ insert_at(initial: #{default.inspect})
51
57
  end
52
58
 
53
59
  def remove_from_reorderable
data/lib/oort/inserts.rb CHANGED
@@ -26,13 +26,22 @@ module Oort
26
26
  # end
27
27
  # end
28
28
  <<-RUBY, __FILE__, __LINE__ + 1
29
- def #{insert_method_name}(insert:, at: 0)
29
+ def #{insert_method_name}(insert:, at: 0, initial: nil)
30
30
  with_lock do
31
31
  current_values = public_send(#{stored_in.inspect})
32
- current_index = current_values.find_index(insert)
33
- insertable = current_index.blank? ? insert : current_values.delete_at(current_index)
34
- current_values.insert(at, insertable)
35
- update(#{stored_in.inspect} => current_values)
32
+
33
+ if initial == :top
34
+ current_values.unshift(insert)
35
+ save
36
+ elsif initial == :bottom
37
+ current_values << insert
38
+ save
39
+ else
40
+ current_index = current_values.find_index(insert)
41
+ insertable = current_index.blank? ? insert : current_values.delete_at(current_index)
42
+ current_values.insert(at, insertable)
43
+ update(#{stored_in.inspect} => current_values)
44
+ end
36
45
  end
37
46
  end
38
47
  RUBY
data/lib/oort/ordered.rb CHANGED
@@ -14,20 +14,23 @@ module Oort
14
14
  end
15
15
 
16
16
  module ClassMethods
17
- def handles_ordering_of(association)
17
+ def handles_ordering_of(association, default: :top)
18
18
  args = {
19
19
  stored_in: :"#{association}_ordering",
20
20
  insert_method_name: :"update_#{association}_ordering",
21
21
  remove_from_method_name: :"remove_from_#{association}_ordering",
22
22
  association_class: association.to_s.classify.constantize,
23
23
  instance_name: :"#{name.downcase}",
24
- class_name: name.classify.constantize
24
+ class_name: name.classify.constantize,
25
+ default:
25
26
  }
26
27
 
27
28
  Inserts.call(**args.slice(:stored_in, :insert_method_name, :class_name))
28
29
  Removes.call(**args.slice(:stored_in, :remove_from_method_name, :class_name))
29
30
  Scopes.call(**args.slice(:association_class))
30
- Callbacks.call(**args.slice(:association_class, :remove_from_method_name, :insert_method_name, :instance_name))
31
+ Callbacks.call(
32
+ **args.slice(:association_class, :remove_from_method_name, :insert_method_name, :instance_name, :default)
33
+ )
31
34
  end
32
35
  end
33
36
  end
data/lib/oort/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oort
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
data/oort.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/oort/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "oort"
7
+ spec.version = Oort::VERSION
8
+ spec.authors = ["tobyond"]
9
+ spec.homepage = "https://github.com/tobyond/oort"
10
+
11
+ spec.summary = "Oort to sort, order"
12
+ spec.description = "Rails sorting and ordering without deadlocks"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 3.2.0"
15
+
16
+ spec.metadata["source_code_uri"] = "https://github.com/tobyond/oort"
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(__dir__) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
23
+ end
24
+ end
25
+ spec.bindir = "exe"
26
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ["lib"]
28
+
29
+ # Uncomment to register a new dependency of your gem
30
+ # spec.add_dependency "rspec"
31
+
32
+ # For more information and examples about making a new gem, check out our
33
+ # guide at: https://bundler.io/guides/creating_gem.html
34
+ end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oort
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - tobyond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-26 00:00:00.000000000 Z
11
+ date: 2024-03-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Oort to sort, order
13
+ description: Rails sorting and ordering without deadlocks
14
14
  email:
15
15
  executables: []
16
16
  extensions: []
@@ -40,8 +40,9 @@ files:
40
40
  - lib/oort/removes.rb
41
41
  - lib/oort/scopes.rb
42
42
  - lib/oort/version.rb
43
+ - oort.gemspec
43
44
  - sig/oort.rbs
44
- homepage:
45
+ homepage: https://github.com/tobyond/oort
45
46
  licenses:
46
47
  - MIT
47
48
  metadata:
@@ -61,7 +62,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
62
  - !ruby/object:Gem::Version
62
63
  version: '0'
63
64
  requirements: []
64
- rubygems_version: 3.4.10
65
+ rubygems_version: 3.5.3
65
66
  signing_key:
66
67
  specification_version: 4
67
68
  summary: Oort to sort, order