abyme 0.2.1 → 0.5.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.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.github/workflows/build.yml +60 -0
- data/.gitignore +10 -0
- data/.rspec +1 -3
- data/.simplecov +0 -0
- data/CHANGELOG.md +93 -0
- data/Gemfile.lock +210 -15
- data/README.md +119 -181
- data/Rakefile +47 -2
- data/abyme.gemspec +18 -2
- data/bin/rails +12 -0
- data/javascript/abyme_controller.js +86 -11
- data/lib/abyme.rb +2 -0
- data/lib/abyme/abyme_builder.rb +21 -10
- data/lib/abyme/action_view_extensions/builder.rb +15 -0
- data/lib/abyme/controller.rb +15 -0
- data/lib/abyme/engine.rb +3 -1
- data/lib/abyme/model.rb +79 -5
- data/lib/abyme/version.rb +4 -2
- data/lib/abyme/view_helpers.rb +182 -16
- data/package.json +2 -2
- metadata +182 -11
- data/.travis.yml +0 -7
- data/lib/generators/.DS_Store +0 -0
- data/lib/generators/abyme/install_generator.rb +0 -25
- data/lib/generators/abyme/templates/abyme_controller.js +0 -17
data/Rakefile
CHANGED
@@ -1,6 +1,51 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
1
|
+
# require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
|
+
# require 'rails/dummy/tasks'
|
3
4
|
|
4
5
|
RSpec::Core::RakeTask.new(:spec)
|
5
|
-
|
6
6
|
task :default => :spec
|
7
|
+
|
8
|
+
# APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
9
|
+
# load 'rails/tasks/engine.rake'
|
10
|
+
# load 'rails/tasks/statistics.rake'
|
11
|
+
|
12
|
+
# Bundler::GemHelper.install_tasks
|
13
|
+
|
14
|
+
# begin
|
15
|
+
# require 'bundler/setup'
|
16
|
+
# rescue LoadError
|
17
|
+
# puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
18
|
+
# end
|
19
|
+
|
20
|
+
# require 'rdoc/task'
|
21
|
+
|
22
|
+
# RDoc::Task.new(:rdoc) do |rdoc|
|
23
|
+
# rdoc.rdoc_dir = 'rdoc'
|
24
|
+
# rdoc.title = 'Abyme'
|
25
|
+
# rdoc.options << '--line-numbers'
|
26
|
+
# rdoc.rdoc_files.include('README.md')
|
27
|
+
# rdoc.rdoc_files.include('lib/**/*.rb')
|
28
|
+
# end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'bundler/setup'
|
32
|
+
rescue LoadError
|
33
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
34
|
+
end
|
35
|
+
|
36
|
+
require 'rdoc/task'
|
37
|
+
|
38
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
39
|
+
rdoc.rdoc_dir = 'rdoc'
|
40
|
+
rdoc.title = 'SampleEngineWithRspecAndCucumber'
|
41
|
+
rdoc.options << '--line-numbers'
|
42
|
+
rdoc.rdoc_files.include('README.rdoc')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
45
|
+
|
46
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
47
|
+
|
48
|
+
load 'rails/tasks/engine.rake'
|
49
|
+
load 'rails/tasks/statistics.rake'
|
50
|
+
|
51
|
+
Bundler::GemHelper.install_tasks
|
data/abyme.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
22
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
23
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
24
|
end
|
25
25
|
spec.bindir = "exe"
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
@@ -28,5 +28,21 @@ Gem::Specification.new do |spec|
|
|
28
28
|
|
29
29
|
spec.add_development_dependency "bundler", "~> 2.0"
|
30
30
|
spec.add_development_dependency "rake", "~> 13.0"
|
31
|
-
|
31
|
+
# Tests
|
32
|
+
spec.add_development_dependency "rspec-rails"
|
33
|
+
spec.add_development_dependency 'rails-controller-testing'
|
34
|
+
spec.add_development_dependency 'database_cleaner-active_record'
|
35
|
+
spec.add_development_dependency 'capybara'
|
36
|
+
spec.add_development_dependency 'webdrivers'
|
37
|
+
spec.add_development_dependency "generator_spec"
|
38
|
+
|
39
|
+
# Dummy app
|
40
|
+
spec.add_development_dependency "sqlite3"
|
41
|
+
spec.add_development_dependency 'rails'
|
42
|
+
spec.add_development_dependency 'pry-rails'
|
43
|
+
spec.add_development_dependency 'web-console'
|
44
|
+
|
45
|
+
spec.add_development_dependency 'puma'
|
46
|
+
spec.add_development_dependency 'simplecov'
|
47
|
+
spec.add_development_dependency 'simplecov-lcov'
|
32
48
|
end
|
data/bin/rails
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
5
|
+
ENGINE_PATH = File.expand_path('../lib/abyme/engine', __dir__)
|
6
|
+
|
7
|
+
# Set up gems listed in the Gemfile.
|
8
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
9
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
10
|
+
|
11
|
+
require 'rails/all'
|
12
|
+
require 'rails/engine/commands'
|
@@ -1,27 +1,62 @@
|
|
1
1
|
import { Controller } from 'stimulus';
|
2
2
|
|
3
3
|
export default class extends Controller {
|
4
|
-
static targets = ['template', 'associations', 'fields', 'newFields'];
|
4
|
+
// static targets = ['template', 'associations', 'fields', 'newFields'];
|
5
|
+
// Some applications don't compile correctly with the usual static syntax.
|
6
|
+
// Thus implementing targets with standard getters below
|
7
|
+
|
8
|
+
static get targets() {
|
9
|
+
return ['template', 'associations', 'fields', 'newFields'];
|
10
|
+
}
|
5
11
|
|
6
12
|
connect() {
|
13
|
+
console.log("Abyme Connected")
|
14
|
+
|
7
15
|
if (this.count) {
|
8
|
-
|
16
|
+
// If data-count is present,
|
17
|
+
// add n default fields on page load
|
18
|
+
|
19
|
+
this.add_default_associations();
|
9
20
|
}
|
10
21
|
}
|
11
22
|
|
23
|
+
// return the value of the data-count attribute
|
24
|
+
|
12
25
|
get count() {
|
13
26
|
return this.element.dataset.minCount || 0;
|
14
27
|
}
|
15
28
|
|
29
|
+
// return the value of the data-position attribute
|
30
|
+
// if there is no position specified set end as default
|
31
|
+
|
16
32
|
get position() {
|
17
33
|
return this.associationsTarget.dataset.abymePosition === 'end' ? 'beforeend' : 'afterbegin';
|
18
34
|
}
|
19
35
|
|
36
|
+
// ADD_ASSOCIATION
|
37
|
+
|
38
|
+
// this function is call whenever a click occurs
|
39
|
+
// on the element with the click->abyme#add_association
|
40
|
+
// <button> element by default
|
41
|
+
|
42
|
+
// if a data-count is present the add_association
|
43
|
+
// will be call without an event so we have to check
|
44
|
+
// this case
|
45
|
+
|
46
|
+
// check for limit reached
|
47
|
+
// dispatch an event if the limit is reached
|
48
|
+
|
49
|
+
// - call the function build_html that take care
|
50
|
+
// for building the correct html to be inserted in the DOM
|
51
|
+
// - dispatch an event before insert
|
52
|
+
// - insert html into the dom
|
53
|
+
// - dispatch an event after insert
|
54
|
+
|
20
55
|
add_association(event) {
|
21
56
|
if (event) {
|
22
57
|
event.preventDefault();
|
23
58
|
}
|
24
|
-
|
59
|
+
|
25
60
|
if (this.element.dataset.limit && this.limit_check()) {
|
26
61
|
this.create_event('limit-reached')
|
27
62
|
return false
|
@@ -33,8 +68,21 @@ export default class extends Controller {
|
|
33
68
|
this.create_event('after-add');
|
34
69
|
}
|
35
70
|
|
71
|
+
// REMOVE_ASSOCIATION
|
72
|
+
|
73
|
+
// this function is call whenever a click occurs
|
74
|
+
// on the element with the click->abyme#remove_association
|
75
|
+
// <button> element by default
|
76
|
+
|
77
|
+
// - call the function mark_for_destroy that takes care
|
78
|
+
// of marking the element for destruction and hiding it
|
79
|
+
// - dispatch an event before mark & hide
|
80
|
+
// - mark for descrution + hide the element
|
81
|
+
// - dispatch an event after mark and hide
|
82
|
+
|
36
83
|
remove_association(event) {
|
37
84
|
event.preventDefault();
|
85
|
+
|
38
86
|
this.create_event('before-remove');
|
39
87
|
this.mark_for_destroy(event);
|
40
88
|
this.create_event('after-remove');
|
@@ -42,6 +90,12 @@ export default class extends Controller {
|
|
42
90
|
|
43
91
|
// LIFECYCLE EVENTS RELATED
|
44
92
|
|
93
|
+
// CREATE_EVENT
|
94
|
+
|
95
|
+
// take a stage (String) => before-add, after-add...
|
96
|
+
// create a new custom event
|
97
|
+
// and dispatch at at the controller level
|
98
|
+
|
45
99
|
create_event(stage, html = null) {
|
46
100
|
const event = new CustomEvent(`abyme:${stage}`, { detail: {controller: this, content: html} });
|
47
101
|
this.element.dispatchEvent(event);
|
@@ -69,9 +123,14 @@ export default class extends Controller {
|
|
69
123
|
abymeAfterRemove(event) {
|
70
124
|
}
|
71
125
|
|
72
|
-
//
|
126
|
+
// BUILD HTML
|
127
|
+
|
128
|
+
// takes the html template and substitutes the sub-string
|
129
|
+
// NEW_RECORD for a generated timestamp
|
130
|
+
// then if there is a sub template in the html (multiple nested level)
|
131
|
+
// set all the sub timestamps back as NEW_RECORD
|
132
|
+
// finally returns the html
|
73
133
|
|
74
|
-
// build html
|
75
134
|
build_html() {
|
76
135
|
let html = this.templateTarget.innerHTML.replace(
|
77
136
|
/NEW_RECORD/g,
|
@@ -88,8 +147,15 @@ export default class extends Controller {
|
|
88
147
|
|
89
148
|
return html;
|
90
149
|
}
|
91
|
-
|
92
|
-
//
|
150
|
+
|
151
|
+
// MARK_FOR_DESTROY
|
152
|
+
|
153
|
+
// mark association for destruction
|
154
|
+
// get the closest abyme--fields from the remove_association button
|
155
|
+
// set the _destroy input value as 1
|
156
|
+
// hide the element
|
157
|
+
// add the class of abyme--marked-for-destroy to the element
|
158
|
+
|
93
159
|
mark_for_destroy(event) {
|
94
160
|
let item = event.target.closest('.abyme--fields');
|
95
161
|
item.querySelector("input[name*='_destroy']").value = 1;
|
@@ -97,20 +163,29 @@ export default class extends Controller {
|
|
97
163
|
item.classList.add('abyme--marked-for-destroy')
|
98
164
|
}
|
99
165
|
|
100
|
-
|
166
|
+
|
167
|
+
// LIMIT_CHECK
|
168
|
+
|
169
|
+
// Check if associations limit is reached
|
170
|
+
// based on newFieldsTargets only
|
171
|
+
// persisted fields are ignored
|
172
|
+
|
101
173
|
limit_check() {
|
102
174
|
return (this.newFieldsTargets
|
103
175
|
.filter(item => !item.classList.contains('abyme--marked-for-destroy'))).length
|
104
176
|
>= parseInt(this.element.dataset.limit)
|
105
177
|
}
|
106
178
|
|
107
|
-
//
|
108
|
-
|
179
|
+
// ADD_DEFAULT_ASSOCIATION
|
180
|
+
|
181
|
+
// Add n default blank associations at page load
|
182
|
+
// call sleep function to ensure uniqueness of timestamp
|
183
|
+
|
184
|
+
async add_default_associations() {
|
109
185
|
let i = 0
|
110
186
|
while (i < this.count) {
|
111
187
|
this.add_association()
|
112
188
|
i++
|
113
|
-
// Sleep function to ensure uniqueness of timestamp
|
114
189
|
await this.sleep(1);
|
115
190
|
}
|
116
191
|
}
|
data/lib/abyme.rb
CHANGED
data/lib/abyme/abyme_builder.rb
CHANGED
@@ -2,31 +2,42 @@ module Abyme
|
|
2
2
|
class AbymeBuilder < ActionView::Base
|
3
3
|
include ActionView
|
4
4
|
|
5
|
-
|
5
|
+
# If a block is given to the #abymize helper
|
6
|
+
# it will instanciate a new AbymeBuilder
|
7
|
+
# and pass to it the association name (Symbol)
|
8
|
+
# the form object, lookup_context optionaly a partial path
|
9
|
+
# then yield itself to the block
|
10
|
+
|
11
|
+
def initialize(association:, form:, context:, partial:, &block)
|
6
12
|
@association = association
|
7
13
|
@form = form
|
8
|
-
@
|
14
|
+
@context = context
|
15
|
+
@lookup_context = context.lookup_context
|
9
16
|
@partial = partial
|
10
17
|
yield(self) if block_given?
|
11
18
|
end
|
19
|
+
|
20
|
+
# RECORDS
|
21
|
+
|
22
|
+
# calls the #persisted_records_for helper method
|
23
|
+
# passing association, form and options to it
|
12
24
|
|
13
25
|
def records(options = {})
|
14
26
|
persisted_records_for(@association, @form, options) do |fields_for_association|
|
15
|
-
render_association_partial(fields_for_association,
|
27
|
+
render_association_partial(@association, fields_for_association, @partial, @context)
|
16
28
|
end
|
17
29
|
end
|
30
|
+
|
31
|
+
# NEW_RECORDS
|
32
|
+
|
33
|
+
# calls the #new_records_for helper method
|
34
|
+
# passing association, form and options to it
|
18
35
|
|
19
36
|
def new_records(options = {}, &block)
|
20
37
|
new_records_for(@association, @form, options) do |fields_for_association|
|
21
|
-
render_association_partial(fields_for_association,
|
38
|
+
render_association_partial(@association, fields_for_association, @partial, @context)
|
22
39
|
end
|
23
40
|
end
|
24
41
|
|
25
|
-
private
|
26
|
-
|
27
|
-
def render_association_partial(fields, options)
|
28
|
-
partial = @partial || options[:partial] || "abyme/#{@association.to_s.singularize}_fields"
|
29
|
-
ActionController::Base.render(partial: partial, locals: { f: fields })
|
30
|
-
end
|
31
42
|
end
|
32
43
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Abyme
|
2
|
+
module ActionViewExtensions
|
3
|
+
module Builder
|
4
|
+
def abyme_for(association, options = {}, &block)
|
5
|
+
@template.abyme_for(association, self, options, &block)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ActionView::Helpers
|
12
|
+
class FormBuilder
|
13
|
+
include Abyme::ActionViewExtensions::Builder
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Abyme
|
2
|
+
module Controller
|
3
|
+
def abyme_attributes
|
4
|
+
return [] if resource_class.nil?
|
5
|
+
|
6
|
+
resource_class.abyme_attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def resource_class
|
12
|
+
self.class.name.match(/(.*)(Controller)/)[1].singularize.safe_constantize
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/abyme/engine.rb
CHANGED
@@ -4,9 +4,11 @@ module Abyme
|
|
4
4
|
|
5
5
|
config.after_initialize do
|
6
6
|
ActiveSupport.on_load :action_view do
|
7
|
-
# ActionView::Base.send :include, Abyme::ViewHelpers
|
8
7
|
include Abyme::ViewHelpers
|
9
8
|
end
|
9
|
+
ActiveSupport.on_load :action_controller do
|
10
|
+
include Abyme::Controller
|
11
|
+
end
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
data/lib/abyme/model.rb
CHANGED
@@ -1,11 +1,85 @@
|
|
1
1
|
module Abyme
|
2
2
|
module Model
|
3
|
-
|
4
|
-
|
5
|
-
class_methods do
|
6
|
-
def abyme_for(association, options = {})
|
3
|
+
module ClassMethods
|
4
|
+
def abymize(association, permit: nil, reject: nil, **options)
|
7
5
|
default_options = {reject_if: :all_blank, allow_destroy: true}
|
8
|
-
|
6
|
+
nested_attributes_options = default_options.merge(options)
|
7
|
+
accepts_nested_attributes_for association, nested_attributes_options
|
8
|
+
# Save allow_destroy value for this model/association for later
|
9
|
+
save_destroy_option(association, nested_attributes_options[:allow_destroy])
|
10
|
+
Abyme::Model.permit_attributes(self.name, association, permit || reject, permit.present?) if permit.present? || reject.present?
|
11
|
+
end
|
12
|
+
|
13
|
+
def abyme_attributes
|
14
|
+
Abyme::Model.instance_variable_get(:@permitted_attributes)[self.name]
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def save_destroy_option(association, value)
|
20
|
+
Abyme::Model.instance_variable_get(:@allow_destroy)[self.name][association] = value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
@permitted_attributes ||= {}
|
25
|
+
@allow_destroy ||= {}
|
26
|
+
|
27
|
+
attr_accessor :allow_destroy
|
28
|
+
attr_reader :permitted_attributes
|
29
|
+
|
30
|
+
def self.permit_attributes(class_name, association, attributes, permit)
|
31
|
+
@permitted_attributes[class_name]["#{association}_attributes".to_sym] = AttributesBuilder.new(class_name, association, attributes, permit)
|
32
|
+
.build_attributes
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.included(klass)
|
36
|
+
@permitted_attributes[klass.name] ||= {}
|
37
|
+
@allow_destroy[klass.name] ||= {}
|
38
|
+
klass.extend ClassMethods
|
39
|
+
end
|
40
|
+
|
41
|
+
class AttributesBuilder
|
42
|
+
def initialize(model, association, attributes, permit = true)
|
43
|
+
@model = model
|
44
|
+
@association = association
|
45
|
+
@attributes_list = attributes
|
46
|
+
@permit = permit
|
47
|
+
@association_class = @association.to_s.classify.constantize
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_attributes
|
51
|
+
nested_attributes = @association_class.abyme_attributes if @association_class.respond_to? :abyme_attributes
|
52
|
+
authorized_attributes = build_default_attributes
|
53
|
+
if @permit && @attributes_list == :all_attributes
|
54
|
+
authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
|
55
|
+
elsif @permit
|
56
|
+
@attributes_list << nested_attributes unless (nested_attributes.blank? || @attributes_list.include?(nested_attributes))
|
57
|
+
authorized_attributes += @attributes_list
|
58
|
+
else
|
59
|
+
authorized_attributes = build_all_attributes(authorized_attributes, nested_attributes)
|
60
|
+
authorized_attributes -= @attributes_list
|
61
|
+
end
|
62
|
+
authorized_attributes
|
63
|
+
end
|
64
|
+
|
65
|
+
def destroy_allowed?
|
66
|
+
Abyme::Model.instance_variable_get(:@allow_destroy).dig(@model, @association)
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_all_attributes
|
70
|
+
@association_class.column_names.map(&:to_sym).reject { |attr| [:id, :created_at, :updated_at].include?(attr) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_all_attributes(authorized_attributes, nested_attributes)
|
74
|
+
authorized_attributes += add_all_attributes
|
75
|
+
authorized_attributes << nested_attributes unless (nested_attributes.blank? || authorized_attributes.include?(nested_attributes))
|
76
|
+
authorized_attributes
|
77
|
+
end
|
78
|
+
|
79
|
+
def build_default_attributes
|
80
|
+
attributes = [:id]
|
81
|
+
attributes << :_destroy if destroy_allowed?
|
82
|
+
attributes
|
9
83
|
end
|
10
84
|
end
|
11
85
|
end
|