Taggings
diff --git a/app/views/admin_public_body/show.html.erb b/app/views/admin_public_body/show.html.erb
index a21205d719..48a4a8df79 100644
--- a/app/views/admin_public_body/show.html.erb
+++ b/app/views/admin_public_body/show.html.erb
@@ -131,3 +131,11 @@
Censor rules
<%= render :partial => 'admin_censor_rule/show', :locals => { :censor_rules => @public_body.censor_rules, :public_body => @public_body } %>
+
+
+
+
Notes
+
+<%= render partial: 'admin/notes/show',
+ locals: { notes: @public_body.all_notes,
+ notable: @public_body } %>
diff --git a/config/routes.rb b/config/routes.rb
index ed94afd313..e070c8726f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -485,6 +485,22 @@
end
####
+ #### AdminNote controller
+ namespace :admin do
+ resources :notes, except: [:index, :show]
+ end
+
+ direct :admin_note_parent do |note|
+ if note.notable_tag
+ admin_tag_path(tag: note.notable_tag)
+ elsif note.notable
+ url_for([:admin, note.notable])
+ else
+ admin_general_index_path
+ end
+ end
+ ####
+
#### AdminPublicBody controller
scope '/admin', :as => 'admin' do
resources :bodies,
@@ -498,6 +514,9 @@
:only => [:new, :create]
end
end
+ direct :admin_public_body do |pb|
+ admin_body_path(pb)
+ end
####
#### AdminPublicBodyCategory controller
@@ -569,6 +588,9 @@
:only => [:new, :create]
end
end
+ direct :admin_info_request do |ir|
+ admin_request_path(ir)
+ end
####
#### AdminComment controller
diff --git a/db/migrate/20220720085105_create_notes.rb b/db/migrate/20220720085105_create_notes.rb
new file mode 100644
index 0000000000..5e50edb3b8
--- /dev/null
+++ b/db/migrate/20220720085105_create_notes.rb
@@ -0,0 +1,19 @@
+class CreateNotes < ActiveRecord::Migration[6.1]
+ def change
+ create_table :notes do |t|
+ t.references :notable, polymorphic: true
+ t.string :notable_tag
+ t.timestamps
+ end
+
+ reversible do |dir|
+ dir.up do
+ Note.create_translation_table!(body: :text)
+ end
+
+ dir.down do
+ Note.drop_translation_table!
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/notes_controller_spec.rb b/spec/controllers/admin/notes_controller_spec.rb
new file mode 100644
index 0000000000..85f6c0bc7c
--- /dev/null
+++ b/spec/controllers/admin/notes_controller_spec.rb
@@ -0,0 +1,232 @@
+require 'spec_helper'
+
+RSpec.describe Admin::NotesController do
+ before(:each) { basic_auth_login(@request) }
+
+ describe 'GET new' do
+ before { get :new }
+
+ it 'returns a successful response' do
+ expect(response).to be_successful
+ end
+
+ it 'assigns the note' do
+ expect(assigns[:note]).to be_a(Note)
+ end
+
+ it 'renders the correct template' do
+ expect(response).to render_template(:new)
+ end
+ end
+
+ describe 'POST #create' do
+ before do
+ post :create, params: params
+ end
+
+ shared_context 'successful create' do
+ it 'assigns the note' do
+ expect(assigns[:note]).to be_a(Note)
+ end
+
+ it 'creates the note' do
+ expect(assigns[:note].body).to eq('New body')
+ end
+
+ it 'sets a notice' do
+ expect(flash[:notice]).to eq('Note successfully created.')
+ end
+ end
+
+ context 'on a successful create of concrete note' do
+ include_context 'successful create'
+
+ let!(:note) { FactoryBot.create(:note, :for_public_body) }
+ let(:public_body) { note.notable }
+
+ let(:params) do
+ {
+ id: note.id,
+ note: {
+ body: 'New body',
+ notable_id: public_body.id,
+ notable_type: public_body.class.name
+ }
+ }
+ end
+
+ it 'redirects to the public body admin' do
+ expect(response).to redirect_to(admin_public_body_path(public_body))
+ end
+ end
+
+ context 'on a successful create of tagged note' do
+ include_context 'successful create'
+
+ let!(:note) { FactoryBot.create(:note, :tagged) }
+ let(:tag) { note.notable_tag }
+
+ let(:params) do
+ {
+ id: note.id,
+ note: { body: 'New body', notable_tag: tag }
+ }
+ end
+
+ it 'redirects to the tag admin' do
+ expect(response).to redirect_to(admin_tag_path(tag))
+ end
+ end
+
+ context 'on an unsuccessful create' do
+ let(:params) do
+ { note: { body: '' } }
+ end
+
+ it 'assigns the note' do
+ expect(assigns[:note]).to be_a(Note)
+ end
+
+ it 'does not create the note' do
+ expect(assigns[:note]).to be_new_record
+ end
+
+ it 'renders the form again' do
+ expect(response).to render_template(:new)
+ end
+ end
+ end
+
+ describe 'GET edit' do
+ let!(:note) { FactoryBot.create(:note) }
+
+ before { get :edit, params: { id: note.id } }
+
+ it 'returns a successful response' do
+ expect(response).to be_successful
+ end
+
+ it 'assigns the note' do
+ expect(assigns[:note]).to eq(note)
+ end
+
+ it 'renders the correct template' do
+ expect(response).to render_template(:edit)
+ end
+ end
+
+ describe 'PATCH #update' do
+ let!(:note) { FactoryBot.create(:note) }
+
+ before do
+ patch :update, params: params
+ end
+
+ shared_context 'successful update' do
+ it 'assigns the note' do
+ expect(assigns[:note]).to eq(note)
+ end
+
+ it 'updates the note' do
+ expect(note.reload.body).to eq('New body')
+ end
+
+ it 'sets a notice' do
+ expect(flash[:notice]).to eq('Note successfully updated.')
+ end
+ end
+
+ context 'on a successful update of concrete note' do
+ include_context 'successful update'
+
+ let!(:note) { FactoryBot.create(:note, :for_public_body) }
+ let(:public_body) { note.notable }
+
+ let(:params) do
+ {
+ id: note.id,
+ note: {
+ body: 'New body',
+ notable_id: public_body.id,
+ notable_type: public_body.class.name
+ }
+ }
+ end
+
+ it 'redirects to the public body admin' do
+ expect(response).to redirect_to(admin_public_body_path(public_body))
+ end
+ end
+
+ context 'on a successful update of tagged note' do
+ include_context 'successful update'
+
+ let!(:note) { FactoryBot.create(:note, :tagged) }
+ let(:tag) { note.notable_tag }
+
+ let(:params) do
+ {
+ id: note.id,
+ note: { body: 'New body', notable_tag: tag }
+ }
+ end
+
+ it 'redirects to the tag admin' do
+ expect(response).to redirect_to(admin_tag_path(tag))
+ end
+ end
+
+ context 'on an unsuccessful update' do
+ let(:params) do
+ { id: note.id, note: { body: '' } }
+ end
+
+ it 'assigns the note' do
+ expect(assigns[:note]).to eq(note)
+ end
+
+ it 'does not update the note' do
+ expect(note.reload.body).not_to be_blank
+ end
+
+ it 'renders the form again' do
+ expect(response).to render_template(:edit)
+ end
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ let!(:note) { FactoryBot.create(:note) }
+
+ it 'destroys the note' do
+ allow(Note).to receive(:find).and_return(note)
+ expect(note).to receive(:destroy)
+ delete :destroy, params: { id: note.id }
+ end
+
+ it 'sets a notice' do
+ delete :destroy, params: { id: note.id }
+ expect(flash[:notice]).to eq('Note successfully destroyed.')
+ end
+
+ context 'when concrete note' do
+ let!(:note) { FactoryBot.create(:note, :for_public_body) }
+ let(:public_body) { note.notable }
+
+ it 'redirects to the public body admin' do
+ delete :destroy, params: { id: note.id }
+ expect(response).to redirect_to(admin_public_body_path(public_body))
+ end
+ end
+
+ context 'when tagged note' do
+ let!(:note) { FactoryBot.create(:note, :tagged) }
+ let(:tag) { note.notable_tag }
+
+ it 'redirects to the public body admin' do
+ delete :destroy, params: { id: note.id }
+ expect(response).to redirect_to(admin_tag_path(tag))
+ end
+ end
+ end
+end
diff --git a/spec/controllers/admin/tags_controller_spec.rb b/spec/controllers/admin/tags_controller_spec.rb
index 833480fec8..756e04c0e4 100644
--- a/spec/controllers/admin/tags_controller_spec.rb
+++ b/spec/controllers/admin/tags_controller_spec.rb
@@ -105,6 +105,15 @@ def tags
)
end
+ it 'loads notes' do
+ note = FactoryBot.create(:note, notable_tag: 'foo')
+ other_note = FactoryBot.create(:note, notable_tag: 'bar')
+
+ get :show, params: { tag: 'foo' }
+ expect(assigns[:notes]).to include(note).once
+ expect(assigns[:notes]).to_not include(other_note)
+ end
+
def taggings
assigns[:taggings]
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
new file mode 100644
index 0000000000..b4f042430f
--- /dev/null
+++ b/spec/factories/notes.rb
@@ -0,0 +1,31 @@
+# == Schema Information
+# Schema version: 20220720085105
+#
+# Table name: notes
+#
+# id :bigint not null, primary key
+# notable_type :string
+# notable_id :bigint
+# notable_tag :string
+# created_at :datetime not null
+# updated_at :datetime not null
+# body :text
+#
+
+FactoryBot.define do
+ factory :note do
+ body { 'Test note' }
+ association :notable, factory: :public_body
+ notable_tag { 'some_tag' }
+
+ trait :for_public_body do
+ association :notable, factory: :public_body
+ notable_tag { nil }
+ end
+
+ trait :tagged do
+ notable { nil }
+ notable_tag { 'foo' }
+ end
+ end
+end
diff --git a/spec/helpers/admin/tag_helper_spec.rb b/spec/helpers/admin/tag_helper_spec.rb
index 7a9153919d..a26de9b07c 100644
--- a/spec/helpers/admin/tag_helper_spec.rb
+++ b/spec/helpers/admin/tag_helper_spec.rb
@@ -42,5 +42,30 @@
to eq(expected)
end
end
+
+ context 'tag with no value, as a string' do
+ let(:record_tag) { 'foo' }
+
+ it 'renders the tag with a link' do
+ expected = '
' \
+ 'foo' \
+ ''
+ expect(helper.render_tag(record_tag)).
+ to eq(expected)
+ end
+ end
+
+ context 'tag with a value, as a string' do
+ let(:record_tag) { 'foo:bar' }
+
+ it 'renders the tag with a link' do
+ expected = '
' \
+ 'foo:' \
+ 'bar' \
+ ''
+ expect(helper.render_tag(record_tag)).
+ to eq(expected)
+ end
+ end
end
end
diff --git a/spec/models/concerns/notable.rb b/spec/models/concerns/notable.rb
new file mode 100644
index 0000000000..c941749878
--- /dev/null
+++ b/spec/models/concerns/notable.rb
@@ -0,0 +1,11 @@
+RSpec.shared_examples 'concerns/notable' do |record|
+ describe '#all_notes' do
+ subject { record.all_notes }
+
+ let!(:note) { FactoryBot.create(:note, notable: record) }
+ let!(:other_note) { FactoryBot.create(:note) }
+
+ it { is_expected.to include note }
+ it { is_expected.to_not include other_note }
+ end
+end
diff --git a/spec/models/concerns/notable_and_taggable.rb b/spec/models/concerns/notable_and_taggable.rb
new file mode 100644
index 0000000000..edf9e13a7d
--- /dev/null
+++ b/spec/models/concerns/notable_and_taggable.rb
@@ -0,0 +1,13 @@
+RSpec.shared_examples 'concerns/notable_and_taggable' do |record|
+ describe '#all_notes' do
+ subject { record.all_notes }
+
+ before { record.tag_string = 'foo' }
+
+ let!(:tagged_note) { FactoryBot.create(:note, notable_tag: 'foo') }
+ let!(:other_tagged_note) { FactoryBot.create(:note, notable_tag: 'bar') }
+
+ it { is_expected.to include tagged_note }
+ it { is_expected.to_not include other_tagged_note }
+ end
+end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
new file mode 100644
index 0000000000..0b82d4d852
--- /dev/null
+++ b/spec/models/note_spec.rb
@@ -0,0 +1,62 @@
+# == Schema Information
+# Schema version: 20220720085105
+#
+# Table name: notes
+#
+# id :bigint not null, primary key
+# notable_type :string
+# notable_id :bigint
+# notable_tag :string
+# created_at :datetime not null
+# updated_at :datetime not null
+# body :text
+#
+
+require 'spec_helper'
+
+RSpec.describe Note, type: :model do
+ let(:note) { FactoryBot.build(:note) }
+
+ describe 'validations' do
+ specify { expect(note).to be_valid }
+
+ it 'requires body' do
+ note.body = nil
+ expect(note).not_to be_valid
+ end
+
+ it 'requires notable or notable_tag' do
+ note.notable = nil
+ note.notable_tag = nil
+ expect(note).not_to be_valid
+
+ note.notable = nil
+ note.notable_tag = 'foo'
+ expect(note).to be_valid
+
+ note.notable = PublicBody.first
+ note.notable_tag = nil
+ expect(note).to be_valid
+ end
+ end
+
+ describe 'translations' do
+ before { note.save! }
+
+ it 'adds translated body' do
+ expect(note.body_translations).to_not include(es: 'body')
+ AlaveteliLocalization.with_locale(:es) { note.body = 'body' }
+ expect(note.body_translations).to include(es: 'body')
+ end
+ end
+
+ describe 'associations' do
+ context 'when info request cited' do
+ let(:note) { FactoryBot.build(:note, :for_public_body) }
+
+ it 'belongs to a public body via polymorphic notable' do
+ expect(note.notable).to be_a PublicBody
+ end
+ end
+ end
+end
diff --git a/spec/models/public_body_spec.rb b/spec/models/public_body_spec.rb
index 26ec36b015..c1bfe0a7a3 100644
--- a/spec/models/public_body_spec.rb
+++ b/spec/models/public_body_spec.rb
@@ -29,8 +29,13 @@
#
require 'spec_helper'
+require 'models/concerns/notable'
+require 'models/concerns/notable_and_taggable'
RSpec.describe PublicBody do
+ it_behaves_like 'concerns/notable', FactoryBot.build(:public_body)
+ it_behaves_like 'concerns/notable_and_taggable',
+ FactoryBot.build(:public_body)
describe <<-EOF.squish do
temporary tests for Globalize::ActiveRecord::InstanceMethods#read_attribute