in_order 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +759 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/in_order_manifest.js +2 -0
  6. data/app/assets/javascripts/in_order/application.js +15 -0
  7. data/app/assets/stylesheets/in_order/application.css +15 -0
  8. data/app/controllers/in_order/application_controller.rb +5 -0
  9. data/app/controllers/in_order/concerns/response_helpers.rb +33 -0
  10. data/app/controllers/in_order/elements_controller.rb +47 -0
  11. data/app/controllers/in_order/lists_controller.rb +86 -0
  12. data/app/helpers/in_order/application_helper.rb +4 -0
  13. data/app/jobs/in_order/application_job.rb +4 -0
  14. data/app/mailers/in_order/application_mailer.rb +6 -0
  15. data/app/models/in_order/add.rb +51 -0
  16. data/app/models/in_order/application_record.rb +5 -0
  17. data/app/models/in_order/aux/create_element.rb +17 -0
  18. data/app/models/in_order/aux/element_iterator.rb +39 -0
  19. data/app/models/in_order/aux/get_element.rb +17 -0
  20. data/app/models/in_order/aux/get_keys.rb +27 -0
  21. data/app/models/in_order/aux/keys.rb +58 -0
  22. data/app/models/in_order/aux/poly_find.rb +24 -0
  23. data/app/models/in_order/aux/poly_key.rb +117 -0
  24. data/app/models/in_order/aux/position_base.rb +25 -0
  25. data/app/models/in_order/aux/repair.rb +71 -0
  26. data/app/models/in_order/aux/sort_elements.rb +32 -0
  27. data/app/models/in_order/aux/var_keys.rb +20 -0
  28. data/app/models/in_order/create.rb +62 -0
  29. data/app/models/in_order/element.rb +119 -0
  30. data/app/models/in_order/fetch.rb +33 -0
  31. data/app/models/in_order/insert.rb +42 -0
  32. data/app/models/in_order/move.rb +15 -0
  33. data/app/models/in_order/purge.rb +71 -0
  34. data/app/models/in_order/queue.rb +65 -0
  35. data/app/models/in_order/remove.rb +29 -0
  36. data/app/models/in_order/stack.rb +12 -0
  37. data/app/models/in_order/trim.rb +74 -0
  38. data/app/models/in_order/update.rb +83 -0
  39. data/app/models/in_order.rb +9 -0
  40. data/app/views/in_order/lists/_list.html.erb +16 -0
  41. data/app/views/in_order/lists/index.html.erb +9 -0
  42. data/app/views/layouts/in_order/application.html.erb +16 -0
  43. data/config/routes.rb +10 -0
  44. data/db/migrate/20190814101433_create_in_order_elements.rb +15 -0
  45. data/lib/in_order/engine.rb +5 -0
  46. data/lib/in_order/version.rb +3 -0
  47. data/lib/in_order.rb +5 -0
  48. data/lib/tasks/in_order_tasks.rake +4 -0
  49. metadata +119 -0
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'InOrder'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/in_order .js
2
+ //= link_directory ../stylesheets/in_order .css
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require rails-ujs
14
+ //= require activestorage
15
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ module InOrder
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module InOrder
3
+ module Concerns::ResponseHelpers
4
+ private
5
+
6
+ def respond_to_list(status=:ok, send_nothing_back=false)
7
+ respond_to do |format|
8
+ format.html { render partial_name }
9
+
10
+ format.js { render partial_name }
11
+
12
+ if send_nothing_back
13
+ #format.json { render nothing: true, status: status }
14
+ format.json { head status }
15
+ else
16
+ format.json do
17
+ #render json: (@elements ||= []), include: :subject, status: status
18
+ render json: (@elements ||= []), status: status
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def http_status(status=:ok)
25
+ status ? status : :unprocessable_entity
26
+ end
27
+
28
+ def partial_name
29
+ params[:partial_name] or params[:partial] or 'index'
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,47 @@
1
+
2
+ module InOrder
3
+ class ElementsController < ::ApplicationController
4
+ include Concerns::ResponseHelpers
5
+
6
+ def create
7
+ marker_id, adjacency = element_params.values_at(:marker_id, :adjacency)
8
+
9
+ InOrder::Insert.call(record_key, marker_id, adjacency)
10
+
11
+ respond_to_list http_status(:created), true
12
+ end
13
+
14
+ def update
15
+ target = params[:id]
16
+
17
+ marker, adjacency = element_params.values_at(:marker_id, :adjacency)
18
+
19
+ InOrder::Move.new(target, marker, adjacency).call
20
+
21
+ respond_to_list
22
+ end
23
+
24
+ def destroy
25
+ target = params[:id]
26
+
27
+ InOrder::Remove.new(target).call
28
+
29
+ respond_to_list
30
+ end
31
+
32
+ private
33
+
34
+ def element_params
35
+ params.require(:element).permit(*element_names)
36
+ end
37
+
38
+ def element_names
39
+ %i(marker_id adjacency subject_type subject_id)
40
+ end
41
+
42
+ def record_key
43
+ element_params.values_at(:subject_type, :subject_id)
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,86 @@
1
+
2
+ module InOrder
3
+ class ListsController < ::ApplicationController
4
+ include Concerns::ResponseHelpers
5
+
6
+ def index
7
+ @elements = InOrder::Fetch.new(keys).elements
8
+
9
+ respond_to_list
10
+ end
11
+
12
+ def create
13
+ @elements = InOrder::Create.new(keys).(record_keys)
14
+
15
+ respond_to_list http_status(:created)
16
+ end
17
+
18
+ def destroy
19
+ if element = InOrder::Element.find_by_id(params[:id])
20
+ delete_by_keys element.to_keys
21
+ end
22
+
23
+ respond_to_list http_status(:accepted), true
24
+ end
25
+
26
+ def remove
27
+ delete_by_keys keys
28
+
29
+ respond_to_list http_status(:accepted), true
30
+ end
31
+
32
+ def add
33
+ update = InOrder::Update.new(keys)
34
+
35
+ @elements = update.(record_keys, append: append_to_existing_list?,
36
+ uniq: uniq?,
37
+ max: max)
38
+
39
+ respond_to_list http_status
40
+ end
41
+
42
+ private
43
+
44
+ def append_to_existing_list?
45
+ param = list_params[:position]
46
+
47
+ param.blank? or param =~ /(after|last)/i ? true : false
48
+ end
49
+
50
+ def max
51
+ list_params[:max]
52
+ end
53
+
54
+ FALSITIES = %w(f false 0 n no)
55
+ def uniq?
56
+ uniq = list_params[:uniq]
57
+
58
+ uniq != false and (uniq.blank? or FALSITIES.exclude?(uniq.strip))
59
+ end
60
+
61
+ def record_keys
62
+ list_params[:record_keys]
63
+ end
64
+
65
+ def list_params
66
+ params.fetch(:list).permit(*list_names)
67
+ end
68
+
69
+ def list_names
70
+ [ :scope, :owner_type, :owner_id, # keys for element list
71
+ :position, :max, # for add action
72
+ record_keys: [] ] # records to attach
73
+ end
74
+
75
+ def keys
76
+ owner = list_params.values_at :owner_type, :owner_id
77
+
78
+ InOrder::Aux::Keys.new(owner, list_params[:scope])
79
+ end
80
+
81
+ def delete_by_keys(deletion_keys)
82
+ InOrder::Element.delete_elements deletion_keys
83
+ end
84
+ end
85
+ end
86
+
@@ -0,0 +1,4 @@
1
+ module InOrder
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module InOrder
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module InOrder
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,51 @@
1
+
2
+ module InOrder
3
+ # Adds a new element at the beginning or end of a list.
4
+ # Identified (i.e. keyed) by an Owner and/or Scope.
5
+ class Add
6
+ include Aux::VarKeys
7
+ include Aux::CreateElement
8
+
9
+ # +model+ is an ActiveRecord model to be linked
10
+ def call(model, at: :bottom)
11
+ bottom?(at) ? append(model) : prepend(model)
12
+ end
13
+
14
+ def prepend(model)
15
+ insert model, :before, first_element
16
+ end
17
+ alias shift prepend
18
+
19
+ def append(model)
20
+ insert model, :after, last_element
21
+ end
22
+ alias push append
23
+
24
+ def insert(model, adjacency=nil, marker=nil)
25
+ marker = block_given? ? yield(iterator, model, adjacency, marker) : marker
26
+
27
+ InOrder::Element.transaction do
28
+ Insert.new(create_element(model, keys), marker, adjacency).call
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def bottom?(at)
35
+ at.nil? or %i(bottom last end).include?(at.to_sym)
36
+ end
37
+
38
+ def first_element
39
+ InOrder::Element.first_element(keys)
40
+ end
41
+
42
+ def last_element
43
+ InOrder::Element.last_element(keys)
44
+ end
45
+
46
+ def iterator
47
+ Aux::ElementIterator.new(keys)
48
+ end
49
+ end
50
+ end
51
+
@@ -0,0 +1,5 @@
1
+ module InOrder
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ module CreateElement
5
+ private
6
+
7
+ def create_element(subject, keys, element_id=nil)
8
+ poly_key = Aux::PolyKey.new(subject, name: :subject)
9
+
10
+ atts = keys.(poly_key.to_params.merge element_id: element_id)
11
+
12
+ InOrder::Element.create(atts)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,39 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ class ElementIterator
5
+ include Enumerable
6
+
7
+ include Comparable
8
+
9
+ include Aux::VarKeys
10
+
11
+ def initialize(*args)
12
+ super if args.any?
13
+ end
14
+
15
+ def each(current=get_first, &block)
16
+ if block
17
+ if current
18
+ yield current
19
+
20
+ each current.element, &block
21
+ end
22
+ else
23
+ to_enum :each
24
+ end
25
+ end
26
+
27
+ def <=>(other)
28
+ subject <=> other.subject
29
+ end
30
+
31
+ private
32
+
33
+ def get_first
34
+ InOrder::Element.first_element(keys) if keys
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,17 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ module GetElement
5
+ private
6
+
7
+ def get_element(element_candidate)
8
+ if InOrder::Element === element_candidate
9
+ element_candidate
10
+ elsif element_candidate
11
+ InOrder::Element.find(element_candidate)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,27 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ module GetKeys
5
+ private
6
+
7
+ def get_keys(keys)
8
+ if keys&.any?
9
+ if keys_instance? keys[0]
10
+ keys[0]
11
+ elsif Array === keys[0] and keys_instance? keys[0][0]
12
+ keys[0][0]
13
+ else
14
+ InOrder::Aux::Keys.new(*keys)
15
+ end
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def keys_instance?(candidate)
22
+ InOrder::Aux::Keys === candidate
23
+ end
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,58 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ class Keys
5
+ attr_accessor :scope, :owner
6
+
7
+ def initialize(_owner=nil, _scope=nil, owner: _owner, scope: _scope)
8
+ self.owner = owner
9
+
10
+ self.scope = scope
11
+ end
12
+
13
+ def call(**extras)
14
+ to_params.merge extras
15
+ end
16
+
17
+ def valid?
18
+ to_a.any?
19
+ end
20
+
21
+ def to_h
22
+ owner_params.merge scope_params
23
+ end
24
+ alias to_params to_h
25
+
26
+ def to_s
27
+ to_a.compact.map(&:to_s) * ', '
28
+ end
29
+
30
+ def to_a
31
+ [ owner, scope ]
32
+ end
33
+
34
+ def owner_params
35
+ owner_key.to_params
36
+ end
37
+
38
+ def scope_params
39
+ { scope: scope }
40
+ end
41
+
42
+ def owner_key
43
+ InOrder::Aux::PolyKey.new(owner, default_name: 'owner')
44
+ end
45
+
46
+ def ==(other)
47
+ to_a == other.to_a
48
+ end
49
+
50
+ private
51
+
52
+ def hash
53
+ to_a.hash
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,24 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ class PolyFind
5
+ attr_accessor :poly_key
6
+
7
+ def initialize(*args)
8
+ self.poly_key = args.first if InOrder::Aux::PolyKey === args.first
9
+
10
+ self.poly_key ||= PolyKey.new(*args)
11
+ end
12
+
13
+ def call
14
+ poly_key.type.constantize.find poly_key.id if poly_key.valid?
15
+ end
16
+
17
+ def self.call(keys)
18
+ (keys || []).map {|key| new(key).call }
19
+ .compact
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,117 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ class PolyKey
5
+ FIELD_NAMES = %i(type id)
6
+
7
+ SPLITTER_RE = %r([-,:/^\\|\s])
8
+
9
+ attr_accessor :type, :id, :default_name
10
+
11
+ def initialize(candidate, id=nil, name: nil, default_name: name)
12
+ self.default_name = default_name
13
+
14
+ candidate = [ candidate, id ] if id
15
+
16
+ assign candidate
17
+ end
18
+
19
+ def to_s(delim='-')
20
+ "#{type}#{delim}#{id}"
21
+ end
22
+ alias to_param to_s
23
+ alias to_key to_s
24
+
25
+ def to_a
26
+ [ type, id ]
27
+ end
28
+
29
+ def to_h(field_names=full_names)
30
+ field_names.zip(to_a).to_h
31
+ end
32
+ alias to_params to_h
33
+
34
+ def to_bare_params
35
+ to_h FIELD_NAMES
36
+ end
37
+ alias to_short_params to_bare_params
38
+
39
+ # REST key params
40
+ def to_id_key_params
41
+ { id: to_s }
42
+ end
43
+ def to_id_params
44
+ { id: id }
45
+ end
46
+ def to_type_params
47
+ { type: type }
48
+ end
49
+
50
+ def to_params_by_name(key_name=nil, default: true)
51
+ key_name ||= name_prefix
52
+
53
+ { key_name.to_sym => to_bare_params }
54
+ end
55
+ #alias to_named params to_params_by_name
56
+
57
+ def set(type=nil, _id=nil, id: _id)
58
+ self.type = type.to_s if type
59
+
60
+ self.id = id
61
+
62
+ self
63
+ end
64
+
65
+ def valid?
66
+ type.present? and id.present?
67
+ end
68
+
69
+ protected
70
+
71
+ def type_name
72
+ type.demodulize.underscore if type
73
+ end
74
+
75
+ def name_prefix
76
+ default_name or type_name
77
+ end
78
+
79
+ def full_name(suffix)
80
+ "#{name_prefix}_#{suffix}".to_sym
81
+ end
82
+
83
+ def full_names
84
+ return full_name(:type), full_name(:id)
85
+ end
86
+
87
+ private
88
+
89
+ def assign(key)
90
+ case key
91
+ when String
92
+ set(*key.split(SPLITTER_RE))
93
+ when Array
94
+ set(*key)
95
+ when Hash
96
+ try_to_set_from_hash(key)
97
+ when model?
98
+ set(key.class, key.to_param)
99
+ end
100
+ end
101
+
102
+ def model?
103
+ -> whatever { whatever.respond_to?(:to_model) }
104
+ end
105
+
106
+ def try_to_set_from_hash(key)
107
+ set_from_hash(key, FIELD_NAMES)
108
+
109
+ set_from_hash(key, full_names) if not valid? and name_prefix
110
+ end
111
+ def set_from_hash(key, name_args)
112
+ set(*key.values_at(*name_args))
113
+ end
114
+ end
115
+ end
116
+ end
117
+
@@ -0,0 +1,25 @@
1
+
2
+ module InOrder
3
+ module Aux
4
+ class PositionBase
5
+ include Aux::GetElement
6
+
7
+ attr_accessor :target, :marker, :adjacency
8
+
9
+ def initialize(target, marker, adjacency=nil)
10
+ self.target = get_element target
11
+
12
+ self.marker = get_element marker
13
+
14
+ self.adjacency = adjacency.present? ? adjacency.to_sym : :after
15
+ end
16
+
17
+ private
18
+
19
+ def after?
20
+ adjacency == :after
21
+ end
22
+ end
23
+ end
24
+ end
25
+