govuk_content_models 14.1.1 → 15.0.0

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.
@@ -212,7 +212,8 @@ class Artefact
212
212
  def as_json(options={})
213
213
  super.tap { |hash|
214
214
  if hash["tag_ids"]
215
- hash["tags"] = Tag.by_tag_ids!(hash["tag_ids"]).map(&:as_json)
215
+ Tag.validate_tag_ids(hash["tag_ids"])
216
+ hash["tags"] = Tag.by_tag_ids(hash["tag_ids"]).map(&:as_json)
216
217
  else
217
218
  hash["tag_ids"] = []
218
219
  hash["tags"] = []
@@ -354,7 +355,6 @@ class Artefact
354
355
  end
355
356
 
356
357
  def snapshot
357
- reconcile_tag_ids
358
358
  attributes.except "_id", "created_at", "updated_at", "actions"
359
359
  end
360
360
 
data/app/models/tag.rb CHANGED
@@ -3,7 +3,7 @@ require 'tag_id_validator'
3
3
 
4
4
  class Tag
5
5
  include Mongoid::Document
6
-
6
+
7
7
  field :tag_id, type: String
8
8
  field :title, type: String
9
9
  field :tag_type, type: String #TODO: list of accepted types?
@@ -66,17 +66,18 @@ class Tag
66
66
  end
67
67
 
68
68
  def self.by_tag_id(tag_id, tag_type = nil)
69
- scope = tag_type ? Tag.where(tag_type: tag_type) : Tag
70
- scope.where(tag_id: tag_id).first
69
+ by_tag_ids([tag_id], tag_type).first
71
70
  end
72
71
 
73
- # Retrieve a list of tags by tag ID. Any missing tags become `nil`.
74
72
  def self.by_tag_ids(tag_id_list, tag_type = nil)
75
- scope = tag_type ? Tag.where(tag_type: tag_type) : Tag
73
+ tag_scope = tag_type ? Tag.where(tag_type: tag_type) : Tag
74
+ tag_scope = tag_scope.any_in(tag_id: tag_id_list)
76
75
 
77
- # Load up all the tags in a single query
78
- tags = scope.any_in(tag_id: tag_id_list).to_a
79
- tag_id_list.map { |tag_id| tags.find { |t| t.tag_id == tag_id } }
76
+ # Sort by id list because MongoID 2.x doesn't preserve order
77
+ tags_by_id = tag_scope.each_with_object({}) do |tag, hash|
78
+ hash[tag.tag_id] = tag
79
+ end
80
+ tag_id_list.map { |tag_id| tags_by_id[tag_id] }.compact
80
81
  end
81
82
 
82
83
  def self.by_tag_types_and_ids(tag_types_and_ids)
@@ -85,16 +86,9 @@ class Tag
85
86
  end
86
87
 
87
88
  # Retrieve a list of tags by tag ID. Any missing tags raise an exception.
88
- def self.by_tag_ids!(tag_id_list, tag_type = nil)
89
- tags = by_tag_ids(tag_id_list, tag_type)
90
- if tags.any?(&:nil?)
91
- # Find the tag IDs for which the resulting tag is nil
92
- missing_ids = tag_id_list.zip(tags).select { |tag_id, tag|
93
- tag.nil?
94
- }.map(&:first)
95
- raise MissingTags, missing_ids
96
- else
97
- tags
98
- end
89
+ def self.validate_tag_ids(tag_id_list, tag_type = nil)
90
+ found_tags = by_tag_ids(tag_id_list, tag_type)
91
+ missing_tag_ids = tag_id_list - found_tags.map(&:tag_id)
92
+ raise MissingTags.new(missing_tag_ids) if missing_tag_ids.any?
99
93
  end
100
94
  end
@@ -1,39 +1,47 @@
1
1
  module Taggable
2
2
  module ClassMethods
3
- def stores_tags_for(*keys)
4
- tag_types = keys.to_a.flatten.compact.map(&:to_s)
3
+ def stores_tags_for(*tag_types)
4
+ tag_types = tag_types.map {|tag_type|
5
+ raise ArgumentError.new("Please provide tag types as symbols") unless tag_type.is_a?(Symbol)
6
+ tag_type.to_s
7
+ }
8
+
5
9
  class_attribute :tag_types
6
10
  self.tag_types = tag_types
7
11
 
8
- tag_types.each do |k|
9
- define_method "#{k}=" do |values|
10
- set_tags_of_type(k, values)
12
+ tag_types.each do |tag_type|
13
+ define_method("#{tag_type}=") do |tag_ids|
14
+ set_tags_of_type(tag_type, tag_ids)
11
15
  end
12
- alias_method :"#{k.singularize}_ids=", :"#{k}="
16
+ alias_method :"#{tag_type.singularize}_ids=", :"#{tag_type}="
13
17
 
14
- define_method k do
15
- tags_of_type(k.singularize)
18
+ define_method(tag_type) do |*args|
19
+ include_draft = args.first
20
+ tags_of_type(tag_type.singularize, include_draft)
16
21
  end
17
- define_method "#{k.singularize}_ids" do
18
- tags_of_type(k.singularize).collect(&:tag_id)
22
+
23
+ define_method("#{tag_type.singularize}_ids") do |*args|
24
+ send(tag_type, *args).map(&:tag_id)
19
25
  end
20
26
  end
21
27
  end
22
28
 
23
- def has_primary_tag_for(*keys)
24
- tag_types = keys.to_a.flatten.compact.map(&:to_s)
29
+ def has_primary_tag_for(*tag_types)
30
+ tag_types = tag_types.map {|tag_type|
31
+ raise ArgumentError.new("Please provide tag types as symbols") unless tag_type.is_a?(Symbol)
32
+ tag_type.to_s
33
+ }
34
+
25
35
  class_attribute :primary_tag_types
26
36
  self.primary_tag_types = tag_types
27
37
 
28
- tag_types.each do |key|
29
- method_name = "primary_#{key}"
30
-
31
- define_method "#{method_name}=" do |value|
32
- set_primary_tag_of_type(key.to_s, value)
38
+ tag_types.each do |tag_type|
39
+ define_method("primary_#{tag_type}=") do |tag_id|
40
+ set_primary_tag_of_type(tag_type, tag_id)
33
41
  end
34
42
 
35
- define_method method_name do
36
- tags_of_type(key.to_s).first
43
+ define_method("primary_#{tag_type}") do
44
+ tags_of_type(tag_type).first
37
45
  end
38
46
  end
39
47
  end
@@ -49,74 +57,50 @@ module Taggable
49
57
  klass.__send__ :private, :tag_ids=
50
58
  end
51
59
 
52
- def set_tags_of_type(collection_name, values)
60
+ def set_tags_of_type(collection_name, tag_ids)
53
61
  tag_type = collection_name.singularize
62
+ tag_ids = Array(tag_ids)
54
63
 
55
- # Ensure all tags loaded from database. This feels inelegant
56
- # but ensures integrity. It could go away if we moved to a more
57
- # consistent repository approach to retrieving and constructing
58
- # objects, or if we used a custom serialization type for tags
59
- # as documented on http://mongoid.org/en/mongoid/docs/documents.html
60
- tags
64
+ Tag.validate_tag_ids(tag_ids, tag_type)
61
65
 
62
- # Make sure that 'values' is an array. This stops the method blowing up
63
- # if nil is provided.
64
- values_as_array = Array(values)
66
+ current_tags = attributes['tags'].reject {|t| t[:tag_type] == tag_type }
65
67
 
66
- # This will raise a Tag::MissingTags exception unless all the tags exist
67
- new_tags = Tag.by_tag_ids!(values_as_array, tag_type)
68
-
69
- @tags.reject! { |t| t.tag_type == tag_type }
70
- @tags += new_tags
68
+ self.tags = current_tags + tag_ids.map {|tag_id|
69
+ {tag_id: tag_id, tag_type: tag_type}
70
+ }
71
71
  end
72
72
 
73
73
  # The primary tag is simply the first one of its
74
74
  # type. If that tag is already applied this method
75
75
  # moves it to the start of the list. If it's not then
76
76
  # we add it at the start of the list.
77
- def set_primary_tag_of_type(tag_type, value)
78
- tags
77
+ def set_primary_tag_of_type(tag_type, tag_id)
78
+ Tag.validate_tag_ids([tag_id], tag_type)
79
79
 
80
- tag = Tag.by_tag_id(value, tag_type)
81
- raise "Missing tag" unless tag
82
- raise "Wrong tag type" unless tag.tag_type == tag_type
80
+ tag_tuple = {tag_id: tag_id, tag_type: tag_type}
83
81
 
84
- @tags -= [tag]
85
- @tags.unshift(tag)
86
- end
82
+ current_tags = attributes['tags'].dup
83
+ current_tags.delete(tag_tuple)
87
84
 
88
- def tags_of_type(tag_type)
89
- tags.select { |t| t.tag_type == tag_type }
85
+ self.tags = current_tags.unshift(tag_tuple)
90
86
  end
91
87
 
92
- def reconcile_tag_ids
93
- # Ensure tags are loaded so we don't accidentally
94
- # remove all tagging in situations where tags haven't
95
- # been accessed during the lifetime of the object
96
- tags
97
-
98
- self.tag_ids = @tags.collect(&:tag_id)
99
- self.tags = @tags.map {|tag|
100
- { tag_id: tag.tag_id, tag_type: tag.tag_type }
101
- }
102
- end
103
-
104
- def tags
105
- @tags ||= Tag.by_tag_ids(tag_ids).compact.to_a
88
+ def tags_of_type(tag_type, include_draft = false)
89
+ tags(include_draft).select { |t| t.tag_type == tag_type }
106
90
  end
107
91
 
108
- def reload
109
- @tags = nil
110
- super
92
+ def tags=(new_tag_tuples)
93
+ self.tag_ids = new_tag_tuples.map {|tuple| tuple[:tag_id] }
94
+ super(new_tag_tuples)
111
95
  end
112
96
 
113
- def save(options={})
114
- reconcile_tag_ids
115
- super(options)
116
- end
97
+ def tags(include_draft = false)
98
+ all_tags = Tag.by_tag_ids(tag_ids)
117
99
 
118
- def save!(options={})
119
- reconcile_tag_ids
120
- super(options)
100
+ if include_draft
101
+ all_tags
102
+ else
103
+ all_tags.reject {|tag| tag.state == 'draft' }
104
+ end
121
105
  end
122
106
  end
@@ -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 = "14.1.1"
3
+ VERSION = "15.0.0"
4
4
  end
@@ -24,7 +24,6 @@ class ArtefactTagTest < ActiveSupport::TestCase
24
24
  a = FactoryGirl.create(:artefact)
25
25
  a.sections = ['crime', 'crime/the-police']
26
26
  a.primary_section = 'crime'
27
- a.reconcile_tag_ids
28
27
 
29
28
  assert_equal 'Crime', a.section
30
29
  end
@@ -36,7 +35,6 @@ class ArtefactTagTest < ActiveSupport::TestCase
36
35
 
37
36
  a = FactoryGirl.create(:artefact)
38
37
  a.primary_section = child.tag_id
39
- a.reconcile_tag_ids
40
38
 
41
39
  assert_equal "#{parent.title}:#{child.title}", a.section
42
40
  end
@@ -47,7 +45,6 @@ class ArtefactTagTest < ActiveSupport::TestCase
47
45
  a.sections = ['crime', 'crime/the-police']
48
46
  a.legacy_sources = ['businesslink']
49
47
  a.keywords = ['bacon']
50
- a.reconcile_tag_ids
51
48
 
52
49
  expected_tags = [
53
50
  { tag_id: "crime", tag_type: "section" },
@@ -55,13 +55,11 @@ class TagTest < ActiveSupport::TestCase
55
55
  )
56
56
  end
57
57
 
58
- test "should return nil for missing tags" do
59
- tag_ids = %w(crime business batman housing)
58
+ test "should not return missing tags" do
59
+ tag_ids = %w(crime business not_a_real_tag housing)
60
60
  tags = Tag.by_tag_ids(tag_ids)
61
- assert_nil tags[2]
62
- [0, 1, 3].each do |i|
63
- assert_equal tag_ids[i], tags[i].tag_id
64
- end
61
+
62
+ assert_equal %w(crime business housing), tags.map(&:tag_id)
65
63
  end
66
64
 
67
65
  test "should return nil for tags of the wrong type" do
@@ -71,22 +69,15 @@ class TagTest < ActiveSupport::TestCase
71
69
  [0, 1].each do |i| assert_equal tag_ids[i], tags[i].tag_id end
72
70
  end
73
71
 
74
- test "should return multiple tags from the bang method" do
75
- assert_equal(
76
- %w(Crime Business),
77
- Tag.by_tag_ids!(%w(crime business)).map(&:title)
78
- )
79
- end
80
-
81
72
  test "should raise an exception if any tags are missing" do
82
73
  assert_raises Tag::MissingTags do
83
- Tag.by_tag_ids!(%w(crime business batman))
74
+ Tag.validate_tag_ids(%w(crime business batman))
84
75
  end
85
76
  end
86
77
 
87
78
  test "should raise an exception with the wrong tag type" do
88
79
  assert_raises Tag::MissingTags do
89
- Tag.by_tag_ids!(%w(crime business pie chips), "section")
80
+ Tag.validate_tag_ids(%w(crime business pie chips), "section")
90
81
  end
91
82
  end
92
83
 
@@ -6,9 +6,10 @@ class TaggableTest < ActiveSupport::TestCase
6
6
  TEST_KEYWORDS = [['cheese', 'Cheese'], ['bacon', 'Bacon']]
7
7
 
8
8
  setup do
9
- parent_section = FactoryGirl.create(:tag, :tag_id => 'crime', :tag_type => 'section', :title => 'Crime')
10
- FactoryGirl.create(:tag, :tag_id => 'crime/the-police', :tag_type => 'section', :title => 'The Police', :parent_id => parent_section.id)
11
- FactoryGirl.create(:tag, :tag_id => 'crime/batman', :tag_type => 'section', :title => 'Batman', :parent_id => parent_section.id)
9
+ @parent_section = FactoryGirl.create(:tag, :tag_id => 'crime', :tag_type => 'section', :title => 'Crime')
10
+ FactoryGirl.create(:tag, :tag_id => 'crime/the-police', :tag_type => 'section', :title => 'The Police', :parent_id => @parent_section.id)
11
+ FactoryGirl.create(:tag, :tag_id => 'crime/batman', :tag_type => 'section', :title => 'Batman', :parent_id => @parent_section.id)
12
+ @draft_section = FactoryGirl.create(:tag, parent_id: @parent_section.id, state: 'draft')
12
13
 
13
14
  TEST_KEYWORDS.each do |tag_id, title|
14
15
  FactoryGirl.create(:tag, :tag_id => tag_id, :tag_type => 'keyword', :title => title)
@@ -19,7 +20,6 @@ class TaggableTest < ActiveSupport::TestCase
19
20
 
20
21
  test "can set sections" do
21
22
  @item.sections = ['crime', 'crime/the-police']
22
- @item.reconcile_tag_ids
23
23
 
24
24
  assert_equal ['crime', 'crime/the-police'], @item.tag_ids, 'Mismatched tags'
25
25
  assert_equal ['crime', 'crime/the-police'], @item.sections.collect(&:tag_id), 'Mismatched sections'
@@ -30,7 +30,6 @@ class TaggableTest < ActiveSupport::TestCase
30
30
  test "can set sections and primary section separately" do
31
31
  @item.sections = ['crime', 'crime/the-police']
32
32
  @item.primary_section = 'crime'
33
- @item.reconcile_tag_ids
34
33
 
35
34
  assert_equal ['crime', 'crime/the-police'], @item.tag_ids, 'Mismatched tags'
36
35
  assert_equal ['crime', 'crime/the-police'], @item.sections.collect(&:tag_id), 'Mismatched sections'
@@ -41,7 +40,6 @@ class TaggableTest < ActiveSupport::TestCase
41
40
  test "can set subsection as primary section" do
42
41
  @item.sections = ['crime/the-police', 'crime']
43
42
  @item.primary_section = 'crime/the-police'
44
- @item.reconcile_tag_ids
45
43
  assert_equal 'The Police', @item.primary_section.title
46
44
  end
47
45
 
@@ -69,7 +67,6 @@ class TaggableTest < ActiveSupport::TestCase
69
67
  @item.keywords = ['cheese', 'bacon']
70
68
  @item.sections = ['crime']
71
69
  @item.primary_section = 'crime'
72
- @item.reconcile_tag_ids
73
70
 
74
71
  assert_equal ['bacon', 'cheese', 'crime'], @item.tag_ids.sort
75
72
  assert_equal 'Crime', @item.primary_section.title
@@ -78,14 +75,13 @@ class TaggableTest < ActiveSupport::TestCase
78
75
  test "setting primary section adds section to tags" do
79
76
  @item.sections = ['crime', 'crime/the-police']
80
77
  @item.primary_section = 'crime/batman'
81
- @item.reconcile_tag_ids
78
+
82
79
  assert_includes @item.sections.collect(&:tag_id), 'crime/batman'
83
80
  end
84
81
 
85
82
  test "setting primary section to existing section works" do
86
83
  @item.sections = ['crime', 'crime/the-police']
87
84
  @item.primary_section = 'crime/the-police'
88
- @item.reconcile_tag_ids
89
85
  # Note: not testing the order of the sections in this test, just testing
90
86
  # that the section is still present and not duplicated
91
87
  assert_equal ['crime', 'crime/the-police'], @item.sections.collect(&:tag_id).sort
@@ -122,4 +118,15 @@ class TaggableTest < ActiveSupport::TestCase
122
118
 
123
119
  assert_equal [], @item.section_ids
124
120
  end
121
+
122
+ test "returns draft tags only if requested" do
123
+ @item.section_ids = [@parent_section.tag_id, @draft_section.tag_id]
124
+ @item.save!
125
+
126
+ assert_equal [@parent_section.tag_id], @item.section_ids
127
+ assert_equal [@parent_section.tag_id, @draft_section.tag_id], @item.section_ids(draft: true)
128
+
129
+ assert_equal [@parent_section], @item.sections
130
+ assert_equal [@parent_section, @draft_section], @item.sections(draft: true)
131
+ end
125
132
  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: 14.1.1
4
+ version: 15.0.0
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-07-09 00:00:00.000000000 Z
12
+ date: 2014-07-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bson_ext
@@ -466,7 +466,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
466
466
  version: '0'
467
467
  segments:
468
468
  - 0
469
- hash: 2394194236032864468
469
+ hash: -116450993641469610
470
470
  required_rubygems_version: !ruby/object:Gem::Requirement
471
471
  none: false
472
472
  requirements:
@@ -475,7 +475,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
475
475
  version: '0'
476
476
  segments:
477
477
  - 0
478
- hash: 2394194236032864468
478
+ hash: -116450993641469610
479
479
  requirements: []
480
480
  rubyforge_project:
481
481
  rubygems_version: 1.8.23