govuk_content_models 7.1.0 → 7.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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