govuk_content_models 6.0.2

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