para 0.5.4 → 0.6.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/para/admin/async-progress.coffee +29 -0
  3. data/app/assets/javascripts/para/admin/filters-form.coffee +1 -1
  4. data/app/assets/javascripts/para/admin/importers.coffee +38 -0
  5. data/app/assets/javascripts/para/admin/table.coffee +1 -1
  6. data/app/assets/javascripts/para/admin/tabs.coffee +1 -1
  7. data/app/assets/javascripts/para/admin/theme_actions.coffee +1 -1
  8. data/app/assets/javascripts/para/admin/tree.coffee +5 -5
  9. data/app/assets/javascripts/para/admin.coffee +2 -1
  10. data/app/assets/javascripts/para/inputs/nested_many.coffee +1 -1
  11. data/app/assets/javascripts/para/lib/remote-file-forms.coffee +60 -0
  12. data/app/assets/javascripts/para/lib/turbolinks-loading.coffee +1 -1
  13. data/app/assets/javascripts/para/lib/turbolinks-reloader.coffee +19 -0
  14. data/app/assets/stylesheets/para/admin/src/datetimepicker.sass +3 -1
  15. data/app/assets/stylesheets/para/admin/src/fuelux.sass +4 -2
  16. data/app/assets/stylesheets/para/admin/src/nested_many.sass +5 -0
  17. data/app/assets/stylesheets/para/admin/src/redactor.sass +8 -5
  18. data/app/assets/stylesheets/para/admin/src/selectize.sass +7 -4
  19. data/app/assets/stylesheets/para/admin/theme/_breadcrumb.sass +20 -12
  20. data/app/assets/stylesheets/para/admin/theme/_buttons.sass +6 -2
  21. data/app/assets/stylesheets/para/admin/theme/_checkable.sass +4 -2
  22. data/app/assets/stylesheets/para/admin/theme/_commonds.sass +6 -3
  23. data/app/assets/stylesheets/para/admin/theme/_dropdown.sass +3 -1
  24. data/app/assets/stylesheets/para/admin/theme/_form.sass +4 -2
  25. data/app/assets/stylesheets/para/admin/theme/_list.sass +7 -5
  26. data/app/assets/stylesheets/para/admin/theme/_navigation.sass +12 -66
  27. data/app/assets/stylesheets/para/admin/theme/_navtabs.sass +18 -14
  28. data/app/assets/stylesheets/para/admin/theme/_orderable.sass +7 -3
  29. data/app/assets/stylesheets/para/admin/theme/_panel.sass +19 -13
  30. data/app/assets/stylesheets/para/admin/theme/_tree.sass +4 -2
  31. data/app/assets/stylesheets/para/overrides/theme.sass +14 -10
  32. data/app/controllers/para/admin/base_controller.rb +0 -5
  33. data/app/controllers/para/admin/crud_resources_controller.rb +5 -19
  34. data/app/controllers/para/admin/{singleton_resources_controller.rb → form_resources_controller.rb} +4 -4
  35. data/app/controllers/para/admin/imports_controller.rb +68 -0
  36. data/app/controllers/para/admin/resources_controller.rb +0 -1
  37. data/app/decorators/para/component/base_decorator.rb +6 -18
  38. data/app/decorators/para/component/crud_decorator.rb +3 -3
  39. data/app/decorators/para/component/{singleton_resource_decorator.rb → form_decorator.rb} +6 -6
  40. data/app/helpers/para/admin/components_helper.rb +12 -1
  41. data/app/helpers/para/admin/decorators_helper.rb +13 -0
  42. data/app/helpers/para/markup_helper.rb +4 -0
  43. data/app/helpers/para/search_helper.rb +1 -4
  44. data/app/models/para/component/base.rb +17 -14
  45. data/app/models/para/component/crud.rb +17 -0
  46. data/app/models/para/component/form.rb +36 -0
  47. data/app/models/para/component/resource.rb +9 -20
  48. data/app/models/para/component/settings.rb +1 -1
  49. data/app/models/para/library/file.rb +23 -0
  50. data/app/models/para/library.rb +5 -0
  51. data/app/views/para/admin/form_resources/show.html.haml +4 -0
  52. data/app/views/para/admin/imports/_completed.html.haml +16 -0
  53. data/app/views/para/admin/imports/_failed.html.haml +7 -0
  54. data/app/views/para/admin/imports/_progress.html.haml +5 -0
  55. data/app/views/para/admin/imports/new.html.haml +14 -0
  56. data/app/views/para/admin/imports/show.html.haml +10 -0
  57. data/app/views/para/admin/resources/_fields.html.haml +1 -4
  58. data/app/views/para/admin/resources/_imports_menu.html.haml +17 -20
  59. data/app/views/para/admin/resources/_tree.html.haml +1 -1
  60. data/app/views/para/admin/shared/_breadcrumbs.html.haml +1 -4
  61. data/app/views/para/admin/shared/_navigation.html.haml +2 -2
  62. data/app/views/para/form/_tabs.html.haml +1 -1
  63. data/config/locales/en.yml +10 -0
  64. data/config/locales/fr.yml +12 -2
  65. data/db/migrate/20160905134106_create_para_library_files.rb +9 -0
  66. data/lib/generators/para/importer/templates/base_importer.rb +8 -1
  67. data/lib/generators/para/install/install_generator.rb +1 -2
  68. data/lib/generators/para/install/templates/components.rb +9 -5
  69. data/lib/generators/para/tree_item/tree_item_generator.rb +17 -0
  70. data/lib/para/attribute_field/base.rb +12 -4
  71. data/lib/para/attribute_field/enum.rb +7 -3
  72. data/lib/para/component/importable.rb +4 -4
  73. data/lib/para/component.rb +1 -1
  74. data/lib/para/components_cleaner.rb +66 -0
  75. data/lib/para/components_configuration.rb +3 -1
  76. data/lib/para/engine.rb +36 -3
  77. data/lib/para/ext/active_job_status.rb +13 -0
  78. data/lib/para/ext/paperclip.rb +10 -0
  79. data/lib/para/ext/simple_form_extension.rb +6 -0
  80. data/lib/para/ext.rb +1 -0
  81. data/lib/para/form_builder/nested_form.rb +5 -2
  82. data/lib/para/importer/base.rb +84 -7
  83. data/lib/para/inputs/nested_many_input.rb +1 -1
  84. data/lib/para/logging/active_job_log_subscriber.rb +48 -0
  85. data/lib/para/logging.rb +8 -0
  86. data/lib/para/markup/resources_table.rb +2 -4
  87. data/lib/para/model_field_parsers/enums.rb +19 -0
  88. data/lib/para/model_field_parsers/relations.rb +1 -1
  89. data/lib/para/model_field_parsers/store.rb +1 -1
  90. data/lib/para/model_field_parsers.rb +1 -0
  91. data/lib/para/plugins/routes.rb +2 -4
  92. data/lib/para/postgres_extensions_checker.rb +30 -0
  93. data/lib/para/routes.rb +5 -4
  94. data/lib/para/routing/component_controller_constraint.rb +36 -0
  95. data/lib/para/routing.rb +7 -0
  96. data/lib/para/sti/root_model.rb +8 -2
  97. data/lib/para/version.rb +1 -1
  98. data/lib/para.rb +10 -0
  99. data/lib/rails/routing_mapper.rb +59 -27
  100. data/lib/tasks/para_tasks.rake +21 -11
  101. metadata +73 -15
  102. data/app/models/para/component/singleton_resource.rb +0 -35
  103. data/app/views/para/admin/singleton_resources/show.html.haml +0 -4
@@ -6,16 +6,20 @@ module Para
6
6
  def value_for(instance)
7
7
  if (raw_value = instance.send(name)) &&
8
8
  path = enum_path_for(instance, raw_value)
9
- translation = ::I18n.t("activerecord.#{ path }", default: false)
9
+ translation = ::I18n.t("activerecord.#{ path }", default: '')
10
10
 
11
- translation || raw_value
11
+ translation.presence || raw_value
12
12
  end
13
13
  end
14
14
 
15
+ def field_type
16
+ :selectize
17
+ end
18
+
15
19
  private
16
20
 
17
21
  def enum_path_for(instance, key)
18
- ['enums', instance.class.model_name.i18n_key, name, key].join('.')
22
+ ['enums', instance.model_name.i18n_key, name, key].join('.')
19
23
  end
20
24
  end
21
25
  end
@@ -8,14 +8,14 @@ module Para
8
8
  end
9
9
 
10
10
  def importable?
11
- @importable ||= imports.length > 0
11
+ @importable ||= importers.length > 0
12
12
  end
13
13
 
14
14
  # TODO : Move :configuration column store to JSON instead of HStore
15
15
  # which handles more data types and will help us avoid eval here
16
- def imports
17
- @imports ||= if importers.present?
18
- eval(importers)
16
+ def importers
17
+ @importers ||= if (importers = configuration['importers'].presence)
18
+ eval(importers).map(&:constantize)
19
19
  else
20
20
  []
21
21
  end
@@ -25,5 +25,5 @@ require 'para/component/subclassable'
25
25
  require 'para/component/base'
26
26
  require 'para/component/resource'
27
27
  require 'para/component/crud'
28
- require 'para/component/singleton_resource'
28
+ require 'para/component/form'
29
29
  require 'para/component/settings'
@@ -0,0 +1,66 @@
1
+ module Para
2
+ class ComponentsCleaner
3
+ # Hide class instanciation
4
+ def self.run; new.run; end
5
+
6
+ def run
7
+ components.each do |component|
8
+ unless component == Para.components.component_for(component.identifier)
9
+ component.destroy
10
+ end
11
+ end
12
+
13
+ Para::ComponentSection.find_each do |section|
14
+ unless Para.components.section_for(section.identifier)
15
+ section.destroy
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def components
23
+ Para::Component::Base.all.to_a
24
+ rescue ActiveRecord::SubclassNotFound => e
25
+ # When a subclass is not found, define it and try loading all components.
26
+ #
27
+ # Since we're probably about to destroy this class, this sould not
28
+ # produce errors for the end user
29
+ #
30
+ if (match = e.message.match(/the subclass: '([\w:]+)'/))
31
+ define_temporary_component_subclass(match[1])
32
+ return components
33
+ end
34
+
35
+ raise e
36
+ end
37
+
38
+ def define_temporary_component_subclass(subclass_name)
39
+ subclass_path = subclass_name.split('::')
40
+
41
+ subclass_path.each_with_index.each_with_object([]) do |(const_name, index), path|
42
+ parent_name = path.join('::').presence
43
+
44
+ begin
45
+ [parent_name, const_name].compact.join('::').constantize
46
+ rescue NameError
47
+ # Last iterated constnat is the actual component subclass, others must
48
+ # be modules namespacing the component class
49
+ object = if index == (subclass_path.length - 1)
50
+ Class.new(Para::Component::Base)
51
+ else
52
+ Module.new
53
+ end
54
+
55
+ parent = parent_name ? parent_name.constantize : Object
56
+ # Define module or class in parent namespace
57
+ parent.const_set(const_name, object)
58
+ end
59
+
60
+ # Add const name to the path to allow namespaced constants to be
61
+ # defined in next loop iteration
62
+ path << const_name
63
+ end
64
+ end
65
+ end
66
+ end
@@ -86,6 +86,8 @@ module Para
86
86
  end
87
87
 
88
88
  tables_exist
89
+ rescue ActiveRecord::NoDatabaseError
90
+ false # Do not load components when the database is not installed
89
91
  end
90
92
 
91
93
  # Eager loads every file ending with _component.rb that's included in a
@@ -151,7 +153,7 @@ module Para
151
153
 
152
154
  def refresh(attributes = {})
153
155
  self.model = type.where(identifier: identifier).first_or_initialize
154
- model.assign_attributes(attributes.merge(options_with_defaults))
156
+ model.update_with(attributes.merge(options_with_defaults))
155
157
  model.save!
156
158
  end
157
159
 
data/lib/para/engine.rb CHANGED
@@ -29,7 +29,7 @@ module Para
29
29
  end
30
30
 
31
31
  initializer 'Extend paperclip attachment definition' do
32
- return unless Kernel.const_defined?('Paperclip')
32
+ next unless Kernel.const_defined?('Paperclip')
33
33
 
34
34
  ActiveSupport.on_load(:active_record) do
35
35
  ::Paperclip::HasAttachedFile.send(
@@ -39,7 +39,7 @@ module Para
39
39
  end
40
40
 
41
41
  initializer 'Extend cancan ControllerResource class' do
42
- return unless Kernel.const_defined?('CanCan')
42
+ next unless Kernel.const_defined?('CanCan')
43
43
 
44
44
  ActiveSupport.on_load(:active_record) do
45
45
  ::CanCan::ControllerResource.send(
@@ -56,6 +56,14 @@ module Para
56
56
  end
57
57
  end
58
58
 
59
+ initializer 'Extend active job status\' status class' do
60
+ ActiveSupport.on_load(:active_job) do
61
+ ::ActiveJob::Status::Status.send(
62
+ :include, Para::Ext::ActiveJob::StatusMixin
63
+ )
64
+ end
65
+ end
66
+
59
67
  initializer 'Build components tree' do |app|
60
68
  components_config_path = Rails.root.join('config', 'components.rb')
61
69
 
@@ -65,7 +73,32 @@ module Para
65
73
  end
66
74
 
67
75
  initializer 'Load page sections' do |app|
68
- Para::Page::Section.eager_load!
76
+ app.config.to_prepare do
77
+ Para::Page::Section.eager_load!
78
+ end
79
+ end
80
+
81
+ initializer 'Check for extensions installation' do
82
+ Para::PostgresExtensionsChecker.check_all
83
+ end
84
+
85
+ initializer 'Configure ActiveJob' do
86
+ if ActiveSupport::Cache::NullStore === ActiveJob::Status.store
87
+ ActiveJob::Status.store = :memory_store
88
+ end
89
+
90
+ Para::Logging::ActiveJobLogSubscriber.attach_to :active_job
91
+ end
92
+
93
+ # Allow generating resources in the gem without having all the unnecessary
94
+ # files generated
95
+ #
96
+ config.generators do |generators|
97
+ generators.skip_routes true if generators.respond_to?(:skip_routes)
98
+ generators.helper false
99
+ generators.stylesheets false
100
+ generators.javascripts false
101
+ generators.test_framework false
69
102
  end
70
103
  end
71
104
  end
@@ -0,0 +1,13 @@
1
+ module Para
2
+ module Ext
3
+ module ActiveJob
4
+ module StatusMixin
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ delegate :failed?, to: :status_inquiry
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -78,3 +78,13 @@ module Para
78
78
  end
79
79
  end
80
80
  end
81
+
82
+ require 'paperclip/media_type_spoof_detector'
83
+
84
+ module Paperclip
85
+ class MediaTypeSpoofDetector
86
+ def spoofed?
87
+ false
88
+ end
89
+ end
90
+ end
@@ -14,6 +14,12 @@ module Para
14
14
  return name
15
15
  end
16
16
  end
17
+
18
+ if option.respond_to?(:model_name)
19
+ [option.model_name.human, option.id].join(' - ')
20
+ else
21
+ option.to_s
22
+ end
17
23
  end
18
24
  end
19
25
  end
data/lib/para/ext.rb CHANGED
@@ -12,3 +12,4 @@ end
12
12
  require 'para/ext/cancan'
13
13
  require 'para/ext/paperclip'
14
14
  require 'para/ext/simple_form_extension'
15
+ require 'para/ext/active_job_status'
@@ -56,7 +56,7 @@ module Para
56
56
  return false unless nested?
57
57
 
58
58
  reflection = parent_object.class.reflect_on_association(nested_attribute_name)
59
- reflection && (reflection.options[:inverse_of] == field_name)
59
+ reflection && (reflection.options[:inverse_of].to_s == field_name.to_s)
60
60
  end
61
61
 
62
62
  def nested?
@@ -64,7 +64,10 @@ module Para
64
64
  end
65
65
 
66
66
  def nested_attribute_name
67
- options[:nested_attribute_name]
67
+ options[:nested_attribute_name] ||=
68
+ if (match = object_name.match(/\[(\w+)_attributes\]/))
69
+ match[1]
70
+ end
68
71
  end
69
72
 
70
73
  def parent_object
@@ -1,23 +1,100 @@
1
1
  module Para
2
2
  module Importer
3
- class Base
4
- attr_reader :sheet
3
+ class Base < ActiveJob::Base
4
+ include ActiveJob::Status
5
+ # Used to store import errors on the object
6
+ include ActiveModel::Validations
7
+ # Used to translate importer name with rails default `activemodel` i18n keys
8
+ extend ActiveModel::Naming
5
9
 
6
- def initialize(file)
7
- @sheet = Roo::Spreadsheet.open(file.path)
8
- end
10
+ rescue_from Exception, with: :rescue_exception
11
+
12
+ class_attribute :allows_import_errors
13
+
14
+ attr_reader :file, :sheet
15
+
16
+ def perform(file, options = {})
17
+ @file = file
18
+ @sheet = Roo::Spreadsheet.open(file.attachment_path, options)
19
+ progress.total = sheet.last_row - 1
9
20
 
10
- def run
11
21
  ActiveRecord::Base.transaction do
12
22
  (2..(sheet.last_row)).each do |index|
13
- import_from_row(sheet.row(index))
23
+ begin
24
+ progress.increment
25
+ import_from_row(sheet.row(index))
26
+ rescue ActiveRecord::RecordInvalid => error
27
+ if allows_import_errors?
28
+ add_errors_from(index, error.record)
29
+ else
30
+ raise
31
+ end
32
+ end
14
33
  end
15
34
  end
35
+
36
+ status.update(errors: errors.full_messages)
16
37
  end
17
38
 
39
+ private
40
+
18
41
  def import_from_row(row)
19
42
  raise '#import_from_row(row) must be defined'
20
43
  end
44
+
45
+ def add_errors_from(index, record)
46
+ # The file's row number starts at 1 and headers are striped, so we
47
+ # add 2 to the index to obtain the row number
48
+ row_name = I18n.t('para.import.row_error_prefix', number: index)
49
+
50
+ record.errors.messages.each do |attribute, messages|
51
+ messages.each do |message|
52
+ full_message = record.errors.full_message(attribute, message)
53
+
54
+ if (value = record.send(attribute)).presence
55
+ full_message << ' (<b>' << value << '</b>)'
56
+ end
57
+
58
+ errors.add(row_name, full_message)
59
+ end
60
+ end
61
+ end
62
+
63
+ def allows_import_errors?
64
+ !!self.class.allows_import_errors
65
+ end
66
+
67
+ def self.allow_import_errors!
68
+ self.allows_import_errors = true
69
+ end
70
+
71
+ def headers
72
+ @headers ||= sheet.row(1)
73
+ end
74
+
75
+ def rescue_exception(exception)
76
+ status.update(status: :failed)
77
+
78
+ tag_logger(self.class.name, self.job_id) do
79
+ ActiveSupport::Notifications.instrument "failed.active_job",
80
+ adapter: self.class.queue_adapter, job: self, exception: exception
81
+ end
82
+
83
+ if defined?(ExceptionNotifier)
84
+ ExceptionNotifier.notify_exception(
85
+ exception, data: {
86
+ importer: self.class.name,
87
+ payload: {
88
+ file_type: file.class,
89
+ file_id: file.id,
90
+ file_name: file.attachment_file_name
91
+ }
92
+ }
93
+ )
94
+ end
95
+
96
+ raise exception
97
+ end
21
98
  end
22
99
  end
23
100
  end
@@ -11,7 +11,7 @@ module Para
11
11
  # Load existing resources
12
12
  resources = object.send(attribute_name)
13
13
  # Order them if the list should be orderable
14
- resources = resources.order(:position) if orderable
14
+ resources = resources.sort_by(&:position) if orderable
15
15
 
16
16
  locals = options.fetch(:locals, {})
17
17
 
@@ -0,0 +1,48 @@
1
+ module Para
2
+ module Logging
3
+ class ActiveJobLogSubscriber < ActiveSupport::LogSubscriber
4
+ def failed(event)
5
+ fatal do
6
+ job = event.payload[:job]
7
+ buffer = []
8
+ buffer << "#{ job.class.name } (Job ID: #{ job.job_id }) failed" + args_info(job)
9
+ buffer << ''
10
+ buffer << [event.payload[:exception].class, event.payload[:exception].message].join(' - ')
11
+ buffer << ''
12
+ buffer << '-------------------'
13
+ buffer << ''
14
+ buffer += event.payload[:exception].backtrace
15
+ buffer << ''
16
+ buffer.compact.join("\n")
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def args_info(job)
23
+ if job.arguments.any?
24
+ ' with arguments: ' +
25
+ job.arguments.map { |arg| format(arg).inspect }.join(', ')
26
+ end
27
+ end
28
+
29
+ def format(arg)
30
+ case arg
31
+ when Hash
32
+ arg.transform_values { |value| format(value) }
33
+ when Array
34
+ arg.map { |value| format(value) }
35
+ when GlobalID::Identification
36
+ arg.to_global_id rescue arg
37
+ else
38
+ arg
39
+ end
40
+ end
41
+
42
+ def logger
43
+ ActiveJob::Base.logger
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,8 @@
1
+ module Para
2
+ module Logging
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :ActiveJobLogSubscriber
6
+ end
7
+ end
8
+
@@ -79,8 +79,6 @@ module Para
79
79
  field_name = nil
80
80
  end
81
81
 
82
- sort = options.delete(:sort) || field_name
83
-
84
82
  label = if Symbol === field_name
85
83
  model.human_attribute_name(field_name)
86
84
  elsif block
@@ -90,7 +88,7 @@ module Para
90
88
  end
91
89
 
92
90
  content_tag(:th, options) do
93
- if sort != field_name
91
+ if (sort = options.delete(:sort))
94
92
  view.sort_link(search, *sort, label, hide_indicator: true)
95
93
  elsif searchable?(field_name)
96
94
  view.sort_link(search, field_name, label, hide_indicator: true)
@@ -155,7 +153,7 @@ module Para
155
153
  }
156
154
 
157
155
  view.link_to(path, options) do
158
- content_tag(:i, '', class: 'fa fa-refresh')
156
+ content_tag(:i, '', class: 'fa fa-copy')
159
157
  end
160
158
  end
161
159
 
@@ -0,0 +1,19 @@
1
+ module Para
2
+ module ModelFieldParsers
3
+ class Enums < Para::ModelFieldParsers::Base
4
+ register :enums, self
5
+
6
+ def parse!
7
+ model.defined_enums.each do |key, _|
8
+ fields_hash[key] = AttributeField::EnumField.new(
9
+ model, name: key, type: 'enum'
10
+ )
11
+ end
12
+ end
13
+
14
+ def applicable?
15
+ model.respond_to?(:defined_enums) && !model.defined_enums.empty?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -38,7 +38,7 @@ module Para
38
38
  fields_hash[name] = AttributeField::HasManyField.new(
39
39
  model, name: name, type: 'has_many', field_type: 'selectize'
40
40
  )
41
- else
41
+ elsif !reflection.options[:through]
42
42
  fields_hash[name] = AttributeField::BelongsToField.new(
43
43
  model, name: name, type: 'belongs_to', field_type: 'selectize'
44
44
  )
@@ -14,7 +14,7 @@ module Para
14
14
 
15
15
  field_names.each do |field_name|
16
16
  fields_hash[field_name] = AttributeField::Base.new(
17
- model, name: field_name, type: :string
17
+ model, name: field_name, type: :string, searchable: false
18
18
  )
19
19
  end
20
20
  end
@@ -21,3 +21,4 @@ require 'para/model_field_parsers/relations'
21
21
  require 'para/model_field_parsers/redactor'
22
22
  require 'para/model_field_parsers/globalize'
23
23
  require 'para/model_field_parsers/store'
24
+ require 'para/model_field_parsers/enums'
@@ -13,10 +13,8 @@ module Para
13
13
  router = self.router
14
14
 
15
15
  router.instance_eval do
16
- namespace :admin do
17
- scope module: [:para, identifier].join('/').to_sym, as: identifier do
18
- router.instance_eval(&block)
19
- end
16
+ scope module: [:para, identifier].join('/').to_sym do
17
+ router.instance_eval(&block)
20
18
  end
21
19
  end
22
20
  end
@@ -0,0 +1,30 @@
1
+ module Para
2
+ class PostgresExtensionsChecker
3
+ class << self
4
+ def check_all
5
+ %w(hstore unaccent).each do |extname|
6
+ unless extension_exists?(extname)
7
+ # Could not use Rails.logger here, using puts as a temporary
8
+ # solution.
9
+ puts "[Warning] PostgreSQL \"#{ extname }\" extension is not " +
10
+ "installed in your database. This means that you " +
11
+ "missing some migrations that you can install " +
12
+ "with the following command : " +
13
+ "`rake para_engine:install:migrations` and then migrate."
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def extension_exists?(extname)
21
+ ActiveRecord::Base.connection.execute(
22
+ "SELECT COUNT(*) FROM pg_catalog.pg_extension " +
23
+ "WHERE extname = '#{ extname }'"
24
+ ).first['count'].to_i > 0
25
+ rescue ActiveRecord::NoDatabaseError
26
+ true # Do not issue warning when no database is installed
27
+ end
28
+ end
29
+ end
30
+ end
data/lib/para/routes.rb CHANGED
@@ -14,11 +14,12 @@ module Para
14
14
  scope module: :para do
15
15
  namespace :admin do
16
16
  get '/' => 'main#index'
17
-
18
- crud_component :crud, scope: ':model'
19
- singleton_resource_component :singleton, scope: ':model'
20
- component :settings
21
17
  end
18
+
19
+ # Components are namespaced into :admin in their respective methods
20
+ crud_component
21
+ form_component
22
+ component :settings
22
23
  end
23
24
 
24
25
  block.call if block
@@ -0,0 +1,36 @@
1
+ # Allows constraining routing to components that explicitly declares to use a
2
+ # given controller to manage their resources.
3
+ #
4
+ # It's mainly used to allow users to override the default controller 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 ComponentControllerConstraint
11
+ attr_reader :controller
12
+
13
+ def initialize(controller)
14
+ @controller = controller.to_sym
15
+ end
16
+
17
+ def matches?(request)
18
+ component = component_for(request.params[:component_id])
19
+ return false unless component
20
+ component.controller.to_sym == controller
21
+ end
22
+
23
+ private
24
+
25
+ def component_for(component_slug)
26
+ self.class.components[component_slug] ||= Para::Component::Base.find_by_slug(component_slug)
27
+ end
28
+
29
+ # Request based components cache
30
+ #
31
+ def self.components
32
+ RequestStore.store['para.components_by_id'] ||= {}
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,7 @@
1
+ module Para
2
+ module Routing
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :ComponentControllerConstraint
6
+ end
7
+ end
@@ -16,8 +16,8 @@ module Para
16
16
 
17
17
  module ClassMethods
18
18
  def eager_load!
19
- return if @eager_loaded
20
- @eager_loaded = true
19
+ return if RequestStore.store[descendants_eager_load_key]
20
+ RequestStore.store[descendants_eager_load_key] = true
21
21
 
22
22
  models_dir = Rails.root.join('app', 'models')
23
23
 
@@ -38,6 +38,12 @@ module Para
38
38
 
39
39
  private
40
40
 
41
+ def descendants_eager_load_key
42
+ @descendants_eager_load_key ||= [
43
+ 'para', 'root_model', 'descendants_eager_loaded', name
44
+ ].join(':')
45
+ end
46
+
41
47
  # Allows the including class to define `.subclasses_namespace` class
42
48
  # method to override the namespace and directory used to eager load the
43
49
  # subclasses.
data/lib/para/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Para
2
- VERSION = "0.5.4"
2
+ VERSION = '0.6.2'
3
3
  end