positioning 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d2088ce4125fed14b6422ec4e02e1ca5a203d724f0cfaafc043ff7b7c0465a03
4
+ data.tar.gz: 9aa3da89865ab6e2336bcb00a7259b97bd69bc3c639b0495c551a1feb33c24fe
5
+ SHA512:
6
+ metadata.gz: 737adf268dda74000333e2f3d316c58d162d13385a219e3b48668fffb04a7a0b55f197dba5d19b58b2f32b8f1496c10c8dc432b293fffde1142053719facfdc2
7
+ data.tar.gz: 8165738f13801f4d8bb8f749c19afe0939a9d6b2e5907038c9b10d7777f7af6daf6f463eac1fc981288c3fba21c69ce19bfa722988d814cdac68509926a40233
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 3.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-02-07
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in positioning.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 13.0"
7
+
8
+ gem "minitest", "~> 5.0"
9
+
10
+ gem "standard", "~> 1.3"
data/Gemfile.lock ADDED
@@ -0,0 +1,109 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ positioning (0.1.0)
5
+ activerecord (>= 6.1)
6
+ activesupport (>= 6.1)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (7.1.3)
12
+ activesupport (= 7.1.3)
13
+ activerecord (7.1.3)
14
+ activemodel (= 7.1.3)
15
+ activesupport (= 7.1.3)
16
+ timeout (>= 0.4.0)
17
+ activesupport (7.1.3)
18
+ base64
19
+ bigdecimal
20
+ concurrent-ruby (~> 1.0, >= 1.0.2)
21
+ connection_pool (>= 2.2.5)
22
+ drb
23
+ i18n (>= 1.6, < 2)
24
+ minitest (>= 5.1)
25
+ mutex_m
26
+ tzinfo (~> 2.0)
27
+ ast (2.4.2)
28
+ base64 (0.2.0)
29
+ bigdecimal (3.1.6)
30
+ concurrent-ruby (1.2.3)
31
+ connection_pool (2.4.1)
32
+ drb (2.2.0)
33
+ ruby2_keywords
34
+ i18n (1.14.1)
35
+ concurrent-ruby (~> 1.0)
36
+ json (2.7.1)
37
+ language_server-protocol (3.17.0.3)
38
+ lint_roller (1.1.0)
39
+ minitest (5.22.1)
40
+ minitest-hooks (1.5.1)
41
+ minitest (> 5.3)
42
+ mocha (2.1.0)
43
+ ruby2_keywords (>= 0.0.5)
44
+ mutex_m (0.2.0)
45
+ mysql2 (0.5.6)
46
+ parallel (1.24.0)
47
+ parser (3.3.0.5)
48
+ ast (~> 2.4.1)
49
+ racc
50
+ pg (1.5.5)
51
+ racc (1.7.3)
52
+ rainbow (3.1.1)
53
+ rake (13.1.0)
54
+ regexp_parser (2.9.0)
55
+ rexml (3.2.6)
56
+ rubocop (1.59.0)
57
+ json (~> 2.3)
58
+ language_server-protocol (>= 3.17.0)
59
+ parallel (~> 1.10)
60
+ parser (>= 3.2.2.4)
61
+ rainbow (>= 2.2.2, < 4.0)
62
+ regexp_parser (>= 1.8, < 3.0)
63
+ rexml (>= 3.2.5, < 4.0)
64
+ rubocop-ast (>= 1.30.0, < 2.0)
65
+ ruby-progressbar (~> 1.7)
66
+ unicode-display_width (>= 2.4.0, < 3.0)
67
+ rubocop-ast (1.30.0)
68
+ parser (>= 3.2.1.0)
69
+ rubocop-performance (1.20.2)
70
+ rubocop (>= 1.48.1, < 2.0)
71
+ rubocop-ast (>= 1.30.0, < 2.0)
72
+ ruby-progressbar (1.13.0)
73
+ ruby2_keywords (0.0.5)
74
+ sqlite3 (1.7.2-arm64-darwin)
75
+ sqlite3 (1.7.2-x86_64-linux)
76
+ standard (1.33.0)
77
+ language_server-protocol (~> 3.17.0.2)
78
+ lint_roller (~> 1.0)
79
+ rubocop (~> 1.59.0)
80
+ standard-custom (~> 1.0.0)
81
+ standard-performance (~> 1.3)
82
+ standard-custom (1.0.2)
83
+ lint_roller (~> 1.0)
84
+ rubocop (~> 1.50)
85
+ standard-performance (1.3.1)
86
+ lint_roller (~> 1.1)
87
+ rubocop-performance (~> 1.20.2)
88
+ timeout (0.4.1)
89
+ tzinfo (2.0.6)
90
+ concurrent-ruby (~> 1.0)
91
+ unicode-display_width (2.5.0)
92
+
93
+ PLATFORMS
94
+ arm64-darwin-21
95
+ x86_64-linux
96
+
97
+ DEPENDENCIES
98
+ minitest (~> 5.0)
99
+ minitest-hooks (~> 1.5.1)
100
+ mocha (~> 2.1.0)
101
+ mysql2 (~> 0.5.6)
102
+ pg (~> 1.5.5)
103
+ positioning!
104
+ rake (~> 13.0)
105
+ sqlite3 (~> 1.7.2)
106
+ standard (~> 1.3)
107
+
108
+ BUNDLED WITH
109
+ 2.3.8
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 Brendon Muir
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # Positioning
2
+
3
+ The aim of this gem is to allow you to easily position Active Record model instances within a scope of your choosing. In an ideal world this gem will give your model instances sequential integer positions beginning with `1`. Attempts are made to make all changes within a transaction so that position integers remain consistent. To this end, directly assigning a position is discouraged, instead you can move items by declaring an item's prior or subsequent item in the list and your item will be moved to be relative to that item.
4
+
5
+ Positioning supports multiple lists per model with global, simple, and complex scopes.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'positioning'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle install
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install positioning
22
+
23
+ ## Usage
24
+
25
+ In the simplest case our database column should be named `position` and not allow `NULL` as a value:
26
+
27
+ `add_column :items, :position, :integer, null: false`
28
+
29
+ You should also add an index to ensure that the `position` column value is unique within its scope:
30
+
31
+ `add_index :items, [:list_id, :position], unique: true`
32
+
33
+ The above assumes that your items are scoped to a parent table called `lists`.
34
+
35
+ The Positioning gem uses `0` and negative integers to rearrange the lists it manages so don't add database validations to restrict the usage of these. You are also restricted from using `0` and negative integers as position values. If you try, the position value will become `1`. If you try to set an explicit position value that is greater than the next available list position, it will be rounded down to that value.
36
+
37
+ ### Declaring Positioning
38
+
39
+ To declare that your model should keep track of the position of its records you can use the `positioned` method. Here are some examples:
40
+
41
+ ```ruby
42
+ # The scope is global (all records will belong to the same list) and the databse column
43
+ # is 'positioned'
44
+ positioned
45
+
46
+ # The scope is on the belongs_to relationship 'list' and the databse column is 'positioned'
47
+ # We check if the scope is a belongs_to relationship and use its declared foreign_key as
48
+ # the scope value. In this case it would be 'list_id' since we haven't overridden the
49
+ # default foreign key.
50
+ belongs_to :list
51
+ positioned on: :list
52
+
53
+ # If you want to change the database column used to record positions you can do so via the
54
+ # ':column' parameter. This is most useful when you are keeping track of more than one
55
+ # list on a model.
56
+ belongs_to :list
57
+ belongs_to :category
58
+ positioned on: :list
59
+ positioned on: :category, column: :category_position
60
+
61
+ # A scope need not be a belongs_to relationship; it can be any column in the database table.
62
+ positioned on: :type
63
+
64
+ # Finally, you can have more complex scopes defined as an array of relationships and/or
65
+ # columns.
66
+ belongs_to :list
67
+ belongs_to :category
68
+ positioned on: [:list, :category, :enabled]
69
+ ```
70
+
71
+ ### Manipulating Positioning
72
+
73
+ The tools for manipulating the position of records in your list have been kept intentionally terse. Priority has also been given to minimal pollution of the model namespace. Only two class methods are defined on all models (`positioning_columns` and `positioned`), and two instance methods are defined on models that call `positioned`:
74
+
75
+ #### Accessing Relative List Items
76
+
77
+ The two instance methods that we add are for finding the prior and subsequent items relative to the current item in the list. These methods are named after the database column used to track positioning. By default the methods are named `prior_position` and `subsequent_position`. In the example above where we used the column `category_position` then the methods would be named `prior_category_position` and `subsequent_category_position`.
78
+
79
+ #### Assigning Positions
80
+
81
+ If you don't provide a position when creating a record, your record will be added to the end of the list.
82
+
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
+
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).
90
+
91
+ Position parameters can be strings or symbols, so you can provide them from the browser.
92
+
93
+ Here are some examples:
94
+
95
+ ##### Creating
96
+
97
+ ```ruby
98
+ # Added to the third position, other records are moved out of the way
99
+ list.items.create name: 'Item', position: 3
100
+
101
+ # Added to the end of the list
102
+ list.items.create name: 'Item'
103
+ list.items.create name: 'Item', position: :last
104
+ list.items.create name: 'Item', position: nil
105
+ list.items.create name: 'Item', position: {before: nil}
106
+
107
+ # Added to the start of the list
108
+ list.items.create name: 'Item', position: :first
109
+ list.items.create name: 'Item', position: {after: nil}
110
+
111
+ # Added before other_item
112
+ list.items.create name: 'Item', position: {before: other_item}
113
+ # or
114
+ other_item.id # => 22
115
+ list.items.create name: 'Item', position: {before: 22}
116
+
117
+ # Added after other_item
118
+ list.items.create name: 'Item', position: {after: other_item}
119
+ # or
120
+ other_item.id # => 11
121
+ list.items.create name: 'Item', position: {after: 11}
122
+ ```
123
+
124
+ ##### Updating
125
+
126
+ ```ruby
127
+ # Moved to the third position, other records are moved out of the way
128
+ item.update position: 3
129
+
130
+ # Moved to the end of the list
131
+ item.update position: :last
132
+ item.update position: nil
133
+ item.update position: {before: nil}
134
+
135
+ # Moved to the start of the list
136
+ item.update position: :first
137
+ item.update position: {after: nil}
138
+
139
+ # Moved to before other_item
140
+ item.update position: {before: other_item}
141
+ # or
142
+ other_item.id # => 22
143
+ item.update position: {before: 22}
144
+
145
+ # Moved to after other_item
146
+ item.update position: {after: other_item}
147
+ # or
148
+ other_item.id # => 11
149
+ item.update position: {after: 11}
150
+ ```
151
+
152
+ #### Destroying
153
+
154
+ 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.
155
+
156
+ #### Scopes
157
+ Positioning handles things for you when you change the scope of a record. If you move a record from one scope to another, the gap in the position column will be healed in the scope the record is leaving, and by default (unless you specify an explicit position) the record will be added to the end of the list in the new scope.
158
+
159
+ Here are some examples of scope management:
160
+
161
+ ```ruby
162
+ # Moved to being the third item in other_list
163
+ item.update list: other_list, position: 3
164
+
165
+ # Moved to the end of other_list
166
+ item.update list: other_list
167
+ item.update list: other_list, position: :last
168
+ item.update list: other_list, position: nil
169
+ item.update list: other_list, position: {before: nil}
170
+
171
+ # Moved to the start of other_list
172
+ item.update list: other_list, position: :first
173
+ item.update list: other_list, position: {after: nil}
174
+
175
+ # Moved to before other_item in other_list
176
+ item.update list: other_list, position: {before: other_item}
177
+ # or
178
+ other_item.id # => 22
179
+ item.update list: other_list, position: {before: 22}
180
+
181
+ # Moved to after other_item in other_list
182
+ item.update list: other_list, position: {after: other_item}
183
+ # or
184
+ other_item.id # => 11
185
+ item.update list: other_list, position: {after: 11}
186
+ ```
187
+
188
+ It's important to note that in the examples above, `other_item` must already belong to the `other_list` scope.
189
+
190
+ ## Development
191
+
192
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
193
+
194
+ This gem is tested against SQLite, PostgreSQL and MySQL. The default database for testing is MySQL. You can target other databases by prepending the environment variable `DB=sqlite` or `DB=postgresql` before `rake test`. For example: `DB=sqlite rake test`.
195
+
196
+ The PostgreSQL and MySQL environments are configured under `test/support/database.yml`. You can edit this file, or preferrably adjust your environment to support passwordless socket based connections to these two database engines. You'll also need to manually create a database named `positioning_test` in each.
197
+
198
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
199
+
200
+ ## Contributing
201
+
202
+ Bug reports and pull requests are welcome on GitHub at https://github.com/brendon/positioning.
203
+
204
+ ## License
205
+
206
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ require "standard/rake"
11
+
12
+ task default: %i[test standard]
@@ -0,0 +1,177 @@
1
+ module Positioning
2
+ class Mechanisms
3
+ def initialize(positioned, column)
4
+ @positioned = positioned
5
+ @column = column.to_sym
6
+ end
7
+
8
+ def prior
9
+ positioning_scope.where("#{@column}": position - 1).first
10
+ end
11
+
12
+ def subsequent
13
+ positioning_scope.where("#{@column}": position + 1).first
14
+ end
15
+
16
+ def create_position
17
+ solidify_position
18
+
19
+ expand(positioning_scope, position..)
20
+ end
21
+
22
+ def update_position
23
+ # If we're changing scope but not explicitly setting the position then we set the position
24
+ # to nil so that the item gets placed at the end of the list.
25
+ self.position = nil if positioning_scope_changed? && !position_changed?
26
+
27
+ solidify_position
28
+
29
+ # The update strategy is to temporarily set our position to 0, then shift everything out of the way of
30
+ # our new desired position before finalising it.
31
+ if positioning_scope_changed? || position_changed?
32
+ record_scope = base_class.where("#{primary_key_column}": primary_key)
33
+
34
+ position_was = record_scope.pick(@column)
35
+ record_scope.update_all "#{@column}": 0
36
+
37
+ if positioning_scope_changed?
38
+ positioning_scope_was = base_class.where record_scope.first.slice(*positioning_columns)
39
+
40
+ contract(positioning_scope_was, position_was..)
41
+ expand(positioning_scope, position..)
42
+
43
+ # If the position integer was set to the same as its prior value but the scope has changed then
44
+ # we need to tell Rails that it has changed so that it gets updated from the temporary 0 value.
45
+ position_will_change!
46
+ elsif position_was > position
47
+ expand(positioning_scope, position..position_was)
48
+ else
49
+ contract(positioning_scope, position_was..position)
50
+ end
51
+ end
52
+ end
53
+
54
+ def destroy_position
55
+ contract(positioning_scope, (position + 1)..) unless destroyed_via_positioning_scope?
56
+ end
57
+
58
+ private
59
+
60
+ def base_class
61
+ @positioned.class.base_class
62
+ end
63
+
64
+ def primary_key_column
65
+ base_class.primary_key
66
+ end
67
+
68
+ def primary_key
69
+ @positioned.send primary_key_column
70
+ end
71
+
72
+ def position
73
+ @positioned.send @column
74
+ end
75
+
76
+ def position=(position)
77
+ @positioned.send :"#{@column}=", position
78
+ end
79
+
80
+ def position_changed?
81
+ @positioned.send :"#{@column}_changed?"
82
+ end
83
+
84
+ def position_will_change!
85
+ @positioned.send :"#{@column}_will_change!"
86
+ end
87
+
88
+ def expand(scope, range)
89
+ scope.where("#{@column}": range).update_all "#{@column} = #{@column} * -1"
90
+ scope.where("#{@column}": ..-1).update_all "#{@column} = #{@column} * -1 + 1"
91
+ end
92
+
93
+ def contract(scope, range)
94
+ scope.where("#{@column}": range).update_all "#{@column} = #{@column} * -1"
95
+ scope.where("#{@column}": ..-1).update_all "#{@column} = #{@column} * -1 - 1"
96
+ end
97
+
98
+ def solidify_position
99
+ position_before_type_cast = @positioned.read_attribute_before_type_cast @column
100
+ position_before_type_cast.to_sym if position_before_type_cast.is_a? String
101
+ position_before_type_cast.symbolize_keys! if position_before_type_cast.is_a? Hash
102
+
103
+ case position_before_type_cast
104
+ when Integer
105
+ self.position = position_before_type_cast.clamp(1..last_position)
106
+ when :first, {after: nil}
107
+ self.position = 1
108
+ when nil, :last, {before: nil}
109
+ self.position = last_position
110
+ when Hash
111
+ relative_position, relative_record_or_primary_key = *position_before_type_cast.first
112
+
113
+ unless [:before, :after].include? relative_position
114
+ raise Error.new, "relative `#{@column}` must be either :before, :after"
115
+ end
116
+
117
+ relative_primary_key = if relative_record_or_primary_key.is_a? base_class
118
+ relative_record_or_primary_key.send(primary_key_column)
119
+ else
120
+ relative_record_or_primary_key
121
+ end
122
+
123
+ relative_record_scope = positioning_scope.where("#{primary_key_column}": relative_primary_key)
124
+
125
+ unless relative_record_scope.exists?
126
+ raise Error.new, "relative `#{@column}` record must be in the same scope"
127
+ end
128
+
129
+ position_was = base_class.where("#{primary_key_column}": primary_key).pick(@column)
130
+
131
+ solidified_position = relative_record_scope.pick(@column)
132
+ solidified_position += 1 if relative_position == :after
133
+ solidified_position -= 1 if in_positioning_scope? && position_was < solidified_position
134
+
135
+ self.position = solidified_position
136
+ end
137
+
138
+ unless position.is_a? Integer
139
+ raise Error.new,
140
+ "`#{@column}` must be an Integer, :first, :last, before: #{base_class.name}, " \
141
+ "after: #{base_class.name}, or nil"
142
+ end
143
+ end
144
+
145
+ def last_position
146
+ (positioning_scope.maximum(@column) || 0) + (in_positioning_scope? ? 0 : 1)
147
+ end
148
+
149
+ def positioning_columns
150
+ @positioned.class.positioning_columns[@column]
151
+ end
152
+
153
+ def positioning_scope
154
+ @positioned.class.where(
155
+ positioning_columns.to_h { |scope_component|
156
+ [scope_component, @positioned.send(scope_component)]
157
+ }
158
+ ).order(@column)
159
+ end
160
+
161
+ def in_positioning_scope?
162
+ @positioned.persisted? && positioning_scope.exists?(primary_key)
163
+ end
164
+
165
+ def positioning_scope_changed?
166
+ positioning_columns.any? do |scope_component|
167
+ @positioned.attribute_changed?(scope_component)
168
+ end
169
+ end
170
+
171
+ def destroyed_via_positioning_scope?
172
+ @positioned.destroyed_by_association && positioning_columns.any? do |scope_component|
173
+ @positioned.destroyed_by_association.foreign_key == scope_component
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,3 @@
1
+ module Positioning
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,43 @@
1
+ require_relative "positioning/version"
2
+ require_relative "positioning/mechanisms"
3
+
4
+ require "active_support/concern"
5
+ require "active_support/lazy_load_hooks"
6
+
7
+ module Positioning
8
+ class Error < StandardError; end
9
+
10
+ extend ActiveSupport::Concern
11
+
12
+ class_methods do
13
+ def positioning_columns
14
+ @positioning_columns ||= {}
15
+ end
16
+
17
+ def positioned(on: [], column: :position)
18
+ column = column.to_sym
19
+
20
+ if positioning_columns.key? column
21
+ raise Error.new "The column `#{column}` has already been used by the scope `#{positioning_columns[column]}`."
22
+ else
23
+ positioning_columns[column] = Array.wrap(on).map do |scope_component|
24
+ scope_component = scope_component.to_s
25
+ reflection = reflections[scope_component]
26
+
27
+ (reflection && reflection.belongs_to?) ? reflection.foreign_key : scope_component
28
+ end
29
+
30
+ define_method(:"prior_#{column}") { Mechanisms.new(self, column).prior }
31
+ define_method(:"subsequent_#{column}") { Mechanisms.new(self, column).subsequent }
32
+
33
+ before_create { Mechanisms.new(self, column).create_position }
34
+ before_update { Mechanisms.new(self, column).update_position }
35
+ after_destroy { Mechanisms.new(self, column).destroy_position }
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ ActiveSupport.on_load :active_record do
42
+ ActiveRecord::Base.send :include, Positioning
43
+ end
@@ -0,0 +1,40 @@
1
+ require_relative "lib/positioning/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "positioning"
5
+ spec.version = Positioning::VERSION
6
+ spec.authors = ["Brendon Muir"]
7
+ spec.email = ["brendon@spike.net.nz"]
8
+
9
+ spec.summary = "Simple positioning for Active Record models."
10
+ spec.homepage = "https://github.com/brendon/positioning"
11
+ spec.license = "MIT"
12
+ spec.required_ruby_version = ">= 3.0.0"
13
+
14
+ spec.metadata["homepage_uri"] = spec.homepage
15
+ spec.metadata["source_code_uri"] = "https://github.com/brendon/positioning"
16
+ spec.metadata["changelog_uri"] = "https://github.com/brendon/positioning/CHANGELOG.md"
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(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|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 "activesupport", ">= 6.1"
31
+ spec.add_dependency "activerecord", ">= 6.1"
32
+ spec.add_development_dependency "minitest-hooks", "~> 1.5.1"
33
+ spec.add_development_dependency "mocha", "~> 2.1.0"
34
+ spec.add_development_dependency "mysql2", "~> 0.5.6"
35
+ spec.add_development_dependency "pg", "~> 1.5.5"
36
+ spec.add_development_dependency "sqlite3", "~> 1.7.2"
37
+
38
+ # For more information and examples about making a new gem, check out our
39
+ # guide at: https://bundler.io/guides/creating_gem.html
40
+ end
@@ -0,0 +1,4 @@
1
+ module Positioning
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: positioning
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brendon Muir
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-02-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest-hooks
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.5.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.5.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: mysql2
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.5.6
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.5.6
83
+ - !ruby/object:Gem::Dependency
84
+ name: pg
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.5.5
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.5.5
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.7.2
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.7.2
111
+ description:
112
+ email:
113
+ - brendon@spike.net.nz
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".standard.yml"
119
+ - CHANGELOG.md
120
+ - Gemfile
121
+ - Gemfile.lock
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - lib/positioning.rb
126
+ - lib/positioning/mechanisms.rb
127
+ - lib/positioning/version.rb
128
+ - positioning.gemspec
129
+ - sig/positioning.rbs
130
+ homepage: https://github.com/brendon/positioning
131
+ licenses:
132
+ - MIT
133
+ metadata:
134
+ homepage_uri: https://github.com/brendon/positioning
135
+ source_code_uri: https://github.com/brendon/positioning
136
+ changelog_uri: https://github.com/brendon/positioning/CHANGELOG.md
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 3.0.0
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.2.32
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Simple positioning for Active Record models.
156
+ test_files: []