govuk_content_models 7.1.0 → 7.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,31 @@
1
- class SpecialistDocumentEdition < Edition
1
+ require "workflow"
2
+ require "fact_check_address"
3
+
4
+ class SpecialistDocumentEdition
5
+ include Mongoid::Document
6
+ include Mongoid::Timestamps
7
+ include Workflow
8
+
9
+ field :panopticon_id, type: String
10
+ field :version_number, type: Integer, default: 1
11
+ field :sibling_in_progress, type: Integer, default: nil
12
+ field :business_proposition, type: Boolean, default: false
13
+
14
+ field :title, type: String
15
+ field :created_at, type: DateTime, default: lambda { Time.zone.now }
16
+ field :overview, type: String
17
+ field :alternative_title, type: String
18
+ field :slug, type: String
19
+ field :section, type: String
20
+ field :department, type: String
21
+ field :rejected_count, type: Integer, default: 0
22
+ field :tags, type: String
23
+
24
+ field :assignee, type: String
25
+ field :creator, type: String
26
+ field :publisher, type: String
27
+ field :archiver, type: String
28
+
2
29
  field :summary, type: String
3
30
  field :body, type: String
4
31
  field :opened_date, type: Date
@@ -13,4 +40,264 @@ class SpecialistDocumentEdition < Edition
13
40
  def whole_body
14
41
  self.body
15
42
  end
43
+
44
+ belongs_to :assigned_to, class_name: "User"
45
+
46
+ scope :lined_up, where(state: "lined_up")
47
+ scope :draft, where(state: "draft")
48
+ scope :amends_needed, where(state: "amends_needed")
49
+ scope :in_review, where(state: "in_review")
50
+ scope :fact_check, where(state: "fact_check")
51
+ scope :fact_check_received, where(state: "fact_check_received")
52
+ scope :ready, where(state: "ready")
53
+ scope :published, where(state: "published")
54
+ scope :archived, where(state: "archived")
55
+ scope :in_progress, where(:state.nin => ["archived", "published"])
56
+ scope :assigned_to, lambda { |user|
57
+ if user
58
+ where(assigned_to_id: user.id)
59
+ else
60
+ where(:assigned_to_id.exists => false)
61
+ end
62
+ }
63
+
64
+ validates :title, presence: true
65
+ validates :version_number, presence: true
66
+ validates :panopticon_id, presence: true
67
+ validates_with SafeHtml
68
+
69
+ before_save :check_for_archived_artefact
70
+ before_destroy :destroy_artefact
71
+
72
+ index "assigned_to_id"
73
+ index "panopticon_id"
74
+ index "state"
75
+
76
+ class << self; attr_accessor :fields_to_clone end
77
+ @fields_to_clone = []
78
+
79
+ alias_method :admin_list_title, :title
80
+
81
+ def series
82
+ Edition.where(panopticon_id: panopticon_id)
83
+ end
84
+
85
+ def history
86
+ series.order([:version_number, :desc])
87
+ end
88
+
89
+ def siblings
90
+ series.excludes(id: id)
91
+ end
92
+
93
+ def previous_siblings
94
+ siblings.where(:version_number.lt => version_number)
95
+ end
96
+
97
+ def subsequent_siblings
98
+ siblings.where(:version_number.gt => version_number)
99
+ end
100
+
101
+ def latest_edition?
102
+ subsequent_siblings.empty?
103
+ end
104
+
105
+ def published_edition
106
+ series.where(state: "published").order(version_number: "desc").first
107
+ end
108
+
109
+ def previous_published_edition
110
+ series.where(state: "published").order(version_number: "desc").second
111
+ end
112
+
113
+ def in_progress_sibling
114
+ subsequent_siblings.in_progress.order(version_number: "desc").first
115
+ end
116
+
117
+ def can_create_new_edition?
118
+ subsequent_siblings.in_progress.empty?
119
+ end
120
+
121
+ def meta_data
122
+ PublicationMetadata.new self
123
+ end
124
+
125
+ def fact_check_email_address
126
+ FactCheckAddress.new.for_edition(self)
127
+ end
128
+
129
+ def get_next_version_number
130
+ latest_version = series.order(version_number: "desc").first.version_number
131
+ latest_version + 1
132
+ end
133
+
134
+ def indexable_content
135
+ respond_to?(:parts) ? indexable_content_with_parts : indexable_content_without_parts
136
+ end
137
+
138
+ def indexable_content_without_parts
139
+ if respond_to?(:body)
140
+ "#{alternative_title} #{Govspeak::Document.new(body).to_text}".strip
141
+ else
142
+ alternative_title
143
+ end
144
+ end
145
+
146
+ def indexable_content_with_parts
147
+ content = indexable_content_without_parts
148
+ return content unless published_edition
149
+ parts.inject([content]) { |acc, part|
150
+ acc.concat([part.title, Govspeak::Document.new(part.body).to_text])
151
+ }.compact.join(" ").strip
152
+ end
153
+
154
+ # If the new clone is of the same type, we can copy all its fields over; if
155
+ # we are changing the type of the edition, any fields other than the base
156
+ # fields will likely be meaningless.
157
+ def fields_to_copy(edition_class)
158
+ edition_class == self.class ? self.class.fields_to_clone : []
159
+ end
160
+
161
+ def build_clone(edition_class=nil)
162
+ unless state == "published"
163
+ raise "Cloning of non published edition not allowed"
164
+ end
165
+ unless can_create_new_edition?
166
+ raise "Cloning of a published edition when an in-progress edition exists
167
+ is not allowed"
168
+ end
169
+
170
+ edition_class = self.class unless edition_class
171
+ new_edition = edition_class.new(title: self.title,
172
+ version_number: get_next_version_number)
173
+
174
+ real_fields_to_merge = fields_to_copy(edition_class) +
175
+ [:panopticon_id, :overview, :alternative_title,
176
+ :slug, :section, :department]
177
+
178
+ real_fields_to_merge.each do |attr|
179
+ new_edition[attr] = read_attribute(attr)
180
+ end
181
+
182
+ if edition_class == AnswerEdition and %w(GuideEdition ProgrammeEdition TransactionEdition).include?(self.class.name)
183
+ new_edition.body = whole_body
184
+ end
185
+
186
+ if edition_class == TransactionEdition and %w(AnswerEdition GuideEdition ProgrammeEdition).include?(self.class.name)
187
+ new_edition.more_information = whole_body
188
+ end
189
+
190
+ if edition_class == GuideEdition and self.is_a?(AnswerEdition)
191
+ new_edition.parts.build(title: "Part One", body: whole_body,
192
+ slug: "part-one")
193
+ end
194
+
195
+ new_edition
196
+ end
197
+
198
+ def self.find_or_create_from_panopticon_data(panopticon_id,
199
+ importing_user, api_credentials)
200
+ existing_publication = Edition.where(panopticon_id: panopticon_id)
201
+ .order_by([:version_number, :desc]).first
202
+ return existing_publication if existing_publication
203
+
204
+ raise "Artefact not found" unless metadata = Artefact.find(panopticon_id)
205
+
206
+ importing_user.create_edition(metadata.kind.to_sym,
207
+ panopticon_id: metadata.id,
208
+ slug: metadata.slug,
209
+ title: metadata.name,
210
+ section: metadata.section,
211
+ department: metadata.department,
212
+ business_proposition: metadata.business_proposition)
213
+ end
214
+
215
+ def self.find_and_identify(slug, edition)
216
+ scope = where(slug: slug)
217
+
218
+ if edition.present? and edition == "latest"
219
+ scope.order_by(:version_number).last
220
+ elsif edition.present?
221
+ scope.where(version_number: edition).first
222
+ else
223
+ scope.where(state: "published").order_by(:created_at).last
224
+ end
225
+ end
226
+
227
+ def panopticon_uri
228
+ Plek.current.find("panopticon") + "/artefacts/" + (panopticon_id || slug).to_s
229
+ end
230
+
231
+ def format
232
+ self.class.to_s.gsub("Edition", "")
233
+ end
234
+
235
+ def format_name
236
+ format
237
+ end
238
+
239
+ def has_video?
240
+ false
241
+ end
242
+
243
+ def safe_to_preview?
244
+ true
245
+ end
246
+
247
+ def has_sibling_in_progress?
248
+ ! sibling_in_progress.nil?
249
+ end
250
+
251
+ # Stop broadcasting a delete message unless there are no siblings.
252
+ def broadcast_action(callback_action)
253
+ unless callback_action == "destroyed" and self.siblings.any?
254
+ super(callback_action)
255
+ end
256
+ end
257
+
258
+ def was_published
259
+ previous_siblings.all.each(&:archive)
260
+ notify_siblings_of_published_edition
261
+ end
262
+
263
+ def update_from_artefact(artefact)
264
+ self.title = artefact.name unless published?
265
+ self.slug = artefact.slug
266
+ self.section = artefact.section
267
+ self.department = artefact.department
268
+ self.business_proposition = artefact.business_proposition
269
+ self.save!
270
+ end
271
+
272
+ def check_for_archived_artefact
273
+ if panopticon_id
274
+ a = Artefact.find(panopticon_id)
275
+ if a.state == "archived" and changed_attributes.any?
276
+ # If we're only changing the state to archived, that's ok
277
+ # Any other changes are not allowed
278
+ allowed_keys = ["state", "updated_at"]
279
+ unless ((changed_attributes.keys - allowed_keys).empty?) and state == "archived"
280
+ raise "Editing of an edition with an Archived artefact is not allowed"
281
+ end
282
+ end
283
+ end
284
+ end
285
+
286
+ def artefact
287
+ Artefact.find(panopticon_id)
288
+ end
289
+
290
+ # When we delete an edition is the only one in its series
291
+ # we delete the associated artefact to remove all trace of the
292
+ # item from the system.
293
+ #
294
+ # We don't do this by notifying panopticon as this will only ever
295
+ # happen for artefacts representing editions that haven't been
296
+ # published (and therefore aren't registered in the rest of the)
297
+ # system.
298
+ def destroy_artefact
299
+ if can_destroy? && siblings.empty?
300
+ Artefact.find(self.panopticon_id).destroy
301
+ end
302
+ end
16
303
  end
data/app/models/tag.rb CHANGED
@@ -72,6 +72,11 @@ class Tag
72
72
  tag_id_list.map { |tag_id| tags.find { |t| t.tag_id == tag_id } }
73
73
  end
74
74
 
75
+ def self.by_tag_types_and_ids(tag_types_and_ids)
76
+ list = tag_types_and_ids.map {|hash| hash.slice(:tag_id, :tag_type) }
77
+ any_of(list)
78
+ end
79
+
75
80
  # Retrieve a list of tags by tag ID. Any missing tags raise an exception.
76
81
  def self.by_tag_ids!(tag_id_list, tag_type = nil)
77
82
  tags = by_tag_ids(tag_id_list, tag_type)
@@ -42,6 +42,8 @@ module Taggable
42
42
  def self.included(klass)
43
43
  klass.extend ClassMethods
44
44
  klass.field :tag_ids, type: Array, default: []
45
+ klass.field :tags, type: Array, default: []
46
+
45
47
  klass.index :tag_ids
46
48
  klass.attr_protected :tags, :tag_ids
47
49
  klass.__send__ :private, :tag_ids=
@@ -90,6 +92,9 @@ module Taggable
90
92
  tags
91
93
 
92
94
  self.tag_ids = @tags.collect(&:tag_id)
95
+ self.tags = @tags.map {|tag|
96
+ { tag_id: tag.tag_id, tag_type: tag.tag_type }
97
+ }
93
98
  end
94
99
 
95
100
  def tags
@@ -1,4 +1,4 @@
1
1
  module GovukContentModels
2
2
  # Changing this causes Jenkins to tag and release the gem into the wild
3
- VERSION = "7.1.0"
3
+ VERSION = "7.1.1"
4
4
  end
@@ -21,6 +21,7 @@ class ArtefactActionTest < ActiveSupport::TestCase
21
21
  "business_proposition" => false,
22
22
  "active" => false,
23
23
  "tag_ids" => [],
24
+ "tags" => [],
24
25
  "state" => "draft",
25
26
  "related_artefact_ids" => [],
26
27
  "paths" => [],
@@ -6,6 +6,9 @@ class ArtefactTagTest < ActiveSupport::TestCase
6
6
  ['crime', 'Crime'], ['crime/the-police', 'The Police'], ['crime/batman', 'Batman']
7
7
  ]
8
8
  TEST_KEYWORDS = [['cheese', 'Cheese'], ['bacon', 'Bacon']]
9
+ TEST_LEGACY_SOURCES = [
10
+ ['businesslink', 'Business Link'], ['directgov', 'Directgov'], ['dvla', 'DVLA']
11
+ ]
9
12
 
10
13
  setup do
11
14
  TEST_SECTIONS.each do |tag_id, title|
@@ -14,6 +17,9 @@ class ArtefactTagTest < ActiveSupport::TestCase
14
17
  TEST_KEYWORDS.each do |tag_id, title|
15
18
  FactoryGirl.create(:tag, :tag_id => tag_id, :tag_type => 'keyword', :title => title)
16
19
  end
20
+ TEST_LEGACY_SOURCES.each do |tag_id, title|
21
+ FactoryGirl.create(:tag, :tag_id => tag_id, :tag_type => 'legacy_source', :title => title)
22
+ end
17
23
  end
18
24
 
19
25
  test "return primary section title when asked for its section" do
@@ -37,16 +43,30 @@ class ArtefactTagTest < ActiveSupport::TestCase
37
43
  assert_equal "#{parent.title}:#{child.title}", a.section
38
44
  end
39
45
 
40
- test "has legacy_sources tag collection" do
41
- ls1 = FactoryGirl.create(:tag, :tag_id => 'businesslink', :tag_type => 'legacy_source', :title => 'Business Link')
42
- ls2 = FactoryGirl.create(:tag, :tag_id => 'directgov', :tag_type => 'legacy_source', :title => 'Directgov')
43
- ls3 = FactoryGirl.create(:tag, :tag_id => 'dvla', :tag_type => 'legacy_source', :title => 'DVLA')
46
+ test "stores the tag type and tag id for each tag" do
47
+ a = FactoryGirl.create(:artefact)
48
+
49
+ a.sections = ['crime', 'crime/the-police']
50
+ a.legacy_sources = ['businesslink']
51
+ a.keywords = ['bacon']
52
+ a.reconcile_tag_ids
44
53
 
54
+ expected_tags = [
55
+ { tag_id: "crime", tag_type: "section" },
56
+ { tag_id: "crime/the-police", tag_type: "section" },
57
+ { tag_id: "businesslink", tag_type: "legacy_source" },
58
+ { tag_id: "bacon", tag_type: "keyword" },
59
+ ]
60
+ assert_equal ["crime", "crime/the-police", "businesslink", "bacon"], a.tag_ids
61
+ assert_equal expected_tags, a.attributes["tags"]
62
+ end
63
+
64
+ test "has legacy_sources tag collection" do
45
65
  a = FactoryGirl.build(:artefact)
46
66
  a.legacy_sources = ['businesslink', 'dvla']
47
67
  a.save
48
68
 
49
69
  a = Artefact.first
50
- assert_equal [ls1, ls3], a.legacy_sources
70
+ assert_equal ["businesslink", "dvla"], a.legacy_source_ids
51
71
  end
52
72
  end
@@ -89,4 +89,16 @@ class TagTest < ActiveSupport::TestCase
89
89
  Tag.by_tag_ids!(%w(crime business pie chips), "section")
90
90
  end
91
91
  end
92
+
93
+ test "should return tags given a list of tag ids and tag types" do
94
+ tag_types_and_ids = [
95
+ { tag_type: "section", tag_id: "crime" },
96
+ { tag_type: "section", tag_id: "business" },
97
+ { tag_type: "keyword", tag_id: "pie" },
98
+ { tag_type: "keyword", tag_id: "chips" }
99
+ ]
100
+ tags = Tag.by_tag_types_and_ids(tag_types_and_ids)
101
+
102
+ assert_equal %w{Business Chips Crime Pie}, tags.map(&:title).sort
103
+ end
92
104
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govuk_content_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.0
4
+ version: 7.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-02-17 00:00:00.000000000 Z
12
+ date: 2014-02-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bson_ext
@@ -456,7 +456,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
456
456
  version: '0'
457
457
  segments:
458
458
  - 0
459
- hash: 1342987933027242847
459
+ hash: -3334293540953649833
460
460
  required_rubygems_version: !ruby/object:Gem::Requirement
461
461
  none: false
462
462
  requirements:
@@ -465,7 +465,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
465
465
  version: '0'
466
466
  segments:
467
467
  - 0
468
- hash: 1342987933027242847
468
+ hash: -3334293540953649833
469
469
  requirements: []
470
470
  rubyforge_project:
471
471
  rubygems_version: 1.8.23