decidim-accountability 0.10.1 → 0.11.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/assets/javascripts/decidim/accountability/accountability.js.es6 +2 -2
  4. data/app/assets/javascripts/decidim/accountability/admin/accountability_admin.js.es6 +11 -11
  5. data/app/commands/decidim/accountability/admin/create_result.rb +1 -1
  6. data/app/commands/decidim/accountability/admin/create_status.rb +1 -1
  7. data/app/commands/decidim/accountability/admin/destroy_result.rb +43 -0
  8. data/app/controllers/decidim/accountability/admin/application_controller.rb +2 -2
  9. data/app/controllers/decidim/accountability/admin/results_controller.rb +12 -10
  10. data/app/controllers/decidim/accountability/admin/statuses_controller.rb +1 -1
  11. data/app/controllers/decidim/accountability/admin/timeline_entries_controller.rb +1 -1
  12. data/app/controllers/decidim/accountability/application_controller.rb +2 -2
  13. data/app/controllers/decidim/accountability/result_widgets_controller.rb +1 -1
  14. data/app/controllers/decidim/accountability/results_controller.rb +3 -3
  15. data/app/controllers/decidim/accountability/versions_controller.rb +1 -1
  16. data/app/events/decidim/accountability/proposal_linked_event.rb +1 -1
  17. data/app/forms/decidim/accountability/admin/result_form.rb +5 -5
  18. data/app/helpers/decidim/accountability/application_helper.rb +4 -4
  19. data/app/helpers/decidim/accountability/breadcrumb_helper.rb +1 -1
  20. data/app/models/decidim/accountability/result.rb +10 -5
  21. data/app/models/decidim/accountability/status.rb +3 -3
  22. data/app/presenters/decidim/accountability/admin_log/result_presenter.rb +5 -4
  23. data/app/presenters/decidim/accountability/admin_log/value_types/parent_presenter.rb +29 -0
  24. data/app/services/decidim/accountability/result_search.rb +2 -2
  25. data/app/services/decidim/accountability/results_calculator.rb +4 -4
  26. data/app/views/decidim/accountability/admin/results/_form.html.erb +5 -6
  27. data/app/views/decidim/accountability/admin/results/_proposals.html.erb +2 -2
  28. data/app/views/decidim/accountability/admin/results/edit.html.erb +2 -2
  29. data/app/views/decidim/accountability/admin/results/index.html.erb +5 -5
  30. data/app/views/decidim/accountability/admin/results/new.html.erb +2 -2
  31. data/app/views/decidim/accountability/admin/shared/_subnav.html.erb +1 -1
  32. data/app/views/decidim/accountability/admin/statuses/edit.html.erb +2 -3
  33. data/app/views/decidim/accountability/admin/statuses/index.html.erb +3 -3
  34. data/app/views/decidim/accountability/admin/statuses/new.html.erb +2 -3
  35. data/app/views/decidim/accountability/admin/timeline_entries/edit.html.erb +2 -3
  36. data/app/views/decidim/accountability/admin/timeline_entries/index.html.erb +4 -4
  37. data/app/views/decidim/accountability/admin/timeline_entries/new.html.erb +2 -3
  38. data/app/views/decidim/accountability/results/_home_categories.html.erb +3 -3
  39. data/app/views/decidim/accountability/results/_home_header.html.erb +2 -2
  40. data/app/views/decidim/accountability/results/_nav_breadcrumb.html.erb +4 -4
  41. data/app/views/decidim/accountability/results/_results_leaf.html.erb +2 -2
  42. data/app/views/decidim/accountability/results/_results_parent.html.erb +2 -4
  43. data/app/views/decidim/accountability/results/_scope_filters.html.erb +1 -1
  44. data/app/views/decidim/accountability/results/_search.html.erb +2 -2
  45. data/app/views/decidim/accountability/results/_show_leaf.html.erb +2 -2
  46. data/app/views/decidim/accountability/results/_show_parent.html.erb +2 -2
  47. data/app/views/decidim/accountability/results/_stats_box.html.erb +1 -1
  48. data/app/views/decidim/accountability/versions/index.html.erb +1 -1
  49. data/app/views/decidim/participatory_processes/participatory_process_groups/_highlighted_results.html.erb +1 -1
  50. data/app/views/decidim/participatory_processes/participatory_process_groups/_result.html.erb +1 -1
  51. data/app/views/decidim/participatory_spaces/_highlighted_results.html.erb +2 -2
  52. data/app/views/decidim/participatory_spaces/_result.html.erb +1 -1
  53. data/config/locales/ca.yml +12 -8
  54. data/config/locales/en.yml +13 -9
  55. data/config/locales/es.yml +13 -9
  56. data/config/locales/eu.yml +13 -9
  57. data/config/locales/fi.yml +13 -9
  58. data/config/locales/fr.yml +14 -10
  59. data/config/locales/gl.yml +13 -9
  60. data/config/locales/it.yml +13 -9
  61. data/config/locales/nl.yml +22 -18
  62. data/config/locales/pl.yml +13 -9
  63. data/config/locales/pt-BR.yml +13 -9
  64. data/config/locales/pt.yml +13 -9
  65. data/config/locales/ru.yml +0 -1
  66. data/config/locales/sv.yml +13 -9
  67. data/config/locales/uk.yml +0 -1
  68. data/db/migrate/20180305133145_rename_features_to_components_at_accountability.rb +16 -0
  69. data/lib/decidim/accountability.rb +1 -1
  70. data/lib/decidim/accountability/{feature.rb → component.rb} +25 -25
  71. data/lib/decidim/accountability/engine.rb +4 -4
  72. data/lib/decidim/accountability/result_serializer.rb +3 -3
  73. data/lib/decidim/accountability/test/factories.rb +5 -5
  74. data/lib/decidim/accountability/version.rb +1 -1
  75. metadata +26 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 757a3051ec263ec176bc03f9ae7b2ce3a683f3cfe3f862b27366b8696ebe6d6d
4
- data.tar.gz: ace2f7badfb536fd190b30ec68351fc4e90e6ed7da6bbbc72764cdb5f2fa9ec3
3
+ metadata.gz: 5207908e5a433224053e690acac13d24905ab648c799ebedc244dc40ae164d82
4
+ data.tar.gz: dbb8a639b0b117157c9f408914d2dfa222340d6e5f31631ac7f0bde9dc7a76af
5
5
  SHA512:
6
- metadata.gz: fe71357f53a478a380e287d81a52f94ba87100dc2b9be7da74ca19cc940ede34da3ab7448ac994d09f485de38038de214fdf005965a6dbfb3e70009f2cc50781
7
- data.tar.gz: 9858e94c1e805a97382f00551e953ae76cc2ac0c88116910d1ed7a2bf50abd5d0d5beade04286b129075bf55124f85bacd678e81a17deceaabba27b274e78dd6
6
+ metadata.gz: a3b5c13923e8a3c4a4bd35c5c901efef01aa143cf87a678462d19225b5b7af8d71f7510f7e0efb30ea55a96d8940c62f85019cdc611e97288a567969469c2da6
7
+ data.tar.gz: ea3e503988ea03b8d6fa1bbf8c96e6e5f8c48e0cc12a54feadc018053d2331163b65ad351fd9d046d1cea53708b6957290ba8b32137b8120b7b1eb93c8a602c0
data/README.md CHANGED
@@ -4,7 +4,7 @@ The Accountability module adds results to any participatory process. It adds a C
4
4
 
5
5
  ## Usage
6
6
 
7
- Accountability will be available as a Feature for a Participatory Process.
7
+ Accountability will be available as a Component for a Participatory Process.
8
8
 
9
9
  ## Installation
10
10
 
@@ -5,8 +5,8 @@
5
5
  $(() => {
6
6
  // Show category list on click when we are on a small scren
7
7
  if ($(window).width() < 768) {
8
- $('.category--section').click((event) => {
9
- $(event.currentTarget).next('.category--elements').toggleClass('active');
8
+ $(".category--section").click((event) => {
9
+ $(event.currentTarget).next(".category--elements").toggleClass("active");
10
10
  });
11
11
  }
12
12
  })
@@ -2,7 +2,7 @@
2
2
 
3
3
  $("#result_decidim_accountability_status_id").change(function () {
4
4
  /* eslint-disable no-invalid-this */
5
- const progress = $(this).find(':selected').data('progress')
5
+ const progress = $(this).find(":selected").data("progress")
6
6
  if (progress || progress === 0) {
7
7
  $("#result_progress").val(progress);
8
8
  }
@@ -12,7 +12,7 @@ $(function() {
12
12
  $(document).on("open.zf.reveal", "#data_picker-modal", function () {
13
13
  let xhr = null;
14
14
 
15
- $('#data_picker-autocomplete').autoComplete({
15
+ $("#data_picker-autocomplete").autoComplete({
16
16
  minChars: 2,
17
17
  source: function(term, response) {
18
18
  try {
@@ -20,26 +20,26 @@ $(function() {
20
20
  } catch (exception) { xhr = null}
21
21
 
22
22
  xhr = $.getJSON(
23
- 'proposals.json',
23
+ "proposals.json",
24
24
  { term: term },
25
25
  function(data) { response(data); }
26
26
  );
27
27
  },
28
28
  renderItem: function (item, search) {
29
- let sanitizedSearch = search.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
30
- let re = new RegExp(`(${sanitizedSearch.split(' ').join('|')})`, "gi");
29
+ let sanitizedSearch = search.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
30
+ let re = new RegExp(`(${sanitizedSearch.split(" ").join("|")})`, "gi");
31
31
  let title = item[0]
32
32
  let modelId = item[1]
33
33
  let val = `#${modelId}- ${title}`;
34
34
  return `<div class="autocomplete-suggestion" data-model-id="${modelId}" data-val ="${title}">${val.replace(re, "<b>$1</b>")}</div>`;
35
35
  },
36
36
  onSelect: function(event, term, item) {
37
- let choose = $('#proposal-picker-choose')
38
- let modelId = item.data('modelId')
39
- let val = `#${modelId}- ${item.data('val')}`;
40
- choose.data('picker-value', modelId)
41
- choose.data('picker-text', val)
42
- choose.data('picker-choose', '')
37
+ let choose = $("#proposal-picker-choose")
38
+ let modelId = item.data("modelId")
39
+ let val = `#${modelId}- ${item.data("val")}`;
40
+ choose.data("picker-value", modelId)
41
+ choose.data("picker-text", val)
42
+ choose.data("picker-choose", "")
43
43
  }
44
44
  })
45
45
  });
@@ -35,7 +35,7 @@ module Decidim
35
35
  @result = Decidim.traceability.create!(
36
36
  Result,
37
37
  @form.current_user,
38
- feature: @form.current_feature,
38
+ component: @form.current_component,
39
39
  scope: @form.scope,
40
40
  category: @form.category,
41
41
  parent_id: @form.parent_id,
@@ -29,7 +29,7 @@ module Decidim
29
29
 
30
30
  def create_status
31
31
  @status = Status.create!(
32
- feature: @form.current_feature,
32
+ component: @form.current_component,
33
33
  key: @form.key,
34
34
  name: @form.name,
35
35
  description: @form.description,
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Accountability
5
+ module Admin
6
+ # This command is executed when the user destroys a Result from the admin
7
+ # panel.
8
+ class DestroyResult < Rectify::Command
9
+ # Initializes an UpdateResult Command.
10
+ #
11
+ # result - The current instance of the result to be destroyed.
12
+ # current_user - the user performing the action
13
+ def initialize(result, current_user)
14
+ @result = result
15
+ @current_user = current_user
16
+ end
17
+
18
+ # Destroys the result.
19
+ #
20
+ # Broadcasts :ok if successful, :invalid otherwise.
21
+ def call
22
+ destroy_result
23
+
24
+ broadcast(:ok)
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :result, :current_user
30
+
31
+ def destroy_result
32
+ Decidim.traceability.perform_action!(
33
+ :delete,
34
+ result,
35
+ current_user
36
+ ) do
37
+ result.destroy!
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -6,9 +6,9 @@ module Decidim
6
6
  # This controller is the abstract class from which all other controllers of
7
7
  # this engine inherit.
8
8
  #
9
- # Note that it inherits from `Decidim::Features::BaseController`, which
9
+ # Note that it inherits from `Decidim::Components::BaseController`, which
10
10
  # override its layout and provide all kinds of useful methods.
11
- class ApplicationController < Decidim::Admin::Features::BaseController
11
+ class ApplicationController < Decidim::Admin::Components::BaseController
12
12
  end
13
13
  end
14
14
  end
@@ -49,11 +49,13 @@ module Decidim
49
49
  end
50
50
 
51
51
  def destroy
52
- result.destroy!
53
-
54
- flash[:notice] = I18n.t("results.destroy.success", scope: "decidim.accountability.admin")
52
+ DestroyResult.call(result, current_user) do
53
+ on(:ok) do
54
+ flash[:notice] = I18n.t("results.destroy.success", scope: "decidim.accountability.admin")
55
55
 
56
- redirect_to results_path(parent_id: result.parent_id)
56
+ redirect_to results_path(parent_id: result.parent_id)
57
+ end
58
+ end
57
59
  end
58
60
 
59
61
  def proposals
@@ -63,7 +65,7 @@ module Decidim
63
65
  end
64
66
  format.json do
65
67
  query = Decidim.find_resource_manifest(:proposals)
66
- .try(:resource_scope, current_feature)&.order(title: :asc)
68
+ .try(:resource_scope, current_component)&.order(title: :asc)
67
69
  term = params[:term]
68
70
  if term&.start_with?("#")
69
71
  term.delete!("#")
@@ -80,23 +82,23 @@ module Decidim
80
82
 
81
83
  def results
82
84
  parent_id = params[:parent_id].presence
83
- @results ||= Result.where(feature: current_feature, parent_id: parent_id).page(params[:page]).per(15)
85
+ @results ||= Result.where(component: current_component, parent_id: parent_id).page(params[:page]).per(15)
84
86
  end
85
87
 
86
88
  def result
87
- @result ||= Result.where(feature: current_feature).find(params[:id])
89
+ @result ||= Result.where(component: current_component).find(params[:id])
88
90
  end
89
91
 
90
92
  def parent_result
91
- @parent_result ||= Result.where(feature: current_feature, id: params[:parent_id]).first
93
+ @parent_result ||= Result.find_by(component: current_component, id: params[:parent_id])
92
94
  end
93
95
 
94
96
  def parent_results
95
- @parent_results ||= Result.where(feature: current_feature, parent_id: nil)
97
+ @parent_results ||= Result.where(component: current_component, parent_id: nil)
96
98
  end
97
99
 
98
100
  def statuses
99
- @statuses ||= Status.where(feature: current_feature)
101
+ @statuses ||= Status.where(component: current_component)
100
102
  end
101
103
  end
102
104
  end
@@ -58,7 +58,7 @@ module Decidim
58
58
  private
59
59
 
60
60
  def statuses
61
- @statuses ||= Status.where(feature: current_feature).page(params[:page]).per(15)
61
+ @statuses ||= Status.where(component: current_component).page(params[:page]).per(15)
62
62
  end
63
63
 
64
64
  def status
@@ -67,7 +67,7 @@ module Decidim
67
67
  end
68
68
 
69
69
  def result
70
- @result ||= Result.where(feature: current_feature).find(params[:result_id])
70
+ @result ||= Result.where(component: current_component).find(params[:result_id])
71
71
  end
72
72
  end
73
73
  end
@@ -5,9 +5,9 @@ module Decidim
5
5
  # This controller is the abstract class from which all other controllers of
6
6
  # this engine inherit.
7
7
  #
8
- # Note that it inherits from `Decidim::Features::BaseController`, which
8
+ # Note that it inherits from `Decidim::Components::BaseController`, which
9
9
  # override its layout and provide all kinds of useful methods.
10
- class ApplicationController < Decidim::Features::BaseController
10
+ class ApplicationController < Decidim::Components::BaseController
11
11
  end
12
12
  end
13
13
  end
@@ -8,7 +8,7 @@ module Decidim
8
8
  private
9
9
 
10
10
  def model
11
- @model ||= Result.where(feature: params[:feature_id]).find(params[:result_id])
11
+ @model ||= Result.where(component: params[:component_id]).find(params[:result_id])
12
12
  end
13
13
 
14
14
  def iframe_url
@@ -19,7 +19,7 @@ module Decidim
19
19
  end
20
20
 
21
21
  def result
22
- @result ||= Result.includes(:timeline_entries).where(feature: current_feature).find(params[:id])
22
+ @result ||= Result.includes(:timeline_entries).where(component: current_component).find(params[:id])
23
23
  end
24
24
 
25
25
  def search_klass
@@ -35,7 +35,7 @@ module Decidim
35
35
  end
36
36
 
37
37
  def context_params
38
- { feature: current_feature, organization: current_organization }
38
+ { component: current_component, organization: current_organization }
39
39
  end
40
40
 
41
41
  def first_class_categories
@@ -43,7 +43,7 @@ module Decidim
43
43
  end
44
44
 
45
45
  def count_calculator(scope_id, category_id)
46
- Decidim::Accountability::ResultsCalculator.new(current_feature, scope_id, category_id).count
46
+ Decidim::Accountability::ResultsCalculator.new(current_component, scope_id, category_id).count
47
47
  end
48
48
  end
49
49
  end
@@ -12,7 +12,7 @@ module Decidim
12
12
  private
13
13
 
14
14
  def result
15
- @result ||= Result.includes(:timeline_entries).where(feature: current_feature).find(params[:result_id])
15
+ @result ||= Result.includes(:timeline_entries).where(component: current_component).find(params[:result_id])
16
16
  end
17
17
 
18
18
  def current_version
@@ -14,7 +14,7 @@ module Decidim
14
14
  end
15
15
 
16
16
  def proposal
17
- @proposal ||= resource.linked_resources(:proposals, "included_proposals").where(id: extra[:proposal_id]).first
17
+ @proposal ||= resource.linked_resources(:proposals, "included_proposals").find_by(id: extra[:proposal_id])
18
18
  end
19
19
  end
20
20
  end
@@ -32,7 +32,7 @@ module Decidim
32
32
 
33
33
  validate :scope_belongs_to_participatory_space_scope
34
34
 
35
- delegate :categories, to: :current_feature
35
+ delegate :categories, to: :current_component
36
36
 
37
37
  def map_model(model)
38
38
  self.proposal_ids = model.linked_resources(:proposals, "included_proposals").pluck(:id)
@@ -42,13 +42,13 @@ module Decidim
42
42
 
43
43
  def proposals
44
44
  @proposals ||= Decidim.find_resource_manifest(:proposals)
45
- .try(:resource_scope, current_feature)
45
+ .try(:resource_scope, current_component)
46
46
  &.where(id: proposal_ids)
47
47
  &.order(title: :asc)
48
48
  end
49
49
 
50
50
  def projects
51
- @projects ||= Decidim.find_resource_manifest(:projects).try(:resource_scope, current_feature)&.order(title: :asc)
51
+ @projects ||= Decidim.find_resource_manifest(:projects).try(:resource_scope, current_component)&.order(title: :asc)
52
52
  &.select(:title, :id)&.map { |a| [a.title[I18n.locale.to_s], a.id] }
53
53
  end
54
54
 
@@ -71,11 +71,11 @@ module Decidim
71
71
  end
72
72
 
73
73
  def parent
74
- @parent ||= Decidim::Accountability::Result.find_by(feature: current_feature, id: parent_id)
74
+ @parent ||= Decidim::Accountability::Result.find_by(component: current_component, id: parent_id)
75
75
  end
76
76
 
77
77
  def status
78
- @status ||= Decidim::Accountability::Status.find_by(feature: current_feature, id: decidim_accountability_status_id)
78
+ @status ||= Decidim::Accountability::Status.find_by(component: current_component, id: decidim_accountability_status_id)
79
79
  end
80
80
 
81
81
  private
@@ -23,15 +23,15 @@ module Decidim
23
23
  end
24
24
 
25
25
  def categories_label
26
- translated_attribute(feature_settings.categories_label).presence || t("results.home.categories_label", scope: "decidim.accountability")
26
+ translated_attribute(component_settings.categories_label).presence || t("results.home.categories_label", scope: "decidim.accountability")
27
27
  end
28
28
 
29
29
  def subcategories_label
30
- translated_attribute(feature_settings.subcategories_label).presence || t("results.home.subcategories_label", scope: "decidim.accountability")
30
+ translated_attribute(component_settings.subcategories_label).presence || t("results.home.subcategories_label", scope: "decidim.accountability")
31
31
  end
32
32
 
33
33
  def heading_parent_level_results(count)
34
- text = translated_attribute(feature_settings.heading_parent_level_results).presence
34
+ text = translated_attribute(component_settings.heading_parent_level_results).presence
35
35
  if text
36
36
  "#{count} #{text}"
37
37
  else
@@ -40,7 +40,7 @@ module Decidim
40
40
  end
41
41
 
42
42
  def heading_leaf_level_results(count)
43
- text = translated_attribute(feature_settings.heading_leaf_level_results).presence
43
+ text = translated_attribute(component_settings.heading_leaf_level_results).presence
44
44
  if text
45
45
  "#{count} #{text}"
46
46
  else
@@ -14,7 +14,7 @@ module Decidim
14
14
  end
15
15
 
16
16
  def progress_calculator(scope_id, category_id)
17
- Decidim::Accountability::ResultsCalculator.new(current_feature, scope_id, category_id).progress
17
+ Decidim::Accountability::ResultsCalculator.new(current_component, scope_id, category_id).progress
18
18
  end
19
19
 
20
20
  def category
@@ -6,14 +6,15 @@ module Decidim
6
6
  # title, description and any other useful information to render a custom result.
7
7
  class Result < Accountability::ApplicationRecord
8
8
  include Decidim::Resourceable
9
- include Decidim::HasFeature
10
- include Decidim::ScopableFeature
9
+ include Decidim::HasComponent
10
+ include Decidim::ScopableComponent
11
11
  include Decidim::HasCategory
12
12
  include Decidim::HasReference
13
13
  include Decidim::Comments::Commentable
14
14
  include Decidim::Traceable
15
+ include Decidim::Loggable
15
16
 
16
- feature_manifest_name "accountability"
17
+ component_manifest_name "accountability"
17
18
 
18
19
  has_many :children, foreign_key: "parent_id", class_name: "Decidim::Accountability::Result", inverse_of: :parent, dependent: :destroy
19
20
  belongs_to :parent, foreign_key: "parent_id", class_name: "Decidim::Accountability::Result", inverse_of: :children, optional: true, counter_cache: :children_count
@@ -32,6 +33,10 @@ module Decidim
32
33
  end
33
34
  end
34
35
 
36
+ def self.log_presenter_class_for(_log)
37
+ Decidim::Accountability::AdminLog::ResultPresenter
38
+ end
39
+
35
40
  def update_parent_progress
36
41
  return if parent.blank?
37
42
 
@@ -45,12 +50,12 @@ module Decidim
45
50
 
46
51
  # Public: Overrides the `commentable?` Commentable concern method.
47
52
  def commentable?
48
- feature.settings.comments_enabled?
53
+ component.settings.comments_enabled?
49
54
  end
50
55
 
51
56
  # Public: Overrides the `accepts_new_comments?` Commentable concern method.
52
57
  def accepts_new_comments?
53
- commentable? && !feature.current_settings.comments_blocked
58
+ commentable? && !component.current_settings.comments_blocked
54
59
  end
55
60
 
56
61
  # Public: Overrides the `comments_have_alignment?` Commentable concern method.
@@ -5,13 +5,13 @@ module Decidim
5
5
  # The data store for a Status in the Decidim::Accountability component. It stores a
6
6
  # key, a localized name, a localized description and and associated progress number.
7
7
  class Status < Accountability::ApplicationRecord
8
- include Decidim::HasFeature
8
+ include Decidim::HasComponent
9
9
 
10
- feature_manifest_name "accountability"
10
+ component_manifest_name "accountability"
11
11
 
12
12
  has_many :results, foreign_key: "decidim_accountability_status_id", class_name: "Decidim::Accountability::Result", inverse_of: :status, dependent: :nullify
13
13
 
14
- validates :key, presence: true, uniqueness: { scope: :decidim_feature_id }
14
+ validates :key, presence: true, uniqueness: { scope: :decidim_component_id }
15
15
  validates :name, presence: true
16
16
  end
17
17
  end