decidim-proposals 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -1
  3. data/app/assets/config/admin/decidim_proposals_manifest.js +1 -0
  4. data/app/assets/javascripts/decidim/proposals/admin/proposals.es6 +113 -0
  5. data/app/assets/javascripts/decidim/proposals/identity_selector_dialog.js.es6 +56 -0
  6. data/app/commands/decidim/proposals/admin/answer_proposal.rb +11 -5
  7. data/app/commands/decidim/proposals/admin/create_proposal.rb +25 -3
  8. data/app/commands/decidim/proposals/admin/create_proposal_note.rb +13 -8
  9. data/app/commands/decidim/proposals/admin/import_proposals.rb +83 -0
  10. data/app/commands/decidim/proposals/admin/update_proposal_category.rb +68 -0
  11. data/app/commands/decidim/proposals/create_proposal.rb +0 -12
  12. data/app/commands/decidim/proposals/endorse_proposal.rb +56 -0
  13. data/app/commands/decidim/proposals/publish_proposal.rb +60 -0
  14. data/app/commands/decidim/proposals/unendorse_proposal.rb +40 -0
  15. data/app/commands/decidim/proposals/update_proposal.rb +3 -3
  16. data/app/commands/decidim/proposals/vote_proposal.rb +1 -1
  17. data/app/commands/decidim/proposals/withdraw_proposal.rb +1 -1
  18. data/app/controllers/decidim/proposals/admin/proposal_answers_controller.rb +1 -1
  19. data/app/controllers/decidim/proposals/admin/proposal_notes_controller.rb +2 -2
  20. data/app/controllers/decidim/proposals/admin/proposals_controller.rb +50 -1
  21. data/app/controllers/decidim/proposals/admin/proposals_imports_controller.rb +35 -0
  22. data/app/controllers/decidim/proposals/proposal_endorsements_controller.rb +56 -0
  23. data/app/controllers/decidim/proposals/proposals_controller.rb +82 -9
  24. data/app/events/decidim/proposals/admin/update_proposal_category_event.rb +11 -0
  25. data/app/events/decidim/proposals/creation_enabled_event.rb +8 -0
  26. data/app/events/decidim/proposals/endorsing_enabled_event.rb +8 -0
  27. data/app/events/decidim/proposals/proposal_endorsed_event.rb +29 -0
  28. data/app/events/decidim/proposals/publish_proposal_event.rb +21 -0
  29. data/app/events/decidim/proposals/voting_enabled_event.rb +8 -0
  30. data/app/forms/decidim/proposals/admin/proposal_form.rb +9 -2
  31. data/app/forms/decidim/proposals/admin/proposals_import_form.rb +60 -0
  32. data/app/forms/decidim/proposals/proposal_form.rb +16 -5
  33. data/app/helpers/decidim/proposals/application_helper.rb +1 -0
  34. data/app/helpers/decidim/proposals/proposal_endorsements_helper.rb +117 -0
  35. data/app/helpers/decidim/proposals/proposal_votes_helper.rb +13 -6
  36. data/app/helpers/decidim/proposals/proposal_wizard_helper.rb +105 -0
  37. data/app/jobs/decidim/proposals/settings_change_job.rb +48 -0
  38. data/app/models/decidim/proposals/abilities/current_user_ability.rb +30 -8
  39. data/app/models/decidim/proposals/proposal.rb +38 -38
  40. data/app/models/decidim/proposals/proposal_endorsement.rb +31 -0
  41. data/app/models/decidim/proposals/proposal_note.rb +7 -0
  42. data/app/presenters/decidim/proposals/admin_log/proposal_note_presenter.rb +39 -0
  43. data/app/presenters/decidim/proposals/admin_log/proposal_presenter.rb +47 -0
  44. data/app/presenters/decidim/proposals/admin_log/value_types/proposal_state_presenter.rb +16 -0
  45. data/app/queries/decidim/proposals/similar_proposals.rb +53 -0
  46. data/app/types/decidim/proposals/proposal_type.rb +34 -0
  47. data/app/types/decidim/proposals/proposals_type.rb +34 -0
  48. data/app/views/decidim/participatory_processes/participatory_process_groups/_highlighted_proposals.html.erb +8 -0
  49. data/app/views/decidim/participatory_processes/participatory_process_groups/_proposal.html.erb +27 -0
  50. data/app/views/decidim/participatory_spaces/_highlighted_proposals.html.erb +10 -0
  51. data/app/views/decidim/participatory_spaces/_proposal.html.erb +27 -0
  52. data/app/views/decidim/proposals/admin/proposals/_bulk-actions.html.erb +15 -0
  53. data/app/views/decidim/proposals/admin/proposals/_js-callout.html.erb +6 -0
  54. data/app/views/decidim/proposals/admin/proposals/_proposal-tr.html.erb +63 -0
  55. data/app/views/decidim/proposals/admin/proposals/index.html.erb +12 -73
  56. data/app/views/decidim/proposals/admin/proposals/update_category.js.erb +25 -0
  57. data/app/views/decidim/proposals/admin/proposals_imports/new.html.erb +28 -0
  58. data/app/views/decidim/proposals/proposal_endorsements/_identity.html.erb +4 -0
  59. data/app/views/decidim/proposals/proposal_endorsements/identities.html.erb +12 -0
  60. data/app/views/decidim/proposals/proposal_endorsements/update_buttons_and_counters.js.erb +9 -0
  61. data/app/views/decidim/proposals/proposals/_endorsement_button.html.erb +11 -0
  62. data/app/views/decidim/proposals/proposals/_endorsement_identities_cabin.html.erb +13 -0
  63. data/app/views/decidim/proposals/proposals/_endorsement_xxs.html.erb +9 -0
  64. data/app/views/decidim/proposals/proposals/_endorsements_card_row.html.erb +22 -0
  65. data/app/views/decidim/proposals/proposals/_endorsements_count.html.erb +5 -0
  66. data/app/views/decidim/proposals/proposals/_endorsements_listing.html.erb +34 -0
  67. data/app/views/decidim/proposals/proposals/_proposal.html.erb +2 -2
  68. data/app/views/decidim/proposals/proposals/_proposal_preview.html.erb +36 -0
  69. data/app/views/decidim/proposals/proposals/_proposal_similar.html.erb +21 -0
  70. data/app/views/decidim/proposals/proposals/_vote_button.html.erb +8 -8
  71. data/app/views/decidim/proposals/proposals/_votes_count.html.erb +23 -6
  72. data/app/views/decidim/proposals/proposals/_voting_rules.html.erb +7 -3
  73. data/app/views/decidim/proposals/proposals/_wizard_aside.html.erb +16 -0
  74. data/app/views/decidim/proposals/proposals/_wizard_header.html.erb +31 -0
  75. data/app/views/decidim/proposals/proposals/compare.html.erb +19 -0
  76. data/app/views/decidim/proposals/proposals/edit_draft.html.erb +55 -0
  77. data/app/views/decidim/proposals/proposals/new.html.erb +7 -20
  78. data/app/views/decidim/proposals/proposals/preview.html.erb +18 -0
  79. data/app/views/decidim/proposals/proposals/show.html.erb +13 -4
  80. data/config/locales/ca.yml +156 -15
  81. data/config/locales/en.yml +156 -15
  82. data/config/locales/es.yml +157 -16
  83. data/config/locales/eu.yml +151 -7
  84. data/config/locales/fi.yml +151 -7
  85. data/config/locales/fr.yml +153 -9
  86. data/config/locales/gl.yml +151 -7
  87. data/config/locales/it.yml +151 -7
  88. data/config/locales/nl.yml +151 -7
  89. data/config/locales/pl.yml +148 -22
  90. data/config/locales/pt-BR.yml +151 -7
  91. data/config/locales/pt.yml +151 -7
  92. data/config/locales/ru.yml +0 -9
  93. data/config/locales/sv.yml +151 -7
  94. data/config/locales/uk.yml +87 -13
  95. data/db/migrate/20170307085300_migrate_proposal_reports_data_to_reports.rb +1 -1
  96. data/db/migrate/20171201115434_create_proposal_endorsements.rb +16 -0
  97. data/db/migrate/20171201122623_add_counter_cache_endorsements_to_proposals.rb +8 -0
  98. data/db/migrate/20171212102250_enable_pg_extensions.rb +7 -0
  99. data/db/migrate/20171220084719_add_published_at_to_proposals.rb +14 -0
  100. data/lib/decidim/proposals.rb +15 -0
  101. data/lib/decidim/proposals/admin_engine.rb +8 -0
  102. data/lib/decidim/proposals/commentable_proposal.rb +39 -0
  103. data/lib/decidim/proposals/engine.rb +69 -1
  104. data/lib/decidim/proposals/feature.rb +51 -6
  105. data/lib/decidim/proposals/test/factories.rb +78 -2
  106. data/lib/decidim/proposals/version.rb +1 -1
  107. metadata +76 -20
  108. data/app/events/decidim/proposals/create_proposal_event.rb +0 -9
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ class SettingsChangeJob < ApplicationJob
6
+ def perform(feature_id, previous_settings, current_settings)
7
+ feature = Decidim::Feature.find(feature_id)
8
+
9
+ if creation_enabled?(previous_settings, current_settings)
10
+ event = "decidim.events.proposals.creation_enabled"
11
+ event_class = Decidim::Proposals::CreationEnabledEvent
12
+ elsif voting_enabled?(previous_settings, current_settings)
13
+ event = "decidim.events.proposals.voting_enabled"
14
+ event_class = Decidim::Proposals::VotingEnabledEvent
15
+ elsif endorsing_enabled?(previous_settings, current_settings)
16
+ event = "decidim.events.proposals.endorsing_enabled"
17
+ event_class = Decidim::Proposals::EndorsingEnabledEvent
18
+ end
19
+
20
+ return unless event && event_class
21
+
22
+ Decidim::EventsManager.publish(
23
+ event: event,
24
+ event_class: event_class,
25
+ resource: feature,
26
+ recipient_ids: feature.participatory_space.followers.pluck(:id)
27
+ )
28
+ end
29
+
30
+ private
31
+
32
+ def creation_enabled?(previous_settings, current_settings)
33
+ current_settings[:creation_enabled] == true &&
34
+ previous_settings[:creation_enabled] == false
35
+ end
36
+
37
+ def voting_enabled?(previous_settings, current_settings)
38
+ (current_settings[:votes_enabled] == true && current_settings[:votes_blocked] == false) &&
39
+ (previous_settings[:votes_enabled] == false || previous_settings[:votes_blocked] == true)
40
+ end
41
+
42
+ def endorsing_enabled?(previous_settings, current_settings)
43
+ (current_settings[:endorsements_enabled] == true && current_settings[:endorsements_blocked] == false) &&
44
+ (previous_settings[:endorsements_enabled] == false || previous_settings[:endorsements_blocked] == true)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -16,14 +16,8 @@ module Decidim
16
16
  @user = user
17
17
  @context = context
18
18
 
19
- can :vote, Proposal do |_proposal|
20
- authorized?(:vote) && voting_enabled? && remaining_votes.positive?
21
- end
22
-
23
- can :unvote, Proposal do |_proposal|
24
- authorized?(:vote) && voting_enabled?
25
- end
26
-
19
+ setup_endorsement_related_abilities
20
+ setup_voting_related_abilities
27
21
  can :create, Proposal if authorized?(:create) && creation_enabled?
28
22
  can :edit, Proposal do |proposal|
29
23
  proposal.editable_by?(user)
@@ -38,6 +32,24 @@ module Decidim
38
32
 
39
33
  private
40
34
 
35
+ def setup_endorsement_related_abilities
36
+ can :endorse, Proposal do |_proposal|
37
+ authorized?(:endorse) && endorsements_enabled? && !endorsements_blocked?
38
+ end
39
+ can :unendorse, Proposal do |_proposal|
40
+ authorized?(:unendorse) && endorsements_enabled?
41
+ end
42
+ end
43
+
44
+ def setup_voting_related_abilities
45
+ can :vote, Proposal do |_proposal|
46
+ authorized?(:vote) && voting_enabled? && remaining_votes.positive?
47
+ end
48
+ can :unvote, Proposal do |_proposal|
49
+ authorized?(:vote) && voting_enabled?
50
+ end
51
+ end
52
+
41
53
  def authorized?(action)
42
54
  return unless feature
43
55
 
@@ -62,6 +74,16 @@ module Decidim
62
74
  feature_settings.vote_limit - votes_count
63
75
  end
64
76
 
77
+ def endorsements_enabled?
78
+ return unless current_settings
79
+ current_settings.endorsements_enabled?
80
+ end
81
+
82
+ def endorsements_blocked?
83
+ return unless current_settings
84
+ current_settings.endorsements_blocked?
85
+ end
86
+
65
87
  def voting_enabled?
66
88
  return unless current_settings
67
89
  current_settings.votes_enabled? && !current_settings.votes_blocked?
@@ -7,16 +7,19 @@ module Decidim
7
7
  include Decidim::Resourceable
8
8
  include Decidim::Authorable
9
9
  include Decidim::HasFeature
10
- include Decidim::HasScope
10
+ include Decidim::ScopableFeature
11
11
  include Decidim::HasReference
12
12
  include Decidim::HasCategory
13
13
  include Decidim::Reportable
14
14
  include Decidim::HasAttachments
15
15
  include Decidim::Followable
16
- include Decidim::Comments::Commentable
16
+ include Decidim::Proposals::CommentableProposal
17
+ include Decidim::Traceable
18
+ include Decidim::Loggable
17
19
 
18
20
  feature_manifest_name "proposals"
19
21
 
22
+ has_many :endorsements, foreign_key: "decidim_proposal_id", class_name: "ProposalEndorsement", dependent: :destroy, counter_cache: "proposal_endorsements_count"
20
23
  has_many :votes, foreign_key: "decidim_proposal_id", class_name: "ProposalVote", dependent: :destroy, counter_cache: "proposal_votes_count"
21
24
  has_many :notes, foreign_key: "decidim_proposal_id", class_name: "ProposalNote", dependent: :destroy, counter_cache: "proposal_notes_count"
22
25
 
@@ -29,6 +32,7 @@ module Decidim
29
32
  scope :evaluating, -> { where(state: "evaluating") }
30
33
  scope :withdrawn, -> { where(state: "withdrawn") }
31
34
  scope :except_withdrawn, -> { where.not(state: "withdrawn").or(where(state: nil)) }
35
+ scope :published, -> { where.not(published_at: nil) }
32
36
 
33
37
  def self.order_randomly(seed)
34
38
  transaction do
@@ -37,6 +41,10 @@ module Decidim
37
41
  end
38
42
  end
39
43
 
44
+ def self.log_presenter_class_for(_log)
45
+ Decidim::Proposals::AdminLog::ProposalPresenter
46
+ end
47
+
40
48
  # Public: Check if the user has voted the proposal.
41
49
  #
42
50
  # Returns Boolean.
@@ -44,6 +52,14 @@ module Decidim
44
52
  votes.where(author: user).any?
45
53
  end
46
54
 
55
+ # Public: Check if the user has endorsed the proposal.
56
+ # - user_group: may be nil if user is not representing any user_group.
57
+ #
58
+ # Returns Boolean.
59
+ def endorsed_by?(user, user_group = nil)
60
+ endorsements.where(author: user, user_group: user_group).any?
61
+ end
62
+
47
63
  # Public: Checks if the organization has given an answer for the proposal.
48
64
  #
49
65
  # Returns Boolean.
@@ -79,32 +95,6 @@ module Decidim
79
95
  state == "withdrawn"
80
96
  end
81
97
 
82
- # Public: Overrides the `commentable?` Commentable concern method.
83
- def commentable?
84
- feature.settings.comments_enabled?
85
- end
86
-
87
- # Public: Overrides the `accepts_new_comments?` Commentable concern method.
88
- def accepts_new_comments?
89
- commentable? && !feature.current_settings.comments_blocked
90
- end
91
-
92
- # Public: Overrides the `comments_have_alignment?` Commentable concern method.
93
- def comments_have_alignment?
94
- true
95
- end
96
-
97
- # Public: Overrides the `comments_have_votes?` Commentable concern method.
98
- def comments_have_votes?
99
- true
100
- end
101
-
102
- # Public: Override Commentable concern method `users_to_notify_on_comment_created`
103
- def users_to_notify_on_comment_created
104
- return (followers | feature.participatory_space.admins).uniq if official?
105
- followers
106
- end
107
-
108
98
  # Public: Overrides the `reported_content_url` Reportable concern method.
109
99
  def reported_content_url
110
100
  ResourceLocatorPresenter.new(self).url
@@ -119,7 +109,7 @@ module Decidim
119
109
  #
120
110
  # Returns an Integer with the maximum amount of votes, nil otherwise.
121
111
  def maximum_votes
122
- maximum_votes = feature.settings.maximum_votes_per_proposal
112
+ maximum_votes = feature.settings.threshold_per_proposal
123
113
  return nil if maximum_votes.zero?
124
114
 
125
115
  maximum_votes
@@ -134,26 +124,31 @@ module Decidim
134
124
  votes.count >= maximum_votes
135
125
  end
136
126
 
137
- # Checks whether the user is author of the given proposal, either directly
138
- # authoring it or via a user group.
127
+ # Public: Can accumulate more votres than maximum for this proposal.
139
128
  #
140
- # user - the user to check for authorship
141
- def authored_by?(user)
142
- author == user || user.user_groups.include?(user_group)
129
+ # Returns true if can accumulate, false otherwise
130
+ def can_accumulate_supports_beyond_threshold
131
+ feature.settings.can_accumulate_supports_beyond_threshold
143
132
  end
144
133
 
145
134
  # Checks whether the user can edit the given proposal.
146
135
  #
147
136
  # user - the user to check for authorship
148
137
  def editable_by?(user)
149
- authored_by?(user) && !answered? && within_edit_time_limit?
138
+ return true if draft?
139
+ authored_by?(user) && !answered? && within_edit_time_limit? && !copied_from_other_component?
150
140
  end
151
141
 
152
142
  # Checks whether the user can withdraw the given proposal.
153
143
  #
154
144
  # user - the user to check for withdrawability.
155
145
  def withdrawable_by?(user)
156
- user && !withdrawn? && authored_by?(user)
146
+ user && !withdrawn? && authored_by?(user) && !copied_from_other_component?
147
+ end
148
+
149
+ # Public: Whether the proposal is a draft or not.
150
+ def draft?
151
+ published_at.nil?
157
152
  end
158
153
 
159
154
  # method for sort_link by number of comments
@@ -171,11 +166,16 @@ module Decidim
171
166
 
172
167
  private
173
168
 
174
- # Checks whether the proposal is inside the time window to be editable or not.
169
+ # Checks whether the proposal is inside the time window to be editable or not once published.
175
170
  def within_edit_time_limit?
176
- limit = created_at + feature.settings.proposal_edit_before_minutes.minutes
171
+ return true if draft?
172
+ limit = updated_at + feature.settings.proposal_edit_before_minutes.minutes
177
173
  Time.current < limit
178
174
  end
175
+
176
+ def copied_from_other_component?
177
+ linked_resources(:proposals, "copied_from_component").any?
178
+ end
179
179
  end
180
180
  end
181
181
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # A proposal can include an endorsement per user or group.
6
+ class ProposalEndorsement < ApplicationRecord
7
+ belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal", counter_cache: true
8
+ belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
9
+ belongs_to :user_group, foreign_key: "decidim_user_group_id", class_name: "Decidim::UserGroup", optional: true
10
+
11
+ validates :proposal, uniqueness: { scope: [:author, :user_group] }
12
+ validate :author_and_proposal_same_organization
13
+ validate :proposal_not_rejected
14
+
15
+ scope :for_listing, -> { order(:decidim_user_group_id, :created_at) }
16
+
17
+ private
18
+
19
+ # Private: check if the proposal and the author have the same organization
20
+ def author_and_proposal_same_organization
21
+ return if !proposal || !author
22
+ errors.add(:proposal, :invalid) unless author.organization == proposal.organization
23
+ end
24
+
25
+ def proposal_not_rejected
26
+ return unless proposal
27
+ errors.add(:proposal, :invalid) if proposal.rejected?
28
+ end
29
+ end
30
+ end
31
+ end
@@ -4,10 +4,17 @@ module Decidim
4
4
  module Proposals
5
5
  # A proposal can include a notes created by admins.
6
6
  class ProposalNote < ApplicationRecord
7
+ include Decidim::Traceable
8
+ include Decidim::Loggable
9
+
7
10
  belongs_to :proposal, foreign_key: "decidim_proposal_id", class_name: "Decidim::Proposals::Proposal", counter_cache: true
8
11
  belongs_to :author, foreign_key: "decidim_author_id", class_name: "Decidim::User"
9
12
 
10
13
  default_scope { order(created_at: :asc) }
14
+
15
+ def self.log_presenter_class_for(_log)
16
+ Decidim::Proposals::AdminLog::ProposalNotePresenter
17
+ end
11
18
  end
12
19
  end
13
20
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module AdminLog
6
+ # This class holds the logic to present a `Decidim::Proposals::ProposalNote`
7
+ # for the `AdminLog` log.
8
+ #
9
+ # Usage should be automatic and you shouldn't need to call this class
10
+ # directly, but here's an example:
11
+ #
12
+ # action_log = Decidim::ActionLog.last
13
+ # view_helpers # => this comes from the views
14
+ # ProposalNotePresenter.new(action_log, view_helpers).present
15
+ class ProposalNotePresenter < Decidim::Log::BasePresenter
16
+ private
17
+
18
+ def diff_fields_mapping
19
+ {
20
+ body: :string
21
+ }
22
+ end
23
+
24
+ def action_string
25
+ case action
26
+ when "create"
27
+ "decidim.proposals.admin_log.proposal_note.#{action}"
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def i18n_labels_scope
34
+ "activemodel.attributes.proposal_note"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module AdminLog
6
+ # This class holds the logic to present a `Decidim::Proposals::Proposal`
7
+ # for the `AdminLog` log.
8
+ #
9
+ # Usage should be automatic and you shouldn't need to call this class
10
+ # directly, but here's an example:
11
+ #
12
+ # action_log = Decidim::ActionLog.last
13
+ # view_helpers # => this comes from the views
14
+ # ProposalPresenter.new(action_log, view_helpers).present
15
+ class ProposalPresenter < Decidim::Log::BasePresenter
16
+ private
17
+
18
+ def diff_fields_mapping
19
+ {
20
+ title: :string,
21
+ body: :string,
22
+ state: "Decidim::Proposals::AdminLog::ValueTypes::ProposalStatePresenter",
23
+ answered_at: :date,
24
+ answer: :i18n
25
+ }
26
+ end
27
+
28
+ def action_string
29
+ case action
30
+ when "answer", "create"
31
+ "decidim.proposals.admin_log.proposal.#{action}"
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def i18n_labels_scope
38
+ "activemodel.attributes.proposal"
39
+ end
40
+
41
+ def has_diff?
42
+ action == "answer" || super
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ module AdminLog
6
+ module ValueTypes
7
+ class ProposalStatePresenter < Decidim::Log::ValueTypes::DefaultPresenter
8
+ def present
9
+ return unless value
10
+ h.t(value, scope: "decidim.proposals.admin.proposal_answers.edit", default: value)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Proposals
5
+ # Class used to retrieve similar proposals.
6
+ class SimilarProposals < Rectify::Query
7
+ include Decidim::TranslationsHelper
8
+
9
+ # Syntactic sugar to initialize the class and return the queried objects.
10
+ #
11
+ # features - Decidim::CurrentFeature
12
+ # proposal - Decidim::Proposals::Proposal
13
+ def self.for(features, proposal)
14
+ new(features, proposal).query
15
+ end
16
+
17
+ # Initializes the class.
18
+ #
19
+ # features - Decidim::CurrentFeature
20
+ # proposal - Decidim::Proposals::Proposal
21
+ def initialize(features, proposal)
22
+ @features = features
23
+ @proposal = proposal
24
+ end
25
+
26
+ # Retrieves similar proposals
27
+ def query
28
+ Decidim::Proposals::Proposal
29
+ .where(feature: @features)
30
+ .published
31
+ .where(
32
+ "GREATEST(#{title_similarity}, #{body_similarity}) >= ?",
33
+ proposal.title,
34
+ proposal.body,
35
+ Decidim::Proposals.similarity_threshold
36
+ )
37
+ .limit(Decidim::Proposals.similarity_limit)
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :proposal
43
+
44
+ def title_similarity
45
+ "similarity(title, ?)"
46
+ end
47
+
48
+ def body_similarity
49
+ "similarity(body, ?)"
50
+ end
51
+ end
52
+ end
53
+ end