para 0.6.3 → 0.6.7

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 (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