para 0.6.3 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/para/admin/job-tracker.coffee +1 -1
  3. data/app/assets/javascripts/para/admin/tabs.coffee +14 -8
  4. data/app/assets/javascripts/para/admin.js +20 -0
  5. data/app/assets/javascripts/para/plugins-includes.js.erb +1 -0
  6. data/app/assets/stylesheets/para/admin.sass +1 -0
  7. data/app/assets/stylesheets/para/plugins-includes.sass.erb +1 -0
  8. data/app/controllers/concerns/para/admin/resource_controller_concerns.rb +8 -2
  9. data/app/controllers/para/admin/crud_resources_controller.rb +1 -1
  10. data/app/controllers/para/admin/exports_controller.rb +1 -1
  11. data/app/controllers/para/admin/resources_controller.rb +7 -2
  12. data/app/decorators/para/component/base_decorator.rb +2 -2
  13. data/app/helpers/para/admin/pagination_helper.rb +31 -0
  14. data/app/helpers/para/model_helper.rb +14 -4
  15. data/app/models/application_record.rb +3 -0
  16. data/app/models/para/cache/item.rb +27 -0
  17. data/app/models/para/page/section.rb +0 -6
  18. data/app/views/para/admin/resources/_exports_menu.html.haml +2 -2
  19. data/app/views/para/admin/resources/_list.html.haml +1 -1
  20. data/app/views/para/admin/resources/_per_page_select.html.haml +10 -0
  21. data/app/views/para/inputs/_nested_many.html.haml +1 -1
  22. data/app/views/para/inputs/nested_many/_add_with_subclasses.html.haml +1 -1
  23. data/config/locales/fr.yml +1 -0
  24. data/db/migrate/20161006105728_create_para_cache_items.rb +12 -0
  25. data/lib/generators/para/admin_user/admin_user_generator.rb +2 -1
  26. data/lib/generators/para/exporter/exporter_generator.rb +1 -5
  27. data/lib/generators/para/exporter/templates/base_exporter.rb +18 -12
  28. data/lib/generators/para/exporter/templates/csv_exporter.rb +35 -11
  29. data/lib/generators/para/exporter/templates/xls_exporter.rb +48 -0
  30. data/lib/generators/para/orderable/orderable_generator.rb +1 -1
  31. data/lib/generators/para/page/section/section_generator.rb +1 -1
  32. data/lib/para/attribute_field/base.rb +4 -0
  33. data/lib/para/attribute_field/datetime.rb +1 -1
  34. data/lib/para/attribute_field/file.rb +4 -0
  35. data/lib/para/attribute_field/image.rb +4 -0
  36. data/lib/para/cache/database_store.rb +71 -0
  37. data/lib/para/cache.rb +11 -0
  38. data/lib/para/config.rb +28 -2
  39. data/lib/para/engine.rb +9 -14
  40. data/lib/para/exporter/base.rb +26 -0
  41. data/lib/para/exporter/csv.rb +6 -26
  42. data/lib/para/exporter/table.rb +23 -0
  43. data/lib/para/exporter/xls.rb +59 -0
  44. data/lib/para/exporter.rb +6 -3
  45. data/lib/para/ext.rb +0 -1
  46. data/lib/para/form_builder/containers.rb +14 -1
  47. data/lib/para/form_builder.rb +1 -0
  48. data/lib/para/importer/base.rb +2 -28
  49. data/lib/para/inputs/nested_many_input.rb +9 -4
  50. data/lib/para/job/base.rb +23 -4
  51. data/lib/para/logging/active_job_log_subscriber.rb +2 -0
  52. data/lib/para/orderable.rb +2 -2
  53. data/lib/para/plugins/set.rb +34 -0
  54. data/lib/para/plugins.rb +3 -2
  55. data/lib/para/routing/component_controller_constraint.rb +1 -1
  56. data/lib/para/routing/component_name_constraint.rb +22 -0
  57. data/lib/para/routing.rb +1 -0
  58. data/lib/para/sti/root_model.rb +4 -54
  59. data/lib/para/version.rb +1 -1
  60. data/lib/para.rb +4 -1
  61. data/lib/rails/routing_mapper.rb +67 -34
  62. data/vendor/assets/javascripts/html5-sortable.js +142 -0
  63. data/vendor/assets/javascripts/jasny.bootstrap.min.js +7 -0
  64. data/vendor/assets/javascripts/jquery.remote-modal-form.coffee +67 -0
  65. data/vendor/assets/javascripts/jquery.scrollto.js +187 -0
  66. data/vendor/assets/stylesheets/animate.min.css +6 -0
  67. data/vendor/assets/stylesheets/jasny.bootstrap.min.css +7 -0
  68. metadata +56 -9
  69. data/app/assets/javascripts/para/admin.coffee +0 -19
  70. data/lib/para/ext/simple_form_extension.rb +0 -27
@@ -0,0 +1,71 @@
1
+ # This is a simple ActiveSupport::Cache::Store implementation, borrowed from
2
+ # activesupport-db-cache gem (https://github.com/sergio-fry/activesupport-db-cache),
3
+ # simplified.
4
+ #
5
+ # It also adds RequestStore.store caching, avoiding same key reads to hit
6
+ # ActiveRecord multiple times in a single request
7
+ #
8
+ # This is primarily meant to manage jobs status storing in Para::Job::Base
9
+ # through the ActiveJob::Status gem. It allows not to add external storage
10
+ # systems dependency (redis, memcache) to the app when not needed (when using
11
+ # default ActiveJob 5 AsyncAdapter or SuckerPunchAdapter).
12
+ #
13
+ # Since it's quite slow, it's advised to replace it with faster cache stores
14
+ # like Redis or Memcache when used in large production systems.
15
+ #
16
+ module Para
17
+ module Cache
18
+ class DatabaseStore < ActiveSupport::Cache::Store
19
+ def clear
20
+ Item.delete_all
21
+ end
22
+
23
+ def delete_entry(key, options)
24
+ Item.delete_all(key: key)
25
+ end
26
+
27
+ def read_entry(key, options={})
28
+ RequestStore.store[cache_key_for(key)] ||= Item.find_by(key: key)
29
+ end
30
+
31
+ def write_entry(key, entry, options)
32
+ RequestStore.store[cache_key_for(key)] ||= Item.where(key: key).first_or_initialize
33
+ item = RequestStore.store[cache_key_for(key)]
34
+
35
+ options = options.clone.symbolize_keys
36
+
37
+ item.value = entry.value
38
+ item.expires_at = options[:expires_in].try(:since)
39
+ item.save!
40
+
41
+ # Ensure cached item in RequestStore is up to date
42
+ RequestStore.store[cache_key_for(key)] = item
43
+ rescue ActiveRecord::RecordNotUnique
44
+ ensure
45
+ clear_expired_keys
46
+ end
47
+
48
+ private
49
+
50
+ def clear_expired_keys
51
+ if Item.count > Para.config.database_cache_store_max_items
52
+ remove_expired_items
53
+ remove_old_items
54
+ end
55
+ end
56
+
57
+ def remove_expired_items
58
+ Item.where("expires_at < ?", Time.now).delete_all
59
+ end
60
+
61
+ def remove_old_items
62
+ count = (Item.count - Para.config.database_cache_store_max_items)
63
+ Item.order('updated_at ASC').limit(count).delete_all if count > 0
64
+ end
65
+
66
+ def cache_key_for(key)
67
+ ['para.cache.database_store', key].join('.')
68
+ end
69
+ end
70
+ end
71
+ end
data/lib/para/cache.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Para
2
+ module Cache
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :DatabaseStore
6
+
7
+ def self.table_name_prefix
8
+ 'para_cache_'
9
+ end
10
+ end
11
+ end
data/lib/para/config.rb CHANGED
@@ -18,8 +18,13 @@ module Para
18
18
  mattr_accessor :resource_name_methods
19
19
  @@resource_name_methods = [:admin_name, :admin_title, :name, :title]
20
20
 
21
- mattr_accessor :plugins
22
- @@plugins = []
21
+ mattr_reader :plugins
22
+ @@plugins = Para::Plugins::Set.new
23
+
24
+ def self.plugins=(array)
25
+ # Add each item in array to the existing plugins set
26
+ array.each(&plugins.method(:<<))
27
+ end
23
28
 
24
29
  # Hidden from initializer on purpose.
25
30
  #
@@ -31,6 +36,27 @@ module Para
31
36
  mattr_accessor :page_actions
32
37
  @@page_actions = {}
33
38
 
39
+ # Allows changing default cache store used by Para to store jobs through
40
+ # the ActiveJob::Status gem
41
+ #
42
+ def self.jobs_store=(store)
43
+ @@jobs_store = store
44
+ ActiveJob::Status.store = store
45
+ end
46
+
47
+ # Default to use Para::Cache::DatabaseStore, allowing cross process and
48
+ # app instances job status sharing
49
+ #
50
+ def self.jobs_store
51
+ @@jobs_store ||= Para::Cache::DatabaseStore.new
52
+ end
53
+
54
+ # The DatabaseStore cache system tries to clean up old keys when reaching
55
+ # that limit after writing new keys
56
+ #
57
+ mattr_accessor :database_cache_store_max_items
58
+ @@database_cache_store_max_items = 10000
59
+
34
60
  # Allows accessing plugins root module to configure them through a method
35
61
  # from the Para::Config class.
36
62
  #
data/lib/para/engine.rb CHANGED
@@ -48,12 +48,11 @@ module Para
48
48
  end
49
49
  end
50
50
 
51
- initializer 'Extend simple form extension selectize input' do
52
- ActiveSupport.on_load(:active_record) do
53
- ::SimpleFormExtension::Inputs::SelectizeInput.send(
54
- :include, Para::Ext::SimpleFormExtension::SelectizeInput
55
- )
56
- end
51
+ initializer 'Add resource name methods to simple form extension' do
52
+ ::SimpleFormExtension.resource_name_methods = (
53
+ Para.config.resource_name_methods +
54
+ ::SimpleFormExtension.resource_name_methods
55
+ ).uniq
57
56
  end
58
57
 
59
58
  initializer 'Extend active job status\' status class' do
@@ -72,19 +71,15 @@ module Para
72
71
  end
73
72
  end
74
73
 
75
- initializer 'Load page sections' do |app|
76
- app.config.to_prepare do
77
- Para::Page::Section.eager_load!
78
- end
79
- end
80
-
81
74
  initializer 'Check for extensions installation' do
82
75
  Para::PostgresExtensionsChecker.check_all
83
76
  end
84
77
 
85
78
  initializer 'Configure ActiveJob' do
86
- if ActiveSupport::Cache::NullStore === ActiveJob::Status.store
87
- ActiveJob::Status.store = :memory_store
79
+ if ActiveSupport::Cache::NullStore === ActiveJob::Status.store ||
80
+ ActiveSupport::Cache::MemoryStore === ActiveJob::Status.store
81
+ then
82
+ ActiveJob::Status.store = Para.config.jobs_store
88
83
  end
89
84
 
90
85
  Para::Logging::ActiveJobLogSubscriber.attach_to :active_job
@@ -25,6 +25,28 @@ module Para
25
25
 
26
26
  private
27
27
 
28
+ def render
29
+ Tempfile.new([name, extension]).tap do |file|
30
+ file.binmode if binary?
31
+ file.write(generate)
32
+ file.rewind
33
+ end
34
+ end
35
+
36
+ def generate
37
+ fail NotImplementedError
38
+ end
39
+
40
+ # Default to writing string data to the exported file, allowing
41
+ # subclasses to write binary data if needed
42
+ def binary?
43
+ false
44
+ end
45
+
46
+ def total_progress
47
+ resources.length
48
+ end
49
+
28
50
  # Allow passing a `:resources` option or a ransack search hash to filter
29
51
  # exported resources
30
52
  #
@@ -37,6 +59,10 @@ module Para
37
59
  model.all
38
60
  end
39
61
  end
62
+
63
+ def encode(string)
64
+ string.presence && string.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
65
+ end
40
66
  end
41
67
  end
42
68
  end
@@ -2,43 +2,23 @@ require 'csv'
2
2
 
3
3
  module Para
4
4
  module Exporter
5
- class Csv < Base
6
- def render
7
- data = CSV.generate do |csv|
5
+ class Csv < Table
6
+ protected
7
+
8
+ def generate
9
+ CSV.generate do |csv|
8
10
  csv << headers
9
11
 
10
12
  resources.each do |resource|
11
13
  csv << row_for(resource)
14
+ progress!
12
15
  end
13
16
  end
14
-
15
- Tempfile.new([name, extension]).tap do |file|
16
- file.write(data)
17
- file.rewind
18
- end
19
17
  end
20
18
 
21
- private
22
-
23
19
  def extension
24
20
  '.csv'
25
21
  end
26
-
27
- def headers
28
- fields.map do |field|
29
- encode(model.human_attribute_name(field))
30
- end
31
- end
32
-
33
- def row_for(resource)
34
- fields.map do |field|
35
- encode(resource.send(field))
36
- end
37
- end
38
-
39
- def encode(string)
40
- string.presence && string.to_s.encode('UTF-8')
41
- end
42
22
  end
43
23
  end
44
24
  end
@@ -0,0 +1,23 @@
1
+ # This class serves as a basic superclass for tabular data specific exports,
2
+ # which are organized as tables : a header row and several rows of data.
3
+ #
4
+ # This allows to only define the `#fields` method in the subclass and let the
5
+ # exporter work alone
6
+ #
7
+ module Para
8
+ module Exporter
9
+ class Table < Para::Exporter::Base
10
+ def headers
11
+ fields.map do |field|
12
+ encode(model.human_attribute_name(field))
13
+ end
14
+ end
15
+
16
+ def row_for(resource)
17
+ fields.map do |field|
18
+ encode(resource.send(field))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,59 @@
1
+ require 'stringio'
2
+ require 'spreadsheet'
3
+
4
+ module Para
5
+ module Exporter
6
+ class Xls < Table
7
+ protected
8
+
9
+ def extension
10
+ '.xls'
11
+ end
12
+
13
+ def name
14
+ 'export'
15
+ end
16
+
17
+ def mime_type
18
+ 'application/vnd.ms-excel'
19
+ end
20
+
21
+ def binary?
22
+ true
23
+ end
24
+
25
+ def generate
26
+ generate_workbook do |workbook|
27
+ sheet = workbook.create_worksheet
28
+
29
+ # Add headers
30
+ sheet.row(0).concat headers
31
+
32
+ # Add content rows
33
+ resources.each_with_index do |resource , index|
34
+ sheet.row(index + 1).concat row_for(resource)
35
+ end
36
+ end
37
+ end
38
+
39
+ def generate_workbook(&block)
40
+ workbook = Spreadsheet::Workbook.new
41
+
42
+ block.call(workbook)
43
+
44
+ buffer = StringIO.new
45
+ workbook.write(buffer)
46
+ buffer.rewind
47
+ buffer.read
48
+ end
49
+
50
+ def fields
51
+ fail NotImplementedError
52
+ end
53
+
54
+ def encode(string)
55
+ string.presence && string.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?')
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/para/exporter.rb CHANGED
@@ -1,7 +1,10 @@
1
1
  module Para
2
2
  module Exporter
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Base
6
+ autoload :Table
7
+ autoload :Csv
8
+ autoload :Xls
3
9
  end
4
10
  end
5
-
6
- require 'para/exporter/base'
7
- require 'para/exporter/csv'
data/lib/para/ext.rb CHANGED
@@ -11,5 +11,4 @@ end
11
11
 
12
12
  require 'para/ext/cancan'
13
13
  require 'para/ext/paperclip'
14
- require 'para/ext/simple_form_extension'
15
14
  require 'para/ext/active_job_status'
@@ -49,7 +49,20 @@ module Para
49
49
  end
50
50
 
51
51
  def para_submit_and_edit_button
52
- button(:submit, ::I18n.t('para.shared.save_and_edit'), name: '_save_and_edit', class: 'btn-primary')
52
+ # Create a hidden field that will hold the last tab used by the user,
53
+ # allowing redirection and re-rendering to display it directly
54
+ current_anchor_tag = template.hidden_field_tag(
55
+ :_current_anchor, template.params[:_current_anchor],
56
+ data: { :'current-anchor' => true }
57
+ )
58
+
59
+ button_tag = button(
60
+ :submit,
61
+ ::I18n.t('para.shared.save_and_edit'),
62
+ name: '_save_and_edit', class: 'btn-primary'
63
+ )
64
+
65
+ current_anchor_tag + button_tag
53
66
  end
54
67
 
55
68
  def para_submit_and_add_another_button
@@ -21,4 +21,5 @@ SimpleForm::FormBuilder.class_eval do
21
21
  include Para::FormBuilder::Tabs
22
22
  end
23
23
 
24
+ # Map IP Address fields to string
24
25
  SimpleForm::FormBuilder.map_type(:inet, to: SimpleForm::Inputs::StringInput)
@@ -1,8 +1,6 @@
1
1
  module Para
2
2
  module Importer
3
3
  class Base < Para::Job::Base
4
- rescue_from Exception, with: :rescue_exception
5
-
6
4
  class_attribute :allows_import_errors
7
5
 
8
6
  attr_reader :file, :sheet
@@ -29,9 +27,9 @@ module Para
29
27
  save_errors!
30
28
  end
31
29
 
32
- private
30
+ protected
33
31
 
34
- def progress_total
32
+ def total_progress
35
33
  sheet.last_row - 1
36
34
  end
37
35
 
@@ -68,30 +66,6 @@ module Para
68
66
  def headers
69
67
  @headers ||= sheet.row(1)
70
68
  end
71
-
72
- def rescue_exception(exception)
73
- status.update(status: :failed)
74
-
75
- tag_logger(self.class.name, self.job_id) do
76
- ActiveSupport::Notifications.instrument "failed.active_job",
77
- adapter: self.class.queue_adapter, job: self, exception: exception
78
- end
79
-
80
- if defined?(ExceptionNotifier)
81
- ExceptionNotifier.notify_exception(
82
- exception, data: {
83
- importer: self.class.name,
84
- payload: {
85
- file_type: file.class,
86
- file_id: file.id,
87
- file_name: file.attachment_file_name
88
- }
89
- }
90
- )
91
- end
92
-
93
- raise exception
94
- end
95
69
  end
96
70
  end
97
71
  end
@@ -26,7 +26,8 @@ module Para
26
26
  dom_identifier: dom_identifier,
27
27
  resources: resources,
28
28
  nested_locals: locals,
29
- subclass: subclass
29
+ subclass: subclass,
30
+ subclasses: subclasses
30
31
  }
31
32
  )
32
33
  end
@@ -49,9 +50,13 @@ module Para
49
50
  end
50
51
 
51
52
  def subclass
52
- @subclass ||= options.fetch(
53
- :subclass,
54
- model.respond_to?(:descendants) && model.descendants.length > 0
53
+ @subclass ||= options.fetch(:subclass, subclasses.presence)
54
+ end
55
+
56
+ def subclasses
57
+ options.fetch(
58
+ :subclasses,
59
+ (model.try(:descendants) || []).sort_by { |m| m.model_name.human }
55
60
  )
56
61
  end
57
62
  end
data/lib/para/job/base.rb CHANGED
@@ -2,11 +2,13 @@ module Para
2
2
  module Job
3
3
  class Base < ActiveJob::Base
4
4
  include ActiveJob::Status
5
- # Used to store import errors on the object
5
+ # Used to store job errors on the object
6
6
  include ActiveModel::Validations
7
- # Used to translate importer name with rails default `activemodel` i18n keys
7
+ # Used to translate job name with rails default `activemodel` i18n keys
8
8
  extend ActiveModel::Translation
9
9
 
10
+ rescue_from Exception, with: :rescue_exception
11
+
10
12
  before_perform :store_job_type
11
13
 
12
14
  protected
@@ -33,8 +35,8 @@ module Para
33
35
  def ensure_total_progress
34
36
  return if @total_progress
35
37
 
36
- @total_progress ||= if respond_to?(:progress_total)
37
- progress.total = progress_total
38
+ @total_progress ||= if respond_to?(:total_progress, true)
39
+ progress.total = total_progress
38
40
  else
39
41
  progress[:total]
40
42
  end
@@ -47,6 +49,23 @@ module Para
47
49
  status[key]
48
50
  end
49
51
  end
52
+
53
+ def rescue_exception(exception)
54
+ status.update(status: :failed)
55
+
56
+ tag_logger(self.class.name, job_id) do
57
+ ActiveSupport::Notifications.instrument "failed.active_job",
58
+ adapter: self.class.queue_adapter, job: self, exception: exception
59
+ end
60
+
61
+ if defined?(ExceptionNotifier)
62
+ ExceptionNotifier.notify_exception(
63
+ exception, data: { job: self.class.name, payload: arguments }
64
+ )
65
+ end
66
+
67
+ raise exception
68
+ end
50
69
  end
51
70
  end
52
71
  end
@@ -23,6 +23,8 @@ module Para
23
23
  if job.arguments.any?
24
24
  ' with arguments: ' +
25
25
  job.arguments.map { |arg| format(arg).inspect }.join(', ')
26
+ else
27
+ ''
26
28
  end
27
29
  end
28
30
 
@@ -3,7 +3,7 @@ module Para
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- scope :ordered, -> { order('position ASC') }
6
+ scope :ordered, -> { order("#{ table_name }.position ASC") }
7
7
  before_create :orderable_assign_position
8
8
  end
9
9
 
@@ -11,7 +11,7 @@ module Para
11
11
  return if attribute_present?(:position)
12
12
 
13
13
  last_resource = self.class.unscoped
14
- .order('position DESC')
14
+ .ordered
15
15
  .where.not(position: nil)
16
16
  .select(:position)
17
17
  .first
@@ -0,0 +1,34 @@
1
+ module Para
2
+ module Plugins
3
+ class Set
4
+ include Enumerable
5
+
6
+ attr_accessor :items
7
+
8
+ delegate :+, :<<, :each, to: :items
9
+
10
+ def initialize
11
+ @items = []
12
+ end
13
+
14
+ def javascript_includes
15
+ each_with_object([]) do |plugin, collection|
16
+ collection.concat(includes_for(:javascript, plugin))
17
+ end
18
+ end
19
+
20
+ def stylesheet_includes
21
+ each_with_object([]) do |plugin, collection|
22
+ collection.concat(includes_for(:stylesheet, plugin))
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def includes_for(type, plugin)
29
+ mod = Para::Plugins.module_name_for(plugin).constantize
30
+ mod.try(:config).try(:"#{ type }_includes") || []
31
+ end
32
+ end
33
+ end
34
+ end
data/lib/para/plugins.rb CHANGED
@@ -2,10 +2,11 @@ module Para
2
2
  module Plugins
3
3
  extend ActiveSupport::Autoload
4
4
 
5
- autoload :Routes
6
-
7
5
  def self.module_name_for(identifier)
8
6
  ['Para', identifier.to_s.camelize].join('::')
9
7
  end
10
8
  end
11
9
  end
10
+
11
+ require 'para/plugins/set'
12
+ require 'para/plugins/routes'
@@ -16,7 +16,7 @@ module Para
16
16
 
17
17
  def matches?(request)
18
18
  component = component_for(request.params[:component_id])
19
- return false unless component
19
+ return false unless component && component.controller
20
20
  component.controller.to_sym == controller
21
21
  end
22
22
 
@@ -0,0 +1,22 @@
1
+ # Allows constraining routing to components that explicitly declares to use a
2
+ # given component to manage their resources.
3
+ #
4
+ # It's mainly used to allow users to override the default component for the
5
+ # resources of a given Crud or Form components without having to
6
+ # subclass the component and declare all the routes again
7
+ #
8
+ module Para
9
+ module Routing
10
+ class ComponentNameConstraint
11
+ attr_reader :component
12
+
13
+ def initialize(component)
14
+ @component = component.to_s
15
+ end
16
+
17
+ def matches?(request)
18
+ component == request.params[:component]
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/para/routing.rb CHANGED
@@ -3,5 +3,6 @@ module Para
3
3
  extend ActiveSupport::Autoload
4
4
 
5
5
  autoload :ComponentControllerConstraint
6
+ autoload :ComponentNameConstraint
6
7
  end
7
8
  end