base_editing_bootstrap 1.5.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aee3afb2ce6b96c834dbc1491588c42355e8e726ce1ba030f1ebd9c31d368800
4
- data.tar.gz: 6ee2f8827e7ec00045a710c113f1e432a874a3900d840257f9602902aaad6466
3
+ metadata.gz: 9c363b9569d715a8519f72345250ee7dd019de73ce87330e6a49d62acc0e0e10
4
+ data.tar.gz: 9d7499c1ba5fbf66c2d0f466237e7b69bce234d022d21cd9d74b920564b6960f
5
5
  SHA512:
6
- metadata.gz: ce1a79b4abb27bc8f1297b7100bdc0ed5beb6589a0fe2ddf0915bc5f258a85cfa071644a84f35ff1b0a03444c57246d15bebb2a1dcf9a738c654675f48f45b19
7
- data.tar.gz: b65de7345289bb1ef0724f27279a4afba4017ca3620714982b94f1fdc958e0322b91c8ee9a3e3ccc82040bd5c2a7b632b376a34f5e95d7ee7c00364e8efd57e3
6
+ metadata.gz: b2243dc34ddba312f01465774a8df4662ea78e832885774966d29c685164201284aae102b8188a59c54d8b3c44a1c8af81227e04ef28a780651e6cdcd1abe6c1
7
+ data.tar.gz: 7c43ad6f7059a50c7719633e432d941ebd2433518712b9b5d20b5fff573cc802b24de785e81a96eebaf31c6d96639b2e3a99445dca3df086956d73a0529cc6d3
data/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
  All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
3
3
 
4
4
  - - -
5
+ ## 1.7.0 - 2025-02-11
6
+ #### Bug Fixes
7
+ - Add permits of ransack scopes - (5fa26c6) - Marino Bonetti
8
+ - Correct set select classes - (a5ef685) - Marino Bonetti
9
+ - Boolean_icon with nil value - (211c91f) - Marino Bonetti
10
+ - Better expection for spec helper - (65b26ce) - Marino Bonetti
11
+ #### Documentation
12
+ - Add documentation for help html version - (dbc1c01) - Marino Bonetti
13
+ #### Features
14
+ - Add HTML helper version in form field help - (f06c438) - Marino Bonetti
15
+ - Add Configurable distinct query in controller - (184d772) - Marino Bonetti
16
+ #### Tests
17
+ - Correct validation for selects - (e4b8016) - Marino Bonetti
18
+
19
+ - - -
20
+
21
+ ## 1.6.0 - 2025-01-23
22
+ #### Features
23
+ - Add new test helper for association relation - (15690c7) - Marino Bonetti
24
+ #### Tests
25
+ - Fix association for order - (9e2bdd4) - Marino Bonetti
26
+ - Example order by ransack - (79cccc7) - Marino Bonetti
27
+
28
+ - - -
29
+
5
30
  ## 1.5.1 - 2025-01-22
6
31
  #### Bug Fixes
7
32
  - Ricerca template anche con contesto del controller - (ac93a9f) - Marino Bonetti
data/README.md CHANGED
@@ -126,7 +126,8 @@ Utilizzo per modello base, in questo esempio prendiamo come modello Post come es
126
126
  ```
127
127
  - è possibile customizzare
128
128
  - un text help per ogni campo andando ad aggiungere nelle traduzioni la relativa
129
- traduzione nella posizione: `it.activerecord.attributes.MODEL.FIELD/help_text`
129
+ traduzione nella posizione: `it.activerecord.attributes.MODEL.FIELD/help_text` oppure `help_text_html` in caso di
130
+ contenuto con html
130
131
  - un blocco per l'unità di misura accanto al campo aggiungendo alle traduzioni:
131
132
  `it.activerecord.attributes.MODEL.FIELD/unit`
132
133
 
@@ -14,13 +14,19 @@ class BaseEditingController < RestrictedAreaController
14
14
  # Works like documented in https://activerecord-hackery.github.io/ransack/getting-started/sorting/#sorting-in-the-controller
15
15
  class_attribute :default_sorts, default: ["id"]
16
16
 
17
+ ##
18
+ # Configure default distinct results in the index query.
19
+ # Works like documented in https://activerecord-hackery.github.io/ransack/going-further/other-notes/#problem-with-distinct-selects
20
+ class_attribute :default_distinct, default: true
21
+
17
22
  def index
18
23
  authorize base_class
19
24
 
20
25
  q = policy_scope(base_scope)
21
26
  @search_instance = search_class.new(q, current_user,
22
27
  params: params.permit(:page, :q => {}), # FIXME trovare modo per essere più "STRONG"
23
- sorts: default_sorts
28
+ sorts: default_sorts,
29
+ distinct: default_distinct
24
30
  )
25
31
  @search_instance = yield(@search_instance) if block_given?
26
32
  end
@@ -1,4 +1,5 @@
1
1
  module Utilities::PageHelper
2
+ include Utilities::IconHelper
2
3
  # @param [BaseModel] base_class
3
4
  def title_mod_g(base_class)
4
5
  "#{t("edit")} #{base_class.model_name.human}"
@@ -24,12 +25,15 @@ module Utilities::PageHelper
24
25
  # end
25
26
  # end
26
27
 
27
- # @param [TrueClass, FalseClass] valore
28
+ # @param [TrueClass, FalseClass, NilClass] valore
28
29
  def boolean_to_icon(valore)
29
- if valore
30
+ case valore
31
+ when true
30
32
  icon("check-lg", class: "text-success")
31
- else
33
+ when false
32
34
  icon("x-lg", class: "text-danger")
35
+ else
36
+ nil
33
37
  end
34
38
  end
35
39
 
@@ -24,6 +24,8 @@ class BaseModelPolicy < ApplicationPolicy
24
24
  []
25
25
  end
26
26
 
27
+ def permitted_scopes_for_ransack = []
28
+
27
29
  def search_fields = []
28
30
 
29
31
  def search_result_fields = []
@@ -6,7 +6,13 @@
6
6
  %>
7
7
  <%# locals: (object:,field:) -%>
8
8
  <%
9
- help_text = object.class.human_attribute_name("#{field}/help_text", default: "")
9
+ nf = "NOT_FOUND_HTML"
10
+ help_text = object.class.human_attribute_name("#{field}/help_text_html", default: nf)
11
+ if help_text != nf
12
+ help_text = help_text.html_safe
13
+ else
14
+ help_text = object.class.human_attribute_name("#{field}/help_text", default: "")
15
+ end
10
16
  unless help_text.blank?
11
17
  %>
12
18
  <div class="form-text"><%= help_text %></div>
@@ -1 +1 @@
1
- 1.5.1
1
+ 1.7.0
@@ -7,7 +7,9 @@ module BaseEditingBootstrap
7
7
  included do
8
8
  include IsValidated
9
9
  include ActionTranslation
10
- delegate :ransackable_attributes, :ransackable_associations, to: :@class
10
+ delegate :ransackable_attributes,
11
+ :ransackable_associations,
12
+ :ransackable_scopes, to: :@class
11
13
 
12
14
 
13
15
  ##
@@ -34,6 +36,14 @@ module BaseEditingBootstrap
34
36
  Pundit.policy(User.new, self.new).permitted_associations_for_ransack.map(&:to_s)
35
37
  end
36
38
  end
39
+
40
+ def ransackable_scopes(auth_object = nil)
41
+ if auth_object
42
+ Pundit.policy(auth_object, self.new).permitted_scopes_for_ransack.map(&:to_s)
43
+ else
44
+ Pundit.policy(User.new, self.new).permitted_scopes_for_ransack.map(&:to_s)
45
+ end
46
+ end
37
47
  end
38
48
  end
39
49
  end
@@ -24,8 +24,8 @@ module BaseEditingBootstrap::Forms
24
24
  ##
25
25
  # Costruisce l'array delle classi che devono essere presenti sul campo della form
26
26
  #
27
- def form_style_class_for(method, options = {})
28
- classes = ["form-control"]
27
+ def form_style_class_for(method, options = {}, base_classes: ["form-control"])
28
+ classes = base_classes
29
29
  classes << "is-invalid" if object.errors && object.errors.include?(method)
30
30
  classes << options[:class].split(" ") if options[:class]
31
31
  classes.flatten.compact.uniq.join(" ")
@@ -46,8 +46,8 @@ module BaseEditingBootstrap::Forms
46
46
  end
47
47
 
48
48
  def select(method, choices = nil, options = {}, html_options = {}, &block)
49
- html_options[:class] = "form-select #{html_options[:class]}"
50
- super(method, choices, options, html_options.merge(class: form_style_class_for(method, html_options)), &block)
49
+ html_options.merge!(class: form_style_class_for(method, html_options, base_classes: ["form-control", "form-select"]))
50
+ super(method, choices, options,html_options , &block)
51
51
  end
52
52
 
53
53
  def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
@@ -57,9 +57,10 @@ module BaseEditingBootstrap::Forms
57
57
  else
58
58
  label_tag = nil
59
59
  end
60
- classes = (["form-check"] + (options.extract!(:class)[:class] || "").split(" ")).join(" ")
61
- @template.content_tag(:div, class: classes) do
62
- super(method, options.reverse_merge(class: "form-check-input"), checked_value, unchecked_value) + label_tag
60
+ @template.content_tag(:div, class: form_style_class_for(method, {class: (options.extract!(:class)[:class] || "")}, base_classes: ["form-check"])) do
61
+ checkbox_class = ["form-check-input"]
62
+ checkbox_class << "is-invalid" if object.errors && object.errors.include?(method)
63
+ super(method, options.reverse_merge(class: checkbox_class.join(" ")), checked_value, unchecked_value) + label_tag
63
64
  end
64
65
  end
65
66
 
@@ -75,7 +76,7 @@ module BaseEditingBootstrap::Forms
75
76
  options = {},
76
77
  html_options = {},
77
78
  &block)
78
- form_check_classes = (["form-check"] + [(html_options.delete(:form_check_class){""}).split(" ").collect(&:strip)]).compact.join(" ")
79
+ form_check_classes = (["form-check"] + [(html_options.delete(:form_check_class) { "" }).split(" ").collect(&:strip)]).compact.join(" ")
79
80
  super do |builder|
80
81
  @template.content_tag(:div, class: form_check_classes) do
81
82
  builder.check_box(class: "form-check-input") + builder.label(class: "form-check-label")
@@ -7,17 +7,18 @@ module BaseEditingBootstrap::Searches
7
7
  include ActiveModel::Naming
8
8
  include ActiveModel::Conversion
9
9
 
10
- attr_reader :model_klass, :user, :params, :scope, :sorts
10
+ attr_reader :model_klass, :user, :params, :scope, :sorts, :distinct
11
11
 
12
12
  # @param [User] user
13
13
  # @param [ActiveRecord::Associations::CollectionProxy] scope
14
14
  # @param [Array<String (frozen)>] sort
15
- def initialize(scope, user, params: {page: nil}, sorts: ["id"])
15
+ def initialize(scope, user, params: {page: nil}, sorts: ["id"], distinct: true)
16
16
  @model_klass = scope.klass
17
17
  @user = user
18
18
  @scope = scope
19
19
  @params = params
20
20
  @sorts = sorts
21
+ @distinct = distinct
21
22
  end
22
23
 
23
24
  ##
@@ -26,7 +27,7 @@ module BaseEditingBootstrap::Searches
26
27
  def results
27
28
  ransack_query
28
29
  .tap { |r| r.sorts = @sorts if r.sorts.empty? }
29
- .result(distinct: true)
30
+ .result(distinct: @distinct)
30
31
  .tap { |q| Rails.logger.debug { "[Ransack] params:#{params} - sql: #{q.to_sql}" } }
31
32
  .page(params[:page])
32
33
  end
@@ -5,11 +5,14 @@ RSpec.describe <%= class_name %>, type: :model do
5
5
 
6
6
  # it_behaves_like "a base model",
7
7
  # ransack_permitted_attributes: %w[<%= attributes_names.join(" ") %>],
8
- # ransack_permitted_associations: [] do
8
+ # ransack_permitted_associations: [],
9
+ # option_label_method: :to_s,
10
+ # ransack_permitted_scopes: [] do
9
11
  # let(:auth_object) { :auth_object } <- default
10
12
  # let(:auth_object) { create(:user, :as_admin) } <- in caso di necessità di override
11
13
  # let(:new_user_ransack_permitted_attributes) { ransack_permitted_attributes }
12
14
  # let(:new_user_ransack_permitted_associations) { ransack_permitted_associations }
15
+ # let(:new_user_ransack_permitted_scopes) { ransack_permitted_scopes }
13
16
  # end
14
17
 
15
18
  end
@@ -49,7 +49,9 @@ RSpec.shared_examples "base editing controller" do |factory: nil, only: [], exce
49
49
 
50
50
  ##
51
51
  # Possibili override per la costruzione delle path
52
- #
52
+
53
+ let(:default_sorts) { BaseEditingController.default_sorts } # configurazione nel controller per ordine di default del modello
54
+ let(:default_distinct) { BaseEditingController.default_distinct } # configurazione nel controller per distinct nella ricerca di ransack
53
55
  let(:url_for_new) { url_for([model.new, action: :new]) }
54
56
  let(:url_for_index) { url_for(model) }
55
57
  let(:url_for_create) { url_for(model.new) }
@@ -89,10 +91,13 @@ RSpec.shared_examples "base editing controller" do |factory: nil, only: [], exce
89
91
  params = {q: {"foo_eq": "foo"}}
90
92
  get url_for_index, params: params
91
93
  expect(response).to have_http_status(200)
92
- expect(assigns[:search_instance]).to be_an_instance_of(BaseEditingBootstrap::Searches::Base).and(have_attributes(
93
- user: user,
94
- params: ActionController::Parameters.new(params).permit!
95
- ))
94
+ expect(assigns[:search_instance]).to be_an_instance_of(BaseEditingBootstrap::Searches::Base)
95
+ .and(have_attributes(
96
+ user: user,
97
+ params: ActionController::Parameters.new(params).permit!,
98
+ sorts: default_sorts,
99
+ distinct: default_distinct,
100
+ ))
96
101
  end
97
102
  end
98
103
  end
@@ -194,7 +199,7 @@ default_unathorized_failure = -> { raise "TODO - passare proc con richiesta che
194
199
  RSpec.shared_examples "fail with unauthorized" do |request: default_unathorized_failure|
195
200
  it "is expected to redirect to root" do
196
201
 
197
- if Gem::Version.create( Pundit::VERSION) < Gem::Version.create('2.3.2')
202
+ if Gem::Version.create(Pundit::VERSION) < Gem::Version.create('2.3.2')
198
203
  allow(Pundit).to receive(:authorize).with(user, any_args).and_raise(Pundit::NotAuthorizedError)
199
204
  else
200
205
  allow_any_instance_of(Pundit::Context).to receive(:authorize).and_raise(Pundit::NotAuthorizedError)
@@ -1,4 +1,7 @@
1
- RSpec.shared_examples "a base model" do |ransack_permitted_attributes: [], ransack_permitted_associations: [], option_label_method: :to_s|
1
+ RSpec.shared_examples "a base model" do |ransack_permitted_attributes: [],
2
+ ransack_permitted_associations: [],
3
+ option_label_method: :to_s,
4
+ ransack_permitted_scopes: []|
2
5
 
3
6
  it_behaves_like "a validated? object"
4
7
 
@@ -10,15 +13,24 @@ RSpec.shared_examples "a base model" do |ransack_permitted_attributes: [], ransa
10
13
  instance = described_class.new
11
14
  expect(instance).to respond_to(:option_label)
12
15
 
13
- expect(instance).to receive(option_label_method).and_call_original
16
+ expect(instance).to receive(option_label_method).and_call_original, "Expected `#{instance.class}#option_label` chiami il metodo `##{option_label_method}` per la traduzione del label nelle options"
14
17
  instance.option_label
15
18
  end
16
19
 
20
+ if ransack_permitted_scopes.any?
21
+ it "have scopes" do
22
+ ransack_permitted_scopes.each do |scope|
23
+ expect(described_class).to respond_to(scope)
24
+ end
25
+ end
26
+ end
27
+
17
28
  ##
18
29
  # Oggetto solitamente di classe User che identifichi l'utente a cui eseguire il check dei permessi
19
30
  let(:auth_object) { :auth_object }
20
31
  let(:new_user_ransack_permitted_attributes) { ransack_permitted_attributes }
21
32
  let(:new_user_ransack_permitted_associations) { ransack_permitted_associations }
33
+ let(:new_user_ransack_permitted_scopes) { ransack_permitted_scopes }
22
34
 
23
35
  describe "with ransackables" do
24
36
  where(:base_model_method, :result, :new_user_result) do
@@ -29,6 +41,9 @@ RSpec.shared_examples "a base model" do |ransack_permitted_attributes: [], ransa
29
41
  [
30
42
  :ransackable_associations, ransack_permitted_associations.map(&:to_s), lazy { new_user_ransack_permitted_associations.map(&:to_s) }
31
43
  ],
44
+ [
45
+ :ransackable_scopes, ransack_permitted_scopes.map(&:to_s), lazy { new_user_ransack_permitted_scopes.map(&:to_s) }
46
+ ],
32
47
  ]
33
48
  end
34
49
 
@@ -25,6 +25,7 @@ RSpec.shared_examples "a standard base model policy" do |factory, check_default_
25
25
  [:search_fields],
26
26
  [:permitted_associations_for_ransack],
27
27
  [:permitted_attributes_for_ransack],
28
+ [:permitted_scopes_for_ransack],
28
29
  [:editable_attributes],
29
30
  [:permitted_attributes],
30
31
  ]
@@ -37,6 +38,47 @@ RSpec.shared_examples "a standard base model policy" do |factory, check_default_
37
38
  end
38
39
  end
39
40
 
41
+ # Controllo che se negli elementi dei #sortable_search_result_fields è presente un elemento
42
+ # che ha come prefisso il nome di una relazione presente in #permitted_associations_for_ransack
43
+ # allora nella policy della relativa associazione dovrò trovare in #permitted_attributes_for_ransack
44
+ # il nome della colonna da ordinare.
45
+ # ES:
46
+ # relazione: User -> Post
47
+ # class PostPolicy
48
+ # def sortable_search_result_fields = %i[user_username]
49
+ # def permitted_associations_for_ransack = %i[user]
50
+ # end
51
+ # class UserPolicy
52
+ # def permitted_attributes_for_ransack = %[username]
53
+ # end
54
+ # ordinamento per #user_username, quindi devo cercare relazione in `user` e attribute `username`
55
+ it "check associated sortable attributes" do
56
+ klass = instance.record.class
57
+ # non facciamo nulla se non è un active record il record da controllare
58
+ if klass.respond_to?(:reflect_on_association)
59
+
60
+ # escludiamo le colonne del modello
61
+ col_to_check = (instance.sortable_search_result_fields.collect(&:to_s) - instance.record.class.column_names).uniq
62
+
63
+ elenco_campi_ordinabili_in_relazione = col_to_check.map do |field|
64
+ instance.permitted_associations_for_ransack.map do |rel|
65
+ if field.match(/^#{rel}_(.*)$/)
66
+ [rel, Regexp.last_match(1)]
67
+ end
68
+ end
69
+ end.flatten(1).compact.uniq
70
+
71
+ elenco_campi_ordinabili_in_relazione.each do |relation,field|
72
+ reflection = klass.reflect_on_association(relation.to_s)
73
+ policy = Pundit.policy(instance.user, reflection.class_name.constantize.new)
74
+ expect(policy.permitted_attributes_for_ransack.collect(&:to_sym).include?(field.to_sym)).to be_truthy, lambda{
75
+ "Mi aspetto che `#{policy.class.name}#permitted_attributes_for_ransack` includa `#{field}` per permettere l'ordinamento del campo tramite relazione"
76
+ }
77
+ end
78
+
79
+ end
80
+ end
81
+
40
82
  describe "standard_methods" do
41
83
  where(:method, :response) do
42
84
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: base_editing_bootstrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marino Bonetti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-22 00:00:00.000000000 Z
11
+ date: 2025-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails