base_editing_bootstrap 0.1.2
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Dockerfile +57 -0
- data/LICENSE.txt +21 -0
- data/MIT-LICENSE +20 -0
- data/README.md +130 -0
- data/Rakefile +8 -0
- data/app/assets/config/base_editing_bootstrap_manifest.js +0 -0
- data/app/assets/images/base_editing_bootstrap/.keep +0 -0
- data/app/assets/stylesheets/base_editing_bootstrap/.keep +0 -0
- data/app/controllers/.keep +0 -0
- data/app/controllers/base_editing_controller.rb +209 -0
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/restricted_area_controller.rb +26 -0
- data/app/helpers/.keep +0 -0
- data/app/helpers/base_editing_helper.rb +22 -0
- data/app/helpers/utilities/enum_helper.rb +24 -0
- data/app/helpers/utilities/form_helper.rb +61 -0
- data/app/helpers/utilities/modal_helper.rb +11 -0
- data/app/helpers/utilities/page_helper.rb +51 -0
- data/app/helpers/utilities/search_helper.rb +110 -0
- data/app/helpers/utilities/template_helper.rb +22 -0
- data/app/jobs/.keep +0 -0
- data/app/mailers/.keep +0 -0
- data/app/models/.keep +0 -0
- data/app/models/concerns/.keep +0 -0
- data/app/policies/base_model_policy.rb +42 -0
- data/app/views/.keep +0 -0
- data/app/views/base_editing/_edit_page_title_header.html.erb +3 -0
- data/app/views/base_editing/_editing_form_measure_unit.html.erb +15 -0
- data/app/views/base_editing/_form.html.erb +17 -0
- data/app/views/base_editing/_form_field.html.erb +6 -0
- data/app/views/base_editing/_form_field_header.html.erb +1 -0
- data/app/views/base_editing/_form_footer.html.erb +3 -0
- data/app/views/base_editing/_index_body.html.erb +17 -0
- data/app/views/base_editing/_index_main_buttons.html.erb +1 -0
- data/app/views/base_editing/_index_title_header.html.erb +10 -0
- data/app/views/base_editing/_navbar.html.erb +0 -0
- data/app/views/base_editing/_new_page_title_header.html.erb +3 -0
- data/app/views/base_editing/_search.html.erb +17 -0
- data/app/views/base_editing/_search_field.erb +4 -0
- data/app/views/base_editing/_search_footer.html.erb +1 -0
- data/app/views/base_editing/_search_result.html.erb +13 -0
- data/app/views/base_editing/_search_result_row.html.erb +8 -0
- data/app/views/base_editing/_tabs.html.erb +2 -0
- data/app/views/base_editing/cell_field/_base.html.erb +3 -0
- data/app/views/base_editing/cell_field/_timestamps.html.erb +3 -0
- data/app/views/base_editing/edit.html.erb +3 -0
- data/app/views/base_editing/form_field/_base.html.erb +7 -0
- data/app/views/base_editing/form_field/_date.html.erb +2 -0
- data/app/views/base_editing/form_field/_datetime.html.erb +2 -0
- data/app/views/base_editing/form_field/_decimal.html.erb +2 -0
- data/app/views/base_editing/form_field/_integer.html.erb +2 -0
- data/app/views/base_editing/index.html.erb +5 -0
- data/app/views/base_editing/new.html.erb +3 -0
- data/app/views/base_editing/show.html.erb +1 -0
- data/app/views/kaminari/_first_page.html.erb +3 -0
- data/app/views/kaminari/_gap.html.erb +3 -0
- data/app/views/kaminari/_last_page.html.erb +3 -0
- data/app/views/kaminari/_next_page.html.erb +3 -0
- data/app/views/kaminari/_page.html.erb +9 -0
- data/app/views/kaminari/_paginator.html.erb +17 -0
- data/app/views/kaminari/_prev_page.html.erb +3 -0
- data/base_editing_bootstrap.gemspec +39 -0
- data/cog.toml +26 -0
- data/config/initializers/base_field_error_proc.rb +1 -0
- data/config/locales/it.yml +67 -0
- data/config/routes.rb +2 -0
- data/docker-compose.yml +20 -0
- data/lib/base_editing_bootstrap/base_model.rb +30 -0
- data/lib/base_editing_bootstrap/engine.rb +15 -0
- data/lib/base_editing_bootstrap/forms/base.rb +101 -0
- data/lib/base_editing_bootstrap/is_validated.rb +20 -0
- data/lib/base_editing_bootstrap/searches/base.rb +47 -0
- data/lib/base_editing_bootstrap/searches/field.rb +18 -0
- data/lib/base_editing_bootstrap/version.rb +3 -0
- data/lib/base_editing_bootstrap.rb +26 -0
- data/lib/tasks/base_editing_bootstrap_tasks.rake +4 -0
- data/spec/support/external_shared/base_editing_controller_helpers.rb +154 -0
- data/spec/support/external_shared/base_model.rb +62 -0
- data/spec/support/external_shared/factory_bot.rb +28 -0
- data/spec/support/external_shared/pundit.rb +14 -0
- metadata +214 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
it:
|
2
|
+
search: "Esegui ricerca"
|
3
|
+
save: "Salva"
|
4
|
+
back: "Indietro"
|
5
|
+
new: "Nuovo"
|
6
|
+
newa: "Nuova"
|
7
|
+
edit: "Modifica"
|
8
|
+
del: "Cancella"
|
9
|
+
wait: "Wait..."
|
10
|
+
are_you_sure: "Confermi la cancellazione?"
|
11
|
+
undo: "Annulla"
|
12
|
+
error: "Errore"
|
13
|
+
notice: "Informazione"
|
14
|
+
alert: "Attenzione"
|
15
|
+
|
16
|
+
#serve per intestazioni tabelle
|
17
|
+
created_at: "Data creazione"
|
18
|
+
updated_at: "Data aggiornamento"
|
19
|
+
name: "Nome"
|
20
|
+
id: "Id"
|
21
|
+
user_create: "Utente creazione"
|
22
|
+
user_update: "Utente modifica"
|
23
|
+
|
24
|
+
male: "Maschio"
|
25
|
+
female: "Femmina"
|
26
|
+
other: "Altro"
|
27
|
+
|
28
|
+
download: "Download"
|
29
|
+
delete: "Elimina"
|
30
|
+
|
31
|
+
impersonate: "Impersona"
|
32
|
+
configure: "Configura"
|
33
|
+
execute_commands: "Esegui Comandi"
|
34
|
+
assigned_to: "Assegnato a %{user}"
|
35
|
+
|
36
|
+
pundit:
|
37
|
+
default: 'Non sei autorizzato ad eseguire questa azione.'
|
38
|
+
# #MEMO nel caso si volesse dare messaggi specifici per le policy
|
39
|
+
# post_policy:
|
40
|
+
# update?: 'You cannot edit this post!'
|
41
|
+
# create?: 'You cannot create posts!'
|
42
|
+
activerecord:
|
43
|
+
successful:
|
44
|
+
messages:
|
45
|
+
created: "%{model} creato correttamente."
|
46
|
+
updated: "%{model} aggiornato correttamente."
|
47
|
+
destroyed: "%{model} è stato correttamente cancellato."
|
48
|
+
unsuccessful:
|
49
|
+
messages:
|
50
|
+
created: "Ci sono errori che impediscono la creazione di %{model}"
|
51
|
+
updated: "Ci sono errori che impediscono l'aggiornamento di %{model}"
|
52
|
+
errors:
|
53
|
+
messages:
|
54
|
+
record_invalid: "Record non valido"
|
55
|
+
blank: "non può essere lasciato in bianco"
|
56
|
+
required: "richiesto"
|
57
|
+
taken: "già presente"
|
58
|
+
attributes:
|
59
|
+
application_record: #serve per ransack default
|
60
|
+
created_at: "Data creazione"
|
61
|
+
updated_at: "Data aggiornamento"
|
62
|
+
id: "Id"
|
63
|
+
menu:
|
64
|
+
items: []
|
65
|
+
ransack:
|
66
|
+
predicates:
|
67
|
+
i_cont: "contiene"
|
data/config/routes.rb
ADDED
data/docker-compose.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
services:
|
2
|
+
app:
|
3
|
+
command: /app/spec/dummy/bin/dev
|
4
|
+
build:
|
5
|
+
context: .
|
6
|
+
user: ${CMPS_UID_GID}
|
7
|
+
volumes:
|
8
|
+
- '.:/app'
|
9
|
+
- 'bundle:/bundle'
|
10
|
+
- '~/.gitconfig:/home/nobody/.gitconfig'
|
11
|
+
- '~/.gnupg:/home/nobody/.gnupg'
|
12
|
+
- '~/.gem:/home/nobody/.gem'
|
13
|
+
- '~/.ssh:/home/nobody/.ssh'
|
14
|
+
environment:
|
15
|
+
RAILS_ENV: development
|
16
|
+
ports:
|
17
|
+
- 3000:3000
|
18
|
+
|
19
|
+
volumes:
|
20
|
+
bundle:
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BaseEditingBootstrap
|
4
|
+
module BaseModel
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include IsValidated
|
9
|
+
delegate :ransackable_attributes, :ransackable_associations, to: :@class
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def ransackable_attributes(auth_object = nil)
|
14
|
+
if auth_object
|
15
|
+
Pundit.policy(auth_object, self.new).permitted_attributes_for_ransack
|
16
|
+
else
|
17
|
+
Pundit.policy(User.new, self.new).permitted_attributes_for_ransack
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ransackable_associations(auth_object = nil)
|
22
|
+
if auth_object
|
23
|
+
Pundit.policy(auth_object, self.new).permitted_associations_for_ransack.map(&:to_s)
|
24
|
+
else
|
25
|
+
Pundit.policy(User.new, self.new).permitted_associations_for_ransack.map(&:to_s)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BaseEditingBootstrap
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
|
4
|
+
config.generators do |g|
|
5
|
+
g.test_framework :rspec
|
6
|
+
g.fixture_replacement :factory_bot
|
7
|
+
g.factory_bot dir: 'spec/factories'
|
8
|
+
end
|
9
|
+
|
10
|
+
initializer "base_editing_bootstrap.deprecator" do |app|
|
11
|
+
app.deprecators[:base_editing_bootstrap] = BaseEditingBootstrap.deprecator
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BaseEditingBootstrap::Forms
|
4
|
+
class Base < ActionView::Helpers::FormBuilder
|
5
|
+
[
|
6
|
+
:text_field,
|
7
|
+
:text_area,
|
8
|
+
:date_field,
|
9
|
+
:datetime_field,
|
10
|
+
:time_field,
|
11
|
+
:file_field,
|
12
|
+
:number_field
|
13
|
+
].each do |selector|
|
14
|
+
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
15
|
+
def #{selector}(method, options = {})
|
16
|
+
super(method,
|
17
|
+
options.merge(class: form_style_class_for(method, options))
|
18
|
+
)
|
19
|
+
end
|
20
|
+
RUBY_EVAL
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Costruisce l'array delle classi che devono essere presenti sul campo della form
|
25
|
+
#
|
26
|
+
def form_style_class_for(method, options = {})
|
27
|
+
classes = ["form-control"]
|
28
|
+
classes << "is-invalid" if object.errors && object.errors.include?(method)
|
29
|
+
classes << options[:class].split(" ") if options[:class]
|
30
|
+
classes.flatten.compact.uniq.join(" ")
|
31
|
+
end
|
32
|
+
|
33
|
+
def decimal_field(field, options = {})
|
34
|
+
number_field(field,
|
35
|
+
options.reverse_merge(
|
36
|
+
value: @template.number_with_delimiter(object[field].to_f,
|
37
|
+
separator: ".", # questo è secondo definizione html5
|
38
|
+
delimiter: ""),
|
39
|
+
step: :any
|
40
|
+
))
|
41
|
+
end
|
42
|
+
|
43
|
+
def ckeditor_text_area(field, options = {})
|
44
|
+
text_area(field, options.reverse_merge(data: {controller: "ckeditor"}))
|
45
|
+
end
|
46
|
+
|
47
|
+
def select(method, choices = nil, options = {}, html_options = {}, &block)
|
48
|
+
html_options[:class] = "form-select #{html_options[:class]}"
|
49
|
+
super(method, choices, options, html_options.merge(class: form_style_class_for(method, html_options)), &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
53
|
+
label = options.extract!(:label)[:label] || nil
|
54
|
+
if label
|
55
|
+
label_tag = label(label, class: "form-check-label")
|
56
|
+
else
|
57
|
+
label_tag = nil
|
58
|
+
end
|
59
|
+
classes = (["form-check"] + (options.extract!(:class)[:class] || "").split(" ")).join(" ")
|
60
|
+
@template.content_tag(:div, class: classes) do
|
61
|
+
super(method, options.reverse_merge(class: "form-check-input"), checked_value, unchecked_value) + label_tag
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def switch_box(method, options = {}, checked_value = "1", unchecked_value = "0")
|
66
|
+
options[:class] = (["form-switch"] + (options[:class] || "").split(" ")).join(" ")
|
67
|
+
self.check_box(method, options, checked_value, unchecked_value)
|
68
|
+
end
|
69
|
+
|
70
|
+
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
71
|
+
super do |builder|
|
72
|
+
@template.content_tag(:div, class: "form-check") do
|
73
|
+
builder.check_box(class: "form-check-input") + builder.label(class: "form-check-label")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def radio_button(method, tag_value, options = {})
|
79
|
+
@template.content_tag(:div, class: "form-check") do
|
80
|
+
super(method, tag_value, options.reverse_merge(class: "form-check-input")) +
|
81
|
+
label(method, class: "form-check-label")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Se necessario modificare il testo dell' "undo", basta aggiungere nelle traduzioni
|
87
|
+
# nella solita struttura di active record l'attributo :_submit_undo,
|
88
|
+
# per il normale submit consiglio la lettura della guida standard di rails
|
89
|
+
# ATTENZIONE: nelle classi del bottone undo, abbiamo aggiunto .btn-undo-button
|
90
|
+
# che ascoltiamo dalle modal e utilizziamo per chiudere la modal, al posto
|
91
|
+
# seguire realmente il link con il browser.
|
92
|
+
def submit(value = nil, options = {})
|
93
|
+
@template.content_tag(:div, class: "btn-group mr-1") do
|
94
|
+
super(value, options.reverse_merge(class: "btn btn-primary")) +
|
95
|
+
@template.link_to(object.class.human_attribute_name(:_submit_undo, default: :undo),
|
96
|
+
@template.index_custom_polymorphic_path(object.class),
|
97
|
+
class: "btn btn-default btn-undo-button")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/concern'
|
4
|
+
|
5
|
+
module BaseEditingBootstrap
|
6
|
+
|
7
|
+
module IsValidated
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
##
|
12
|
+
# Helpers per settare su un record quando abbiamo eseguito o meno la validazione e quindi
|
13
|
+
# utilizzare questa informazione nella renderizzazione dello stato della form.
|
14
|
+
attr_reader :is_validated
|
15
|
+
alias_method :validated?, :is_validated
|
16
|
+
after_initialize -> { @is_validated = false }
|
17
|
+
after_validation -> { @is_validated = true }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BaseEditingBootstrap::Searches
|
4
|
+
##
|
5
|
+
# PORO per la gestione dei metodi associati alla ricerca.
|
6
|
+
class Base
|
7
|
+
include ActiveModel::Naming
|
8
|
+
include ActiveModel::Conversion
|
9
|
+
|
10
|
+
attr_reader :model_klass, :user, :params, :scope
|
11
|
+
|
12
|
+
# @param [User] user
|
13
|
+
# @param [ActiveRecord::Associations::CollectionProxy] scope
|
14
|
+
def initialize(scope, user, params: {page: nil})
|
15
|
+
@model_klass = scope.klass
|
16
|
+
@user = user
|
17
|
+
@scope = scope
|
18
|
+
@params = params
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Risultato della ricerca, fa da pipeline verso ransack
|
23
|
+
def results
|
24
|
+
ransack_query
|
25
|
+
.result(distinct: true)
|
26
|
+
.order(:id)
|
27
|
+
.page(params[:page])
|
28
|
+
end
|
29
|
+
|
30
|
+
def ransack_query
|
31
|
+
scope
|
32
|
+
.ransack(params[:q], auth_object: user)
|
33
|
+
end
|
34
|
+
|
35
|
+
def search_fields
|
36
|
+
Pundit.policy(@user, @model_klass).search_fields.collect { |f| Field.new(self, f) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def search_result_fields
|
40
|
+
Pundit.policy(@user, @model_klass).search_result_fields
|
41
|
+
end
|
42
|
+
|
43
|
+
def persisted?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BaseEditingBootstrap::Searches
|
4
|
+
##
|
5
|
+
# PORO per gestione del singolo campo
|
6
|
+
class Field
|
7
|
+
attr_reader :search_base, :name
|
8
|
+
|
9
|
+
def initialize(search_base, name)
|
10
|
+
@search_base = search_base
|
11
|
+
@name = name
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_partial_path
|
15
|
+
"search_field"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "ransack"
|
2
|
+
require "kaminari"
|
3
|
+
require "pundit"
|
4
|
+
|
5
|
+
if ENV['RAILS_ENV'] == 'test' and defined? RSpec
|
6
|
+
dir_path = File.expand_path('../spec/support/external_shared', __dir__)
|
7
|
+
Dir["#{dir_path}/*.rb"].each do |file|
|
8
|
+
require file
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require "zeitwerk"
|
13
|
+
loader = Zeitwerk::Loader.for_gem
|
14
|
+
loader.setup
|
15
|
+
|
16
|
+
module BaseEditingBootstrap
|
17
|
+
include ActiveSupport::Configurable
|
18
|
+
config_accessor :inherited_controller, default: "ApplicationController"
|
19
|
+
|
20
|
+
def self.deprecator
|
21
|
+
@deprecator ||= ActiveSupport::Deprecation.new("1.0", "BaseEditingBootstrap")
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
loader.eager_load
|
@@ -0,0 +1,154 @@
|
|
1
|
+
##
|
2
|
+
# Condiviso con applicazione esterna
|
3
|
+
|
4
|
+
def check_if_should_execute(only, except, action)
|
5
|
+
return false if except.include?(action)
|
6
|
+
return true if only.empty?
|
7
|
+
only.include?(action)
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Helper per testare controller derivanti da base editing
|
12
|
+
# only e except servono per filtrare o escludere determinate actions
|
13
|
+
RSpec.shared_examples "base editing controller" do |factory: nil, only: [], except: [], skip_invalid_checks: false|
|
14
|
+
if factory
|
15
|
+
let(:inside_factory) { factory }
|
16
|
+
else
|
17
|
+
let(:inside_factory) { "to override inside_factory" }
|
18
|
+
end
|
19
|
+
let(:persisted_instance) do
|
20
|
+
create(inside_factory)
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Possibili override per la costruzione delle path
|
25
|
+
#
|
26
|
+
let(:url_for_new) { [model.new, action: :new] }
|
27
|
+
let(:url_for_index) { url_for(model) }
|
28
|
+
let(:url_for_create) { [model.new] }
|
29
|
+
let(:url_for_succ_delete) { url_for(model) }
|
30
|
+
let(:url_for_fail_delete) { url_for_succ_delete }
|
31
|
+
let(:url_for_edit) { [persisted_instance, action: :edit] }
|
32
|
+
let(:url_for_update) { [persisted_instance, params: {param_key => valid_attributes}] }
|
33
|
+
## non sempre abbiamo l'index nelle action disponibili, dobbiamo quindi avere modo di eseguire un override
|
34
|
+
let(:url_for_unauthorized) { url_for_index }
|
35
|
+
##
|
36
|
+
# Chiave dentro a params
|
37
|
+
let(:param_key) {
|
38
|
+
Pundit::PolicyFinder.new(model).param_key
|
39
|
+
}
|
40
|
+
|
41
|
+
let(:model) {
|
42
|
+
persisted_instance.class
|
43
|
+
}
|
44
|
+
|
45
|
+
let(:invalid_attributes) {
|
46
|
+
attributes_for(inside_factory, :with_invalid_attributes)
|
47
|
+
}
|
48
|
+
|
49
|
+
let(:valid_attributes) {
|
50
|
+
nested_attributes_for(inside_factory)
|
51
|
+
}
|
52
|
+
|
53
|
+
it_behaves_like "fail with unauthorized", request: -> { get url_for(url_for_unauthorized) }
|
54
|
+
|
55
|
+
if check_if_should_execute(only, except, :index)
|
56
|
+
describe "index" do
|
57
|
+
it "response" do
|
58
|
+
params = {q: {"foo_eq": "foo"}}
|
59
|
+
get url_for_index, params: params
|
60
|
+
expect(response).to have_http_status(:ok)
|
61
|
+
expect(assigns[:search_instance]).to be_an_instance_of(BaseEditingBootstrap::Searches::Base).and(have_attributes(
|
62
|
+
user: user,
|
63
|
+
params: ActionController::Parameters.new(params).permit!
|
64
|
+
))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if check_if_should_execute(only, except, :new)
|
70
|
+
describe "new" do
|
71
|
+
it "response" do
|
72
|
+
get url_for(url_for_new)
|
73
|
+
expect(response).to have_http_status(:ok)
|
74
|
+
expect(assigns[:object]).to be_an_instance_of(model)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
if check_if_should_execute(only, except, :edit)
|
80
|
+
describe "edit" do
|
81
|
+
it "response" do
|
82
|
+
get url_for(url_for_edit)
|
83
|
+
expect(response).to have_http_status(:ok)
|
84
|
+
expect(assigns[:object]).to be_an_instance_of(model)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if check_if_should_execute(only, except, :update)
|
90
|
+
describe "update" do
|
91
|
+
it "response" do
|
92
|
+
put url_for(url_for_update)
|
93
|
+
expect(assigns[:object]).to be_an_instance_of(model)
|
94
|
+
expect(response).to have_http_status(:see_other)
|
95
|
+
end
|
96
|
+
|
97
|
+
unless skip_invalid_checks
|
98
|
+
it "not valid" do
|
99
|
+
put url_for([persisted_instance, params: {param_key => invalid_attributes}])
|
100
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
if check_if_should_execute(only, except, :create)
|
107
|
+
describe "create" do
|
108
|
+
it "response" do
|
109
|
+
post url_for([*url_for_create, params: {param_key => valid_attributes}])
|
110
|
+
expect(assigns[:object]).to be_an_instance_of(model)
|
111
|
+
expect(response).to have_http_status(:see_other)
|
112
|
+
end
|
113
|
+
|
114
|
+
unless skip_invalid_checks
|
115
|
+
it "not valid" do
|
116
|
+
post url_for([*url_for_create, params: {param_key => invalid_attributes}])
|
117
|
+
expect(response).to have_http_status(:unprocessable_entity)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if check_if_should_execute(only, except, :delete)
|
124
|
+
describe "delete" do
|
125
|
+
it "response" do
|
126
|
+
delete url_for(persisted_instance)
|
127
|
+
expect(assigns[:object]).to be_an_instance_of(model)
|
128
|
+
expect(response).to redirect_to(url_for_succ_delete)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "not valid" do
|
132
|
+
allow_any_instance_of(model).to receive(:destroy) do |obj|
|
133
|
+
obj.errors.add(:base, :indestructible)
|
134
|
+
false
|
135
|
+
end
|
136
|
+
delete url_for(persisted_instance)
|
137
|
+
expect(response).to redirect_to(url_for_fail_delete)
|
138
|
+
expect(flash[:error]).not_to be_blank
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
default_unathorized_failure = -> { raise "TODO - passare proc con richiesta che dovrà fallire" }
|
145
|
+
|
146
|
+
RSpec.shared_examples "fail with unauthorized" do |request: default_unathorized_failure|
|
147
|
+
it "expect to redirect to root" do
|
148
|
+
expect(Pundit).to receive(:authorize).with(user, any_args).and_raise(Pundit::NotAuthorizedError)
|
149
|
+
instance_exec(&request)
|
150
|
+
expect(response).to redirect_to(root_path)
|
151
|
+
expect(flash[:error]).not_to be_nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
RSpec.shared_examples "a base model" do |ransack_permitted_attributes: [], ransack_permitted_associations: []|
|
2
|
+
|
3
|
+
it_behaves_like "a validated? object"
|
4
|
+
|
5
|
+
describe "with ransackables" do
|
6
|
+
where(:base_model_method, :policy_method, :result) do
|
7
|
+
[
|
8
|
+
[
|
9
|
+
:ransackable_attributes, :permitted_attributes_for_ransack, ransack_permitted_attributes
|
10
|
+
],
|
11
|
+
[
|
12
|
+
:ransackable_associations, :permitted_associations_for_ransack, ransack_permitted_associations
|
13
|
+
]
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
with_them do
|
18
|
+
subject { described_class.send(base_model_method, auth_object) }
|
19
|
+
|
20
|
+
let(:simulated_user_instance) { instance_double("User") }
|
21
|
+
# before do
|
22
|
+
# allow(User).to receive(:new).and_return(simulated_user_instance)
|
23
|
+
# end
|
24
|
+
let(:auth_object) { nil }
|
25
|
+
let(:policy) {
|
26
|
+
instance_double("BaseModelPolicy", policy_method => result)
|
27
|
+
}
|
28
|
+
it "new user" do
|
29
|
+
expect(Pundit).to receive(:policy).with(an_instance_of(User),
|
30
|
+
an_instance_of(described_class)).and_call_original
|
31
|
+
#.and_return(policy)
|
32
|
+
|
33
|
+
is_expected.to match_array(result)
|
34
|
+
end
|
35
|
+
|
36
|
+
context "with auth_object" do
|
37
|
+
let(:auth_object) { :auth_object }
|
38
|
+
it do
|
39
|
+
expect(Pundit).to receive(:policy).with(auth_object, an_instance_of(described_class)).and_call_original
|
40
|
+
# .and_return(policy)
|
41
|
+
|
42
|
+
is_expected.to match_array(result)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
RSpec.shared_examples "a validated? object" do
|
50
|
+
subject {
|
51
|
+
# prendiamo un modello a caso per testare questa cosa
|
52
|
+
described_class.new
|
53
|
+
}
|
54
|
+
|
55
|
+
it { is_expected.not_to be_validated }
|
56
|
+
|
57
|
+
context "validated" do
|
58
|
+
subject { super().tap(&:valid?) }
|
59
|
+
|
60
|
+
it { is_expected.to be_validated }
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'factory_bot_rails'
|
2
|
+
##
|
3
|
+
# Condiviso con applicazione esterna
|
4
|
+
#
|
5
|
+
# Helper per la generazione dati per la creazione di un hash utile alla creazione di un record
|
6
|
+
# completo di associazioni
|
7
|
+
# USAGE:
|
8
|
+
# like attributes_for
|
9
|
+
# nested_attributes_for(:factory_name)
|
10
|
+
module FactoryBot::Syntax::Methods
|
11
|
+
def nested_attributes_for(*args)
|
12
|
+
attributes = attributes_for(*args)
|
13
|
+
klass = FactoryBot::Internal.factory_by_name(args.first).build_class
|
14
|
+
|
15
|
+
klass.reflect_on_all_associations(:belongs_to).each do |r|
|
16
|
+
association = FactoryBot.create(r.class_name.underscore)
|
17
|
+
attributes[:"#{r.name}_id"] = association.id
|
18
|
+
attributes[:"#{r.name}_type"] = association.class.name if r.options[:polymorphic]
|
19
|
+
end
|
20
|
+
|
21
|
+
attributes
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
RSpec.configure do |config|
|
27
|
+
config.include FactoryBot::Syntax::Methods
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
RSpec::Matchers.define :permit_editable_attributes do |*expected_attributes|
|
2
|
+
description { "to permit editable attributes:#{expected_attributes}" }
|
3
|
+
failure_message { "'#{actual}' does not permit attributes #{expected_attributes - actual.editable_attributes}" }
|
4
|
+
match do |actual|
|
5
|
+
actual_attributes = expected_attributes - actual.editable_attributes
|
6
|
+
|
7
|
+
actual_attributes.empty?
|
8
|
+
end
|
9
|
+
match_when_negated do |actual|
|
10
|
+
actual_attributes = expected_attributes & actual.editable_attributes
|
11
|
+
|
12
|
+
actual_attributes.empty?
|
13
|
+
end
|
14
|
+
end
|