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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +13 -0
  4. data/CHANGELOG.md +5 -0
  5. data/CODE_OF_CONDUCT.md +84 -0
  6. data/Dockerfile +57 -0
  7. data/LICENSE.txt +21 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +130 -0
  10. data/Rakefile +8 -0
  11. data/app/assets/config/base_editing_bootstrap_manifest.js +0 -0
  12. data/app/assets/images/base_editing_bootstrap/.keep +0 -0
  13. data/app/assets/stylesheets/base_editing_bootstrap/.keep +0 -0
  14. data/app/controllers/.keep +0 -0
  15. data/app/controllers/base_editing_controller.rb +209 -0
  16. data/app/controllers/concerns/.keep +0 -0
  17. data/app/controllers/restricted_area_controller.rb +26 -0
  18. data/app/helpers/.keep +0 -0
  19. data/app/helpers/base_editing_helper.rb +22 -0
  20. data/app/helpers/utilities/enum_helper.rb +24 -0
  21. data/app/helpers/utilities/form_helper.rb +61 -0
  22. data/app/helpers/utilities/modal_helper.rb +11 -0
  23. data/app/helpers/utilities/page_helper.rb +51 -0
  24. data/app/helpers/utilities/search_helper.rb +110 -0
  25. data/app/helpers/utilities/template_helper.rb +22 -0
  26. data/app/jobs/.keep +0 -0
  27. data/app/mailers/.keep +0 -0
  28. data/app/models/.keep +0 -0
  29. data/app/models/concerns/.keep +0 -0
  30. data/app/policies/base_model_policy.rb +42 -0
  31. data/app/views/.keep +0 -0
  32. data/app/views/base_editing/_edit_page_title_header.html.erb +3 -0
  33. data/app/views/base_editing/_editing_form_measure_unit.html.erb +15 -0
  34. data/app/views/base_editing/_form.html.erb +17 -0
  35. data/app/views/base_editing/_form_field.html.erb +6 -0
  36. data/app/views/base_editing/_form_field_header.html.erb +1 -0
  37. data/app/views/base_editing/_form_footer.html.erb +3 -0
  38. data/app/views/base_editing/_index_body.html.erb +17 -0
  39. data/app/views/base_editing/_index_main_buttons.html.erb +1 -0
  40. data/app/views/base_editing/_index_title_header.html.erb +10 -0
  41. data/app/views/base_editing/_navbar.html.erb +0 -0
  42. data/app/views/base_editing/_new_page_title_header.html.erb +3 -0
  43. data/app/views/base_editing/_search.html.erb +17 -0
  44. data/app/views/base_editing/_search_field.erb +4 -0
  45. data/app/views/base_editing/_search_footer.html.erb +1 -0
  46. data/app/views/base_editing/_search_result.html.erb +13 -0
  47. data/app/views/base_editing/_search_result_row.html.erb +8 -0
  48. data/app/views/base_editing/_tabs.html.erb +2 -0
  49. data/app/views/base_editing/cell_field/_base.html.erb +3 -0
  50. data/app/views/base_editing/cell_field/_timestamps.html.erb +3 -0
  51. data/app/views/base_editing/edit.html.erb +3 -0
  52. data/app/views/base_editing/form_field/_base.html.erb +7 -0
  53. data/app/views/base_editing/form_field/_date.html.erb +2 -0
  54. data/app/views/base_editing/form_field/_datetime.html.erb +2 -0
  55. data/app/views/base_editing/form_field/_decimal.html.erb +2 -0
  56. data/app/views/base_editing/form_field/_integer.html.erb +2 -0
  57. data/app/views/base_editing/index.html.erb +5 -0
  58. data/app/views/base_editing/new.html.erb +3 -0
  59. data/app/views/base_editing/show.html.erb +1 -0
  60. data/app/views/kaminari/_first_page.html.erb +3 -0
  61. data/app/views/kaminari/_gap.html.erb +3 -0
  62. data/app/views/kaminari/_last_page.html.erb +3 -0
  63. data/app/views/kaminari/_next_page.html.erb +3 -0
  64. data/app/views/kaminari/_page.html.erb +9 -0
  65. data/app/views/kaminari/_paginator.html.erb +17 -0
  66. data/app/views/kaminari/_prev_page.html.erb +3 -0
  67. data/base_editing_bootstrap.gemspec +39 -0
  68. data/cog.toml +26 -0
  69. data/config/initializers/base_field_error_proc.rb +1 -0
  70. data/config/locales/it.yml +67 -0
  71. data/config/routes.rb +2 -0
  72. data/docker-compose.yml +20 -0
  73. data/lib/base_editing_bootstrap/base_model.rb +30 -0
  74. data/lib/base_editing_bootstrap/engine.rb +15 -0
  75. data/lib/base_editing_bootstrap/forms/base.rb +101 -0
  76. data/lib/base_editing_bootstrap/is_validated.rb +20 -0
  77. data/lib/base_editing_bootstrap/searches/base.rb +47 -0
  78. data/lib/base_editing_bootstrap/searches/field.rb +18 -0
  79. data/lib/base_editing_bootstrap/version.rb +3 -0
  80. data/lib/base_editing_bootstrap.rb +26 -0
  81. data/lib/tasks/base_editing_bootstrap_tasks.rake +4 -0
  82. data/spec/support/external_shared/base_editing_controller_helpers.rb +154 -0
  83. data/spec/support/external_shared/base_model.rb +62 -0
  84. data/spec/support/external_shared/factory_bot.rb +28 -0
  85. data/spec/support/external_shared/pundit.rb +14 -0
  86. 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
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -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,3 @@
1
+ module BaseEditingBootstrap
2
+ VERSION = "0.1.2"
3
+ 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,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :base_editing_bootstrap do
3
+ # # Task goes here
4
+ # end
@@ -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