base_editing_bootstrap 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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