govuk_content_models 6.0.2

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.
Files changed (104) hide show
  1. data/.gitignore +17 -0
  2. data/.ruby-version +1 -0
  3. data/.travis.yml +14 -0
  4. data/CONTRIBUTING.md +22 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +5 -0
  8. data/Rakefile +46 -0
  9. data/app/models/action.rb +60 -0
  10. data/app/models/answer_edition.rb +13 -0
  11. data/app/models/artefact.rb +341 -0
  12. data/app/models/artefact_action.rb +27 -0
  13. data/app/models/artefact_external_link.rb +15 -0
  14. data/app/models/business_support/business_size.rb +14 -0
  15. data/app/models/business_support/business_type.rb +14 -0
  16. data/app/models/business_support/location.rb +14 -0
  17. data/app/models/business_support/purpose.rb +14 -0
  18. data/app/models/business_support/sector.rb +14 -0
  19. data/app/models/business_support/stage.rb +14 -0
  20. data/app/models/business_support/support_type.rb +14 -0
  21. data/app/models/business_support_edition.rb +69 -0
  22. data/app/models/campaign_edition.rb +72 -0
  23. data/app/models/completed_transaction_edition.rb +14 -0
  24. data/app/models/curated_list.rb +32 -0
  25. data/app/models/edition.rb +286 -0
  26. data/app/models/expectant.rb +21 -0
  27. data/app/models/expectation.rb +12 -0
  28. data/app/models/guide_edition.rb +19 -0
  29. data/app/models/help_page_edition.rb +13 -0
  30. data/app/models/licence_edition.rb +35 -0
  31. data/app/models/local_authority.rb +58 -0
  32. data/app/models/local_interaction.rb +20 -0
  33. data/app/models/local_service.rb +49 -0
  34. data/app/models/local_transaction_edition.rb +49 -0
  35. data/app/models/overview_dashboard.rb +25 -0
  36. data/app/models/part.rb +28 -0
  37. data/app/models/parted.rb +32 -0
  38. data/app/models/place_edition.rb +20 -0
  39. data/app/models/programme_edition.rb +26 -0
  40. data/app/models/simple_smart_answer_edition.rb +66 -0
  41. data/app/models/simple_smart_answer_edition/node.rb +40 -0
  42. data/app/models/simple_smart_answer_edition/node/option.rb +31 -0
  43. data/app/models/tag.rb +88 -0
  44. data/app/models/transaction_edition.rb +28 -0
  45. data/app/models/travel_advice_edition.rb +177 -0
  46. data/app/models/user.rb +54 -0
  47. data/app/models/video_edition.rb +24 -0
  48. data/app/models/workflow.rb +217 -0
  49. data/app/models/workflow_actor.rb +141 -0
  50. data/app/traits/attachable.rb +60 -0
  51. data/app/traits/govspeak_smart_quotes_fixer.rb +19 -0
  52. data/app/traits/taggable.rb +113 -0
  53. data/app/validators/safe_html.rb +33 -0
  54. data/app/validators/slug_validator.rb +53 -0
  55. data/config/mongoid.yml +5 -0
  56. data/govuk_content_models.gemspec +42 -0
  57. data/jenkins.sh +7 -0
  58. data/lib/fact_check_address.rb +36 -0
  59. data/lib/govuk_content_models.rb +12 -0
  60. data/lib/govuk_content_models/require_all.rb +14 -0
  61. data/lib/govuk_content_models/test_helpers/factories.rb +213 -0
  62. data/lib/govuk_content_models/test_helpers/local_services.rb +24 -0
  63. data/lib/govuk_content_models/version.rb +4 -0
  64. data/test/fixtures/contactotron_api_response.json +1 -0
  65. data/test/fixtures/uploads/image.jpg +0 -0
  66. data/test/models/artefact_action_test.rb +123 -0
  67. data/test/models/artefact_external_link_test.rb +32 -0
  68. data/test/models/artefact_tag_test.rb +52 -0
  69. data/test/models/artefact_test.rb +583 -0
  70. data/test/models/business_support/business_size_test.rb +25 -0
  71. data/test/models/business_support/business_type_test.rb +25 -0
  72. data/test/models/business_support/location_test.rb +25 -0
  73. data/test/models/business_support/purpose_test.rb +29 -0
  74. data/test/models/business_support/sector_test.rb +25 -0
  75. data/test/models/business_support/stage_test.rb +25 -0
  76. data/test/models/business_support/support_type_test.rb +25 -0
  77. data/test/models/business_support_edition_test.rb +186 -0
  78. data/test/models/campaign_edition_test.rb +90 -0
  79. data/test/models/curated_list_test.rb +32 -0
  80. data/test/models/edition_test.rb +826 -0
  81. data/test/models/fact_check_address_test.rb +36 -0
  82. data/test/models/help_page_edition_test.rb +38 -0
  83. data/test/models/licence_edition_test.rb +104 -0
  84. data/test/models/local_authority_test.rb +113 -0
  85. data/test/models/local_service_test.rb +199 -0
  86. data/test/models/local_transaction_edition_test.rb +78 -0
  87. data/test/models/overview_dashboard_test.rb +47 -0
  88. data/test/models/simple_smart_answer_edition_test.rb +169 -0
  89. data/test/models/simple_smart_answer_node_test.rb +134 -0
  90. data/test/models/simple_smart_answer_option_test.rb +90 -0
  91. data/test/models/tag_test.rb +92 -0
  92. data/test/models/time_zone_test.rb +48 -0
  93. data/test/models/transaction_edition_test.rb +20 -0
  94. data/test/models/travel_advice_edition_test.rb +480 -0
  95. data/test/models/user_test.rb +114 -0
  96. data/test/models/video_edition_test.rb +64 -0
  97. data/test/models/workflow_actor_test.rb +61 -0
  98. data/test/models/workflow_test.rb +307 -0
  99. data/test/test_helper.rb +47 -0
  100. data/test/traits/attachable_test.rb +143 -0
  101. data/test/traits/taggable_test.rb +114 -0
  102. data/test/validators/safe_html_validator_test.rb +86 -0
  103. data/test/validators/slug_validator_test.rb +42 -0
  104. metadata +511 -0
@@ -0,0 +1,21 @@
1
+ module Expectant
2
+
3
+ module ClassMethods
4
+ def expectation_choices
5
+ Hash[Expectation.all.order_by(text: :asc).map {|e| [e.text, e._id.to_s] }]
6
+ end
7
+ end
8
+
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ field :expectation_ids, type: Array, default: []
13
+ field :minutes_to_complete, type: String
14
+ field :uses_government_gateway, type: Boolean
15
+ end
16
+
17
+ def expectations
18
+ Expectation.criteria.in(_id: self.expectation_ids)
19
+ end
20
+
21
+ end
@@ -0,0 +1,12 @@
1
+ require "safe_html"
2
+
3
+ class Expectation
4
+ include Mongoid::Document
5
+ cache
6
+
7
+ field :text, type: String
8
+
9
+ GOVSPEAK_FIELDS = []
10
+
11
+ validates_with SafeHtml
12
+ end
@@ -0,0 +1,19 @@
1
+ require "edition"
2
+ require "parted"
3
+
4
+ class GuideEdition < Edition
5
+ include Parted
6
+
7
+ field :video_url, type: String
8
+ field :video_summary, type: String
9
+
10
+ @fields_to_clone = [:video_url, :video_summary]
11
+
12
+ def has_video?
13
+ video_url.present?
14
+ end
15
+
16
+ def safe_to_preview?
17
+ parts.any? and parts.first.slug.present?
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ require "edition"
2
+
3
+ class HelpPageEdition < Edition
4
+ field :body, type: String
5
+
6
+ GOVSPEAK_FIELDS = Edition::GOVSPEAK_FIELDS + [:body]
7
+
8
+ @fields_to_clone = [:body]
9
+
10
+ def whole_body
11
+ self.body
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ require "edition"
2
+
3
+ class LicenceEdition < Edition
4
+
5
+ field :licence_identifier, :type => String
6
+ field :licence_short_description, :type => String
7
+ field :licence_overview, :type => String
8
+ field :will_continue_on, :type => String
9
+ field :continuation_link, :type => String
10
+
11
+ GOVSPEAK_FIELDS = Edition::GOVSPEAK_FIELDS + [:licence_overview]
12
+
13
+ validates :licence_identifier, :presence => true
14
+ validate :licence_identifier_unique
15
+ validates_format_of :continuation_link, :with => URI::regexp(%w(http https)), :allow_blank => true
16
+
17
+ @fields_to_clone = [:licence_identifier, :licence_short_description,
18
+ :licence_overview, :will_continue_on, :continuation_link]
19
+
20
+ def whole_body
21
+ [licence_short_description, licence_overview].join("\n\n")
22
+ end
23
+
24
+ def indexable_content
25
+ "#{super} #{licence_short_description} #{Govspeak::Document.new(licence_overview).to_text}".strip
26
+ end
27
+
28
+ private
29
+ def licence_identifier_unique
30
+ if self.class.without_state('archived').where(:licence_identifier => licence_identifier,
31
+ :panopticon_id.ne => panopticon_id).any?
32
+ errors.add(:licence_identifier, :taken)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,58 @@
1
+ require "csv"
2
+ require "local_interaction"
3
+ require "safe_html"
4
+
5
+ class LocalAuthority
6
+ include Mongoid::Document
7
+
8
+ embeds_many :local_interactions
9
+
10
+ field :name, type: String
11
+ field :snac, type: String
12
+ field :local_directgov_id, type: Integer
13
+ field :tier, type: String
14
+ field :contact_address, type: Array
15
+ field :contact_url, type: String
16
+ field :contact_phone, type: String
17
+ field :contact_email, type: String
18
+
19
+ GOVSPEAK_FIELDS = []
20
+
21
+ validates_uniqueness_of :snac
22
+ validates_presence_of :snac, :local_directgov_id, :name, :tier
23
+ validates_with SafeHtml
24
+
25
+ scope :for_snacs, ->(snacs) { any_in(snac: snacs) }
26
+
27
+ def self.find_by_snac(snac)
28
+ for_snacs([snac]).first
29
+ end
30
+
31
+ def provides_service?(lgsl_code, lgil_code = nil)
32
+ interactions_for(lgsl_code, lgil_code).any?
33
+ end
34
+
35
+ def interactions_for(lgsl_code, lgil_code = nil)
36
+ interactions = local_interactions.where(lgsl_code: lgsl_code)
37
+ if lgil_code
38
+ interactions.where(lgil_code: lgil_code)
39
+ else
40
+ interactions
41
+ end
42
+ end
43
+
44
+ def preferred_interaction_for(lgsl_code, lgil_code = nil)
45
+ interactions = local_interactions.where(lgsl_code: lgsl_code)
46
+ if lgil_code
47
+ interactions.where(lgil_code: lgil_code).first
48
+ else
49
+ interactions.excludes(
50
+ lgil_code: LocalInteraction::LGIL_CODE_PROVIDING_INFORMATION
51
+ ).order_by([:lgil_code, :asc]).first ||
52
+ interactions.where(
53
+ lgil_code: LocalInteraction::LGIL_CODE_PROVIDING_INFORMATION
54
+ ).first
55
+ end
56
+ end
57
+
58
+ end
@@ -0,0 +1,20 @@
1
+ require "csv"
2
+ require "safe_html"
3
+
4
+ class LocalInteraction
5
+ include Mongoid::Document
6
+
7
+ LGIL_CODE_PROVIDING_INFORMATION = 8
8
+
9
+ field :lgsl_code, type: Integer
10
+ field :lgil_code, type: Integer
11
+ field :url, type: String
12
+
13
+ GOVSPEAK_FIELDS = []
14
+
15
+ embedded_in :local_authority
16
+
17
+ validates_presence_of :url, :lgil_code, :lgsl_code
18
+ validates_uniqueness_of :lgil_code, :scope => :lgsl_code
19
+ validates_with SafeHtml
20
+ end
@@ -0,0 +1,49 @@
1
+ require "csv"
2
+ require "local_authority"
3
+ require "safe_html"
4
+
5
+ class LocalService
6
+ include Mongoid::Document
7
+
8
+ field :description, type: String
9
+ field :lgsl_code, type: Integer
10
+ field :providing_tier, type: Array
11
+
12
+ GOVSPEAK_FIELDS = []
13
+
14
+ validates_presence_of :lgsl_code, :providing_tier
15
+ validates_uniqueness_of :lgsl_code
16
+ validates :providing_tier, inclusion: {
17
+ in: [%w{county unitary}, %w{district unitary}, %w{district unitary county}]
18
+ }
19
+ validates_with SafeHtml
20
+
21
+ def self.find_by_lgsl_code(lgsl_code)
22
+ LocalService.where(lgsl_code: lgsl_code).first
23
+ end
24
+
25
+ def preferred_interaction(snac_or_snac_list, lgil_override = nil)
26
+ provider = preferred_provider(snac_or_snac_list)
27
+ provider && provider.preferred_interaction_for(lgsl_code, lgil_override)
28
+ end
29
+
30
+ def preferred_provider(snac_or_snac_list)
31
+ snac_list = [*snac_or_snac_list]
32
+ providers = LocalAuthority.for_snacs(snac_list)
33
+ select_tier(providers)
34
+ end
35
+
36
+ def provided_by
37
+ LocalAuthority.where("local_interactions.lgsl_code" => lgsl_code)
38
+ .any_in(tier: providing_tier)
39
+ end
40
+
41
+ private
42
+
43
+ def select_tier(authorities)
44
+ by_tier = Hash[authorities.map {|a| [a.tier, a]}]
45
+ tier = providing_tier.find { |t| by_tier.has_key?(t) }
46
+ tier && by_tier[tier]
47
+ end
48
+
49
+ end
@@ -0,0 +1,49 @@
1
+ require "expectant"
2
+ require "local_service"
3
+ require "edition"
4
+
5
+ class LocalTransactionEdition < Edition
6
+ include Expectant
7
+
8
+ field :lgsl_code, type: Integer
9
+ field :lgil_override, type: Integer
10
+ field :introduction, type: String
11
+ field :more_information, type: String
12
+
13
+ GOVSPEAK_FIELDS = Edition::GOVSPEAK_FIELDS + [:introduction, :more_information]
14
+
15
+ @fields_to_clone = [
16
+ :lgsl_code, :introduction, :more_information,
17
+ :minutes_to_complete, :expectation_ids
18
+ ]
19
+
20
+ validate :valid_lgsl_code
21
+
22
+ def valid_lgsl_code
23
+ if ! self.service
24
+ errors.add(:lgsl_code, "#{lgsl_code} not recognised")
25
+ end
26
+ end
27
+
28
+ def format_name
29
+ "Local transaction"
30
+ end
31
+
32
+ def search_format
33
+ "transaction"
34
+ end
35
+
36
+ def service
37
+ LocalService.find_by_lgsl_code(lgsl_code)
38
+ end
39
+
40
+ def service_provided_by?(snac)
41
+ authority = LocalAuthority.find_by_snac(snac)
42
+ authority && authority.provides_service?(lgsl_code)
43
+ end
44
+
45
+ def whole_body
46
+ self.introduction
47
+ end
48
+
49
+ end
@@ -0,0 +1,25 @@
1
+ require "safe_html"
2
+
3
+ class OverviewDashboard
4
+ include Mongoid::Document
5
+
6
+ TOTAL_KEY = "**TOTAL**"
7
+ UNASSIGNED_KEY = "**UNASSIGNED**"
8
+
9
+ field :dashboard_type, type: String
10
+ field :result_group, type: Integer
11
+ field :count, type: Integer
12
+ field :lined_up, type: Integer
13
+ field :draft, type: Integer
14
+ field :amends_needed, type: Integer
15
+ field :in_review, type: Integer
16
+ field :ready, type: Integer
17
+ field :fact_check_received, type: Integer
18
+ field :fact_check, type: Integer
19
+ field :published, type: Integer
20
+ field :archived, type: Integer
21
+
22
+ GOVSPEAK_FIELDS = []
23
+
24
+ validates_with SafeHtml
25
+ end
@@ -0,0 +1,28 @@
1
+ require "safe_html"
2
+ require 'govspeak_smart_quotes_fixer'
3
+
4
+ class Part
5
+ include Mongoid::Document
6
+
7
+ embedded_in :guide_edition
8
+ embedded_in :programme_edition
9
+ embedded_in :business_support_edition
10
+
11
+ scope :in_order, order_by(:order, :asc)
12
+
13
+ field :order, type: Integer
14
+ field :title, type: String
15
+ field :body, type: String
16
+ field :slug, type: String
17
+ field :created_at, type: DateTime, default: lambda { Time.zone.now }
18
+
19
+ GOVSPEAK_FIELDS = [:body]
20
+
21
+ include GovspeakSmartQuotesFixer
22
+
23
+ validates_presence_of :title
24
+ validates_presence_of :slug
25
+ validates_exclusion_of :slug, in: ["video"], message: "Can not be video"
26
+ validates_format_of :slug, with: /^[a-z0-9\-]+$/i
27
+ validates_with SafeHtml
28
+ end
@@ -0,0 +1,32 @@
1
+ require "part"
2
+
3
+ module Parted
4
+ def self.included(klass)
5
+ klass.embeds_many :parts
6
+ klass.accepts_nested_attributes_for :parts, allow_destroy: true,
7
+ reject_if: proc { |attrs| attrs["title"].blank? and attrs["body"].blank? }
8
+ end
9
+
10
+ def build_clone(edition_class=nil)
11
+ new_edition = super
12
+
13
+ # If the new edition is of the same type or another type that has parts,
14
+ # copy over the parts from this edition
15
+ if edition_class.nil? or edition_class.include? Parted
16
+ new_edition.parts = self.parts.map {|p| p.dup }
17
+ end
18
+
19
+ new_edition
20
+ end
21
+
22
+ def order_parts
23
+ ordered_parts = parts.sort_by { |p| p.order ? p.order : 99999 }
24
+ ordered_parts.each_with_index do |obj, i|
25
+ obj.order = i + 1
26
+ end
27
+ end
28
+
29
+ def whole_body
30
+ self.parts.map {|i| %Q{\# #{i.title}\n\n#{i.body}} }.join("\n\n")
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ require "edition"
2
+ require "expectant"
3
+
4
+ class PlaceEdition < Edition
5
+ include Expectant
6
+
7
+ field :introduction, type: String
8
+ field :more_information, type: String
9
+ field :place_type, type: String
10
+
11
+ GOVSPEAK_FIELDS = Edition::GOVSPEAK_FIELDS + [:introduction, :more_information]
12
+
13
+ @fields_to_clone = [:introduction, :more_information, :place_type,
14
+ :expectation_ids]
15
+
16
+ def whole_body
17
+ self.introduction
18
+ end
19
+
20
+ end
@@ -0,0 +1,26 @@
1
+ require "edition"
2
+ require "parted"
3
+
4
+ class ProgrammeEdition < Edition
5
+ include Parted
6
+
7
+ before_save :setup_default_parts, on: :create
8
+
9
+ @fields_to_clone = []
10
+
11
+ DEFAULT_PARTS = [
12
+ {title: "Overview", slug: "overview"},
13
+ {title: "What you'll get", slug: "what-youll-get"},
14
+ {title: "Eligibility", slug: "eligibility"},
15
+ {title: "How to claim", slug: "how-to-claim"},
16
+ {title: "Further information", slug: "further-information"},
17
+ ]
18
+
19
+ def setup_default_parts
20
+ if parts.empty?
21
+ DEFAULT_PARTS.each { |part|
22
+ parts.build(title: part[:title], slug: part[:slug], body: "")
23
+ }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,66 @@
1
+ require "edition"
2
+ require_relative 'simple_smart_answer_edition/node'
3
+ require_relative 'simple_smart_answer_edition/node/option'
4
+
5
+ class SimpleSmartAnswerEdition < Edition
6
+ include Mongoid::Document
7
+
8
+ field :body, type: String
9
+
10
+ embeds_many :nodes, :class_name => "SimpleSmartAnswerEdition::Node"
11
+
12
+ accepts_nested_attributes_for :nodes, allow_destroy: true
13
+
14
+ GOVSPEAK_FIELDS = Edition::GOVSPEAK_FIELDS + [:body]
15
+ @fields_to_clone = [:body]
16
+
17
+ def whole_body
18
+ body
19
+ end
20
+
21
+ def build_clone(edition_class=nil)
22
+ new_edition = super(edition_class)
23
+ new_edition.body = self.body
24
+
25
+ if new_edition.is_a?(SimpleSmartAnswerEdition)
26
+ self.nodes.each {|n| new_edition.nodes << n.clone }
27
+ end
28
+
29
+ new_edition
30
+ end
31
+
32
+
33
+ # Workaround mongoid conflicting mods error
34
+ # See https://github.com/mongoid/mongoid/issues/1219
35
+ # Override update_attributes so that nested nodes are updated individually.
36
+ # This get around the problem of mongoid issuing a query with conflicting modifications
37
+ # to the same document.
38
+ alias_method :original_update_attributes, :update_attributes
39
+
40
+ def update_attributes(attributes)
41
+ if nodes_attrs = attributes.delete(:nodes_attributes)
42
+ nodes_attrs.each do |index, node_attrs|
43
+ if node_id = node_attrs['id']
44
+ node = nodes.find(node_id)
45
+ if destroy_in_attrs?(node_attrs)
46
+ node.destroy
47
+ else
48
+ node.update_attributes(node_attrs)
49
+ end
50
+ else
51
+ nodes << Node.new(node_attrs) unless destroy_in_attrs?(node_attrs)
52
+ end
53
+ end
54
+ end
55
+
56
+ original_update_attributes(attributes)
57
+ end
58
+
59
+ def initial_node
60
+ self.nodes.first
61
+ end
62
+
63
+ def destroy_in_attrs?(attrs)
64
+ attrs['_destroy'] == '1'
65
+ end
66
+ end