acts_as_manual_list 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5b9339c36ec588ac8f2747aa4051d15433e4f7d2511f63bdf8ddb1a35b260bb1
4
+ data.tar.gz: 8415269162afd50c21afb3af3c0ffe512796e655467169cc529395b7fe99fa8c
5
+ SHA512:
6
+ metadata.gz: 71692f8f11058110b17360d44fe64f323c0b0f073a234e038986d2646ff204390036bc95e094acd2b766e7e4f53d0d5d0538b005e36f1ac96eeda6a77ce5ff77
7
+ data.tar.gz: 275748d836531bb96f1a51f69ecb6258f3f5060cc44f3c8578e6cc9c664dc21a9690228dace01d92fd36339770739ea8ecae6424ecda0b77eb22c33e11e50653
@@ -0,0 +1,20 @@
1
+ Copyright 2016 DMM.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,30 @@
1
+ [![Build Status](https://travis-ci.org/iknow/acts_as_manual_list.svg?branch=master)](https://travis-ci.org/iknow/acts_as_manual_list)
2
+
3
+ # ActsAsManualList
4
+ Short description and motivation.
5
+
6
+ ## Usage
7
+ How to use my plugin.
8
+
9
+ ## Installation
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'acts_as_manual_list'
14
+ ```
15
+
16
+ And then execute:
17
+ ```bash
18
+ $ bundle
19
+ ```
20
+
21
+ Or install it yourself as:
22
+ ```bash
23
+ $ gem install acts_as_manual_list
24
+ ```
25
+
26
+ ## Contributing
27
+ Contribution directions go here.
28
+
29
+ ## License
30
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'ActsAsManualList'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ Bundler::GemHelper.install_tasks
20
+
21
+ require 'rake/testtask'
22
+
23
+ Rake::TestTask.new(:test) do |t|
24
+ t.libs << 'lib'
25
+ t.libs << 'test'
26
+ t.pattern = 'test/**/*_test.rb'
27
+ t.verbose = false
28
+ end
29
+
30
+ task default: :test
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lazily'
4
+ require 'iknow_list_utils'
5
+
6
+ module ActsAsManualList
7
+ using IknowListUtils
8
+
9
+ POSITION_GETTER = ->(el) { el.send(:position) }
10
+ POSITION_SETTER = ->(el, v) { el.send(:position=, v) }
11
+
12
+ # Takes an list of elements in a desired order. Elements have an inherent
13
+ # float position, obtainable via `position_getter`, which may be nil if the
14
+ # element previously was not a list member. For each element whose position
15
+ # is not in order (i.e. between the positions of its neighbours), calls
16
+ # `position_setter` to update the position to a new float value.
17
+ # Returns the count of elements whose positions were changed.
18
+ def self.update_positions(elements,
19
+ position_getter: POSITION_GETTER,
20
+ position_setter: POSITION_SETTER)
21
+
22
+ # calculate index for each previously existing position
23
+ position_indices = elements
24
+ .lazy
25
+ .with_index
26
+ .map { |el, i| [position_getter.(el), i] }
27
+ .reject { |p, _i| p.nil? }
28
+ .to_a
29
+
30
+ # TODO: we haven't dealt with collisions
31
+
32
+ # Calculate stable points in the element array which don't need to have
33
+ # positions updated:
34
+ # * before the beginning of the ordered subsequence
35
+ # * a subsequence of elements which are already in order
36
+ # * after the end of the ordered subsequence.
37
+ stable_position_indices = Lazily.concat([[nil, -1]],
38
+ position_indices.longest_rising_sequence_by(&:first),
39
+ [[nil, elements.size]])
40
+
41
+ # For each possible range of indices that are not stable (i.e. for every
42
+ # adjacent pair of stable indices), assign new positions distributed
43
+ # between the stable positions.
44
+ changed_positions = 0
45
+ stable_position_indices.each_cons(2) do |(start_pos, start_index), (end_pos, end_index)|
46
+ range = (start_index + 1)..(end_index - 1)
47
+ next if range.size.zero?
48
+
49
+ changed_positions += range.size
50
+ new_positions = select_positions(start_pos, end_pos, range.size)
51
+
52
+ new_positions.each.with_index(1) do |new_pos, offset|
53
+ position_setter.(elements[start_index + offset], new_pos)
54
+ end
55
+ end
56
+
57
+ changed_positions
58
+ end
59
+
60
+ # Select `count` positions in order between the provided `start_pos` and `end_pos`
61
+ def self.select_positions(start_pos, end_pos, count)
62
+ case
63
+ when start_pos.nil? && end_pos.nil?
64
+ # all elements are unpositioned, assign sequentially
65
+ 1.upto(count).map(&:to_f)
66
+ when start_pos.nil?
67
+ # before first fixed element
68
+ count.downto(1).map { |i| end_pos - i }
69
+ when end_pos.nil?
70
+ # after last fixed element
71
+ 1.upto(count).map { |i| start_pos + i }
72
+ else
73
+ delta = (end_pos - start_pos) / (count + 1)
74
+ 1.upto(count).map { |i| start_pos + (delta * i) }
75
+ end
76
+ end
77
+ end
78
+
79
+ # Install into ActiveRecord
80
+ require 'acts_as_manual_list/base'
81
+ require 'acts_as_manual_list/list_member'
82
+
83
+ if defined?(Rails::Railtie)
84
+ class Railtie < Rails::Railtie
85
+ initializer 'acts_as_manual_list.insert_into_active_record' do
86
+ ActiveSupport.on_load :active_record do
87
+ ActiveRecord::Base.send(:include, ActsAsManualList::Base)
88
+ end
89
+ end
90
+ end
91
+ elsif defined?(ActiveRecord)
92
+ ActiveRecord::Base.send(:include, ActsAsManualList::Base)
93
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_record'
5
+
6
+ module ActsAsManualList::Base
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def acts_as_manual_list(scope:, attribute: :position)
11
+ include ActsAsManualList::ListMember
12
+ self.acts_as_list_scope = scope
13
+ self.acts_as_list_attribute = attribute
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+ require 'active_record'
5
+
6
+ module ActsAsManualList::ListMember
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class << self
11
+ attr_accessor :acts_as_list_scope, :acts_as_list_attribute
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ # TODO: inject something like this into the scope class
17
+ # def reorder_#{association}(children)
18
+ # ChildType.set_positions(self, children)
19
+ # end
20
+
21
+ def set_positions(_parent, children)
22
+ # TODO: validate that the children are all children of the parent.
23
+
24
+ # Optimization: if a child record is already `changed?`, then it's going
25
+ # to be written no matter what. If we don't consider changed records for
26
+ # selecting stable positions (i.e. ignore their previous `position` field)
27
+ # we can maximize the chance of avoiding updates to otherwise untouched
28
+ # rows.
29
+ get_pos = ->(el) do
30
+ if el.changed?
31
+ nil
32
+ else
33
+ el.public_send(acts_as_list_attribute)
34
+ end
35
+ end
36
+
37
+ set_pos = ->(el, p) { el.public_send(:"#{acts_as_list_attribute}=", p) }
38
+
39
+ ActsAsManualList.update_positions(children, position_getter: get_pos, position_setter: set_pos)
40
+ end
41
+ end
42
+
43
+ # TODO... list motion, append/prepend/etc
44
+ # append from list
45
+ # insert at position
46
+ # remove from list # will need to move from list to list
47
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsManualList
4
+ VERSION = '0.1.2'
5
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IknowListUtils
4
+ refine Array do
5
+ def bsearch_max(min = nil, max = nil)
6
+ if (idx = self.bsearch_max_index(min, max) { |x| yield(x) })
7
+ self[idx]
8
+ end
9
+ end
10
+
11
+ def bsearch_max_index(min = nil, max = nil)
12
+ # lo and hi are valid array indexes
13
+ lo = min || 0
14
+ hi = max || self.count - 1
15
+
16
+ # Empty range does not contain any values satisfying predicate
17
+ return nil if hi < lo
18
+
19
+ # Lowest value has to pass predicate
20
+ return nil unless yield(self[lo])
21
+
22
+ # body invariant established: lo is always satisfied
23
+
24
+ while lo < hi
25
+ # when hi = lo + 1, mid = hi; which
26
+ # - either passes and sets lo' = lo + 1 (= hi) (singleton)
27
+ # - fails and sets hi' = lo (singleton)
28
+ # otherwise mid is between both, and the range will be limited appropriately
29
+ mid = lo + (hi - lo + 1) / 2
30
+ if yield(self[mid])
31
+ # midpoint satisfies predicate, reduce bounds, keep satisfying value
32
+ lo = mid
33
+ else
34
+ # midpoint doesn't satisfy predicate, restrict below non-satsifying value
35
+ hi = mid - 1
36
+ end
37
+ end
38
+
39
+ lo
40
+ end
41
+
42
+ # TODO: make this operate on Enumerables instead of directly accessible
43
+ # collections.
44
+ def longest_rising_sequence(&compare)
45
+ # https://en.wikipedia.org/wiki/Longest_increasing_subsequence
46
+
47
+ compare ||= ->(x, y) { x <=> y }
48
+
49
+ preds = Array.new(self.length)
50
+ ends = Array.new(self.length + 1)
51
+
52
+ max_length = 0
53
+
54
+ (0..self.length - 1).each do |i|
55
+ # bsearch ends for a subsequence to append to, if not found, start
56
+ # new sequence of length 1
57
+ existing_sequence_length =
58
+ ends.bsearch_max_index(1, max_length) do |e|
59
+ compare.(self[e], self[i]) < 0
60
+ end
61
+
62
+ new_length = (existing_sequence_length || 0) + 1
63
+ max_length = new_length if max_length < new_length
64
+ preds[i] = ends[new_length - 1]
65
+ ends[new_length] = i
66
+ end
67
+
68
+ result = []
69
+ k = ends[max_length]
70
+ max_length.downto(1) do |x|
71
+ result[x - 1] = self[k]
72
+ k = preds[k]
73
+ end
74
+
75
+ result
76
+ end
77
+
78
+ def longest_rising_sequence_by
79
+ self.longest_rising_sequence { |x, y| yield(x) <=> yield(y) }
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :acts_as_manual_list do
5
+ # # Task goes here
6
+ # end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_manual_list
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - iKnow Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-04-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: lazily
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.2.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.2.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: appraisal
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: byebug
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: ''
126
+ email:
127
+ - dev@iknow.jp
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - MIT-LICENSE
133
+ - README.md
134
+ - Rakefile
135
+ - lib/acts_as_manual_list.rb
136
+ - lib/acts_as_manual_list/base.rb
137
+ - lib/acts_as_manual_list/list_member.rb
138
+ - lib/acts_as_manual_list/version.rb
139
+ - lib/iknow_list_utils.rb
140
+ - lib/tasks/acts_as_manual_list_tasks.rake
141
+ homepage: ''
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubygems_version: 3.0.3
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Barebones acts_as_list
164
+ test_files: []