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.
- data/app/models/artefact.rb +2 -2
- data/app/models/tag.rb +13 -19
- data/app/traits/taggable.rb +52 -68
- data/lib/govuk_content_models/version.rb +1 -1
- data/test/models/artefact_tag_test.rb +0 -3
- data/test/models/tag_test.rb +6 -15
- data/test/traits/taggable_test.rb +16 -9
- metadata +4 -4
data/app/models/artefact.rb
CHANGED
@@ -212,7 +212,8 @@ class Artefact
|
|
212
212
|
def as_json(options={})
|
213
213
|
super.tap { |hash|
|
214
214
|
if hash["tag_ids"]
|
215
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
78
|
-
|
79
|
-
|
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.
|
89
|
-
|
90
|
-
|
91
|
-
|
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
|
data/app/traits/taggable.rb
CHANGED
@@ -1,39 +1,47 @@
|
|
1
1
|
module Taggable
|
2
2
|
module ClassMethods
|
3
|
-
def stores_tags_for(*
|
4
|
-
tag_types =
|
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 |
|
9
|
-
define_method
|
10
|
-
set_tags_of_type(
|
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 :"#{
|
16
|
+
alias_method :"#{tag_type.singularize}_ids=", :"#{tag_type}="
|
13
17
|
|
14
|
-
define_method
|
15
|
-
|
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
|
-
|
18
|
-
|
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(*
|
24
|
-
tag_types =
|
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 |
|
29
|
-
|
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
|
36
|
-
tags_of_type(
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
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,
|
78
|
-
|
77
|
+
def set_primary_tag_of_type(tag_type, tag_id)
|
78
|
+
Tag.validate_tag_ids([tag_id], tag_type)
|
79
79
|
|
80
|
-
|
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
|
-
|
85
|
-
|
86
|
-
end
|
82
|
+
current_tags = attributes['tags'].dup
|
83
|
+
current_tags.delete(tag_tuple)
|
87
84
|
|
88
|
-
|
89
|
-
tags.select { |t| t.tag_type == tag_type }
|
85
|
+
self.tags = current_tags.unshift(tag_tuple)
|
90
86
|
end
|
91
87
|
|
92
|
-
def
|
93
|
-
|
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
|
109
|
-
|
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
|
114
|
-
|
115
|
-
super(options)
|
116
|
-
end
|
97
|
+
def tags(include_draft = false)
|
98
|
+
all_tags = Tag.by_tag_ids(tag_ids)
|
117
99
|
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
@@ -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" },
|
data/test/models/tag_test.rb
CHANGED
@@ -55,13 +55,11 @@ class TagTest < ActiveSupport::TestCase
|
|
55
55
|
)
|
56
56
|
end
|
57
57
|
|
58
|
-
test "should return
|
59
|
-
tag_ids = %w(crime business
|
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
|
-
|
62
|
-
|
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.
|
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.
|
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
|
-
|
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:
|
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-
|
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:
|
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:
|
478
|
+
hash: -116450993641469610
|
479
479
|
requirements: []
|
480
480
|
rubyforge_project:
|
481
481
|
rubygems_version: 1.8.23
|