govuk_content_models 14.1.1 → 15.0.0

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