para 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +65 -0
- data/Rakefile +21 -0
- data/app/assets/images/para/admin/bg.png +0 -0
- data/app/assets/javascripts/admin/app.coffee +1 -0
- data/app/assets/javascripts/para/admin/table.coffee +46 -0
- data/app/assets/javascripts/para/admin/theme_actions.coffee +26 -0
- data/app/assets/javascripts/para/admin/tree.coffee +53 -0
- data/app/assets/javascripts/para/admin.coffee +18 -0
- data/app/assets/javascripts/para/application.js +13 -0
- data/app/assets/javascripts/para/inputs/nested_many.coffee +57 -0
- data/app/assets/javascripts/para/lib/ajax.coffee +11 -0
- data/app/assets/stylesheets/admin/app.sass +0 -0
- data/app/assets/stylesheets/admin/theme.sass +0 -0
- data/app/assets/stylesheets/para/admin/theme/_base.sass +170 -0
- data/app/assets/stylesheets/para/admin/theme/_breadcrumb.sass +29 -0
- data/app/assets/stylesheets/para/admin/theme/_buttons.sass +24 -0
- data/app/assets/stylesheets/para/admin/theme/_checkable.sass +107 -0
- data/app/assets/stylesheets/para/admin/theme/_commonds.sass +213 -0
- data/app/assets/stylesheets/para/admin/theme/_dropdown.sass +53 -0
- data/app/assets/stylesheets/para/admin/theme/_form.sass +141 -0
- data/app/assets/stylesheets/para/admin/theme/_list.sass +138 -0
- data/app/assets/stylesheets/para/admin/theme/_navigation.sass +287 -0
- data/app/assets/stylesheets/para/admin/theme/_navtabs.sass +127 -0
- data/app/assets/stylesheets/para/admin/theme/_orderable.sass +44 -0
- data/app/assets/stylesheets/para/admin/theme/_panel.sass +288 -0
- data/app/assets/stylesheets/para/admin/theme/_responsive.sass +123 -0
- data/app/assets/stylesheets/para/admin/theme/_sorting.sass +30 -0
- data/app/assets/stylesheets/para/admin/theme/_table.sass +24 -0
- data/app/assets/stylesheets/para/admin/theme/_tree.sass +74 -0
- data/app/assets/stylesheets/para/admin/theme/_variables.sass +31 -0
- data/app/assets/stylesheets/para/admin/theme.sass +22 -0
- data/app/assets/stylesheets/para/admin.sass +10 -0
- data/app/assets/stylesheets/para/application.css +15 -0
- data/app/assets/stylesheets/para/lib/_variables.scss +646 -0
- data/app/assets/stylesheets/para/overrides/datetimepicker.sass +17 -0
- data/app/assets/stylesheets/para/overrides/fuelux.sass +43 -0
- data/app/assets/stylesheets/para/overrides/jasny.bootstrap.sass +41 -0
- data/app/assets/stylesheets/para/overrides/redactor.sass +88 -0
- data/app/assets/stylesheets/para/overrides/responsive.sass +63 -0
- data/app/assets/stylesheets/para/overrides/selectize.sass +45 -0
- data/app/assets/stylesheets/para/overrides/slider.sass +13 -0
- data/app/assets/stylesheets/para/overrides/theme.sass +172 -0
- data/app/controllers/concerns/para/admin/resource_controller_concerns.rb +17 -0
- data/app/controllers/para/admin/base_controller.rb +46 -0
- data/app/controllers/para/admin/component_controller.rb +9 -0
- data/app/controllers/para/admin/crud_component_controller.rb +17 -0
- data/app/controllers/para/admin/crud_resources_controller.rb +71 -0
- data/app/controllers/para/admin/main_controller.rb +8 -0
- data/app/controllers/para/admin/resources_controller.rb +163 -0
- data/app/controllers/para/admin/settings_component_controller.rb +11 -0
- data/app/controllers/para/admin/settings_form_controller.rb +21 -0
- data/app/controllers/para/admin/singleton_resource_component_controller.rb +14 -0
- data/app/controllers/para/admin/singleton_resources_controller.rb +30 -0
- data/app/controllers/para/application_controller.rb +7 -0
- data/app/decorators/para/component/base_decorator.rb +48 -0
- data/app/decorators/para/component/crud_decorator.rb +32 -0
- data/app/decorators/para/component/settings_decorator.rb +11 -0
- data/app/decorators/para/component/singleton_resource_decorator.rb +11 -0
- data/app/decorators/para/component_section_decorator.rb +4 -0
- data/app/helpers/para/admin/base_helper.rb +57 -0
- data/app/helpers/para/admin/component_groups_helper.rb +4 -0
- data/app/helpers/para/admin/components_helper.rb +4 -0
- data/app/helpers/para/admin/resources_helper.rb +27 -0
- data/app/helpers/para/application_helper.rb +14 -0
- data/app/helpers/para/exports_helper.rb +11 -0
- data/app/helpers/para/flash_helper.rb +48 -0
- data/app/helpers/para/form_helper.rb +28 -0
- data/app/helpers/para/markup_helper.rb +15 -0
- data/app/helpers/para/model_helper.rb +38 -0
- data/app/helpers/para/navigation_helper.rb +8 -0
- data/app/helpers/para/ordering_helper.rb +20 -0
- data/app/helpers/para/search_helper.rb +22 -0
- data/app/helpers/para/tag_helper.rb +50 -0
- data/app/helpers/para/tree_helper.rb +41 -0
- data/app/models/para/component/base.rb +70 -0
- data/app/models/para/component/crud.rb +45 -0
- data/app/models/para/component/resource.rb +33 -0
- data/app/models/para/component/settings.rb +17 -0
- data/app/models/para/component/singleton_resource.rb +25 -0
- data/app/models/para/component_resource.rb +6 -0
- data/app/models/para/component_section.rb +18 -0
- data/app/views/layouts/para/admin.html.haml +32 -0
- data/app/views/layouts/para/application.html.erb +14 -0
- data/app/views/para/admin/component_sections/_form.html.haml +10 -0
- data/app/views/para/admin/component_sections/edit.html.haml +5 -0
- data/app/views/para/admin/component_sections/new.html.haml +5 -0
- data/app/views/para/admin/components/_form.html.haml +15 -0
- data/app/views/para/admin/components/new.html.haml +4 -0
- data/app/views/para/admin/crud_component/show.html.haml +4 -0
- data/app/views/para/admin/dashboard.html.haml +10 -0
- data/app/views/para/admin/main/index.html.haml +1 -0
- data/app/views/para/admin/resources/_exports_menu.html.haml +12 -0
- data/app/views/para/admin/resources/_fields.html.haml +5 -0
- data/app/views/para/admin/resources/_filters.html.haml +9 -0
- data/app/views/para/admin/resources/_form.html.haml +6 -0
- data/app/views/para/admin/resources/_list.html.haml +22 -0
- data/app/views/para/admin/resources/_table.html.haml +8 -0
- data/app/views/para/admin/resources/_tree.html.haml +15 -0
- data/app/views/para/admin/resources/_tree_item.html.haml +16 -0
- data/app/views/para/admin/resources/edit.html.haml +6 -0
- data/app/views/para/admin/resources/new.html.haml +6 -0
- data/app/views/para/admin/settings_component/show.html.haml +12 -0
- data/app/views/para/admin/shared/_breadcrumb.html.haml +3 -0
- data/app/views/para/admin/shared/_header.html.haml +27 -0
- data/app/views/para/admin/shared/_navigation.html.haml +21 -0
- data/app/views/para/admin/singleton_resource_component/show.html.haml +5 -0
- data/app/views/para/inputs/_nested_many.html.haml +10 -0
- data/app/views/para/inputs/_nested_many_container.html.haml +19 -0
- data/app/views/para/inputs/_nested_one.html.haml +3 -0
- data/config/locales/en.yml +86 -0
- data/config/locales/fr.yml +101 -0
- data/db/migrate/20140911091225_create_para_components.rb +19 -0
- data/db/migrate/20140911112150_add_slug_to_para_components.rb +6 -0
- data/db/migrate/20140929125733_create_para_component_sections.rb +8 -0
- data/db/migrate/20140929131111_add_identifier_to_para_component_sections.rb +5 -0
- data/db/migrate/20140930121822_add_position_to_para_component_section.rb +5 -0
- data/db/migrate/20150129170710_create_para_component_resources.rb +13 -0
- data/db/migrate/20150203173219_add_identifier_to_para_components.rb +5 -0
- data/lib/generators/para/admin_user/admin_user_generator.rb +16 -0
- data/lib/generators/para/component/component_generator.rb +59 -0
- data/lib/generators/para/component/templates/component.rb +3 -0
- data/lib/generators/para/component/templates/component_controller.rb +7 -0
- data/lib/generators/para/component/templates/show.html.haml +2 -0
- data/lib/generators/para/exporter/exporter_generator.rb +42 -0
- data/lib/generators/para/exporter/templates/base_exporter.rb +15 -0
- data/lib/generators/para/exporter/templates/csv_exporter.rb +13 -0
- data/lib/generators/para/form/form_generator.rb +15 -0
- data/lib/generators/para/form/templates/_form.html.haml +7 -0
- data/lib/generators/para/install/install_generator.rb +112 -0
- data/lib/generators/para/install/templates/components.rb +13 -0
- data/lib/generators/para/install/templates/initializer.rb +23 -0
- data/lib/generators/para/nested_fields/nested_fields_generator.rb +15 -0
- data/lib/generators/para/nested_fields/templates/_nested_fields.html.haml +3 -0
- data/lib/generators/para/orderable/orderable_generator.rb +53 -0
- data/lib/generators/para/orderable/templates/orderable_migration.rb +6 -0
- data/lib/generators/para/resource/resource_generator.rb +71 -0
- data/lib/generators/para/resource/templates/resource_controller.rb +5 -0
- data/lib/generators/para/table/table_generator.rb +34 -0
- data/lib/generators/para/table/templates/_table.html.haml +10 -0
- data/lib/para/attribute_field/base.rb +77 -0
- data/lib/para/attribute_field/belongs_to.rb +29 -0
- data/lib/para/attribute_field/boolean.rb +15 -0
- data/lib/para/attribute_field/datetime.rb +9 -0
- data/lib/para/attribute_field/file.rb +20 -0
- data/lib/para/attribute_field/has_many.rb +31 -0
- data/lib/para/attribute_field/image.rb +25 -0
- data/lib/para/attribute_field/nested_many.rb +26 -0
- data/lib/para/attribute_field/nested_one.rb +27 -0
- data/lib/para/attribute_field/password.rb +18 -0
- data/lib/para/attribute_field/redactor.rb +20 -0
- data/lib/para/attribute_field/relation.rb +52 -0
- data/lib/para/attribute_field/translation.rb +9 -0
- data/lib/para/attribute_field_mappings.rb +82 -0
- data/lib/para/cloneable.rb +20 -0
- data/lib/para/component/exportable.rb +25 -0
- data/lib/para/component.rb +27 -0
- data/lib/para/components_configuration.rb +156 -0
- data/lib/para/config.rb +21 -0
- data/lib/para/engine.rb +59 -0
- data/lib/para/errors.rb +15 -0
- data/lib/para/exporter/base.rb +27 -0
- data/lib/para/exporter/csv.rb +49 -0
- data/lib/para/exporter.rb +57 -0
- data/lib/para/ext/cancan.rb +26 -0
- data/lib/para/ext/paperclip.rb +67 -0
- data/lib/para/ext.rb +13 -0
- data/lib/para/form_builder/containers.rb +70 -0
- data/lib/para/form_builder/field_mappings.rb +9 -0
- data/lib/para/form_builder/nested_form.rb +80 -0
- data/lib/para/form_builder/ordering.rb +15 -0
- data/lib/para/form_builder/settings.rb +24 -0
- data/lib/para/form_builder.rb +22 -0
- data/lib/para/generators/field_helpers.rb +32 -0
- data/lib/para/generators/name_helpers.rb +9 -0
- data/lib/para/generators/named_base.rb +7 -0
- data/lib/para/generators.rb +11 -0
- data/lib/para/inputs/nested_many_input.rb +40 -0
- data/lib/para/inputs/nested_one_input.rb +24 -0
- data/lib/para/inputs.rb +11 -0
- data/lib/para/markup/alert.rb +37 -0
- data/lib/para/markup/component.rb +20 -0
- data/lib/para/markup/modal.rb +52 -0
- data/lib/para/markup/panel.rb +41 -0
- data/lib/para/markup/resources_table.rb +178 -0
- data/lib/para/markup.rb +10 -0
- data/lib/para/model_field_parsers/base.rb +20 -0
- data/lib/para/model_field_parsers/devise.rb +37 -0
- data/lib/para/model_field_parsers/globalize.rb +23 -0
- data/lib/para/model_field_parsers/orderable.rb +15 -0
- data/lib/para/model_field_parsers/paperclip.rb +51 -0
- data/lib/para/model_field_parsers/redactor.rb +19 -0
- data/lib/para/model_field_parsers/relations.rb +67 -0
- data/lib/para/model_field_parsers.rb +22 -0
- data/lib/para/orderable.rb +44 -0
- data/lib/para/routes.rb +49 -0
- data/lib/para/version.rb +3 -0
- data/lib/para.rb +65 -0
- data/lib/rails/relation_length_validator.rb +45 -0
- data/lib/rails/routing_mapper.rb +27 -0
- data/lib/tasks/para_tasks.rake +22 -0
- metadata +595 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module Para
|
2
|
+
module Component
|
3
|
+
def self.registered_components
|
4
|
+
@registered_components ||= {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.registered_component?(model_name)
|
8
|
+
registered_components.any? do |name, component|
|
9
|
+
component.name == model_name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.config
|
14
|
+
@config ||= Para::ComponentsConfiguration.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Require concerns
|
20
|
+
require 'para/component/exportable'
|
21
|
+
|
22
|
+
# Require models
|
23
|
+
require 'para/component/base'
|
24
|
+
require 'para/component/resource'
|
25
|
+
require 'para/component/crud'
|
26
|
+
require 'para/component/singleton_resource'
|
27
|
+
require 'para/component/settings'
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Para
|
2
|
+
class ComponentsConfiguration
|
3
|
+
def draw(&block)
|
4
|
+
return unless components_installed?
|
5
|
+
eager_load_components!
|
6
|
+
instance_eval(&block)
|
7
|
+
build
|
8
|
+
end
|
9
|
+
|
10
|
+
def section(*args, &block)
|
11
|
+
sections << Section.new(*args, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def sections
|
15
|
+
@sections ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *args, &block)
|
19
|
+
component_for(method) || super
|
20
|
+
end
|
21
|
+
|
22
|
+
def section_for(identifier)
|
23
|
+
if (section = sections_cache[identifier])
|
24
|
+
section
|
25
|
+
elsif (section_id = sections_ids_hash[identifier])
|
26
|
+
sections_cache[identifier] = Para::ComponentSection.find(section_id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def component_for(identifier)
|
31
|
+
if (component = components_cache[identifier])
|
32
|
+
component
|
33
|
+
elsif (component_id = components_ids_hash[identifier])
|
34
|
+
components_cache[identifier] = Para::Component::Base.find(component_id)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build
|
41
|
+
sections.each_with_index do |section, index|
|
42
|
+
section.refresh(position: index)
|
43
|
+
sections_ids_hash[section.identifier] = section.model.id
|
44
|
+
|
45
|
+
section.components.each do |component|
|
46
|
+
components_ids_hash[component.identifier] = component.model.id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def sections_ids_hash
|
52
|
+
@sections_ids_hash ||= {}.with_indifferent_access
|
53
|
+
end
|
54
|
+
|
55
|
+
def components_ids_hash
|
56
|
+
@components_ids_hash ||= {}.with_indifferent_access
|
57
|
+
end
|
58
|
+
|
59
|
+
# Only store sections cache for the request duration to avoid expired
|
60
|
+
# references to AR objects between requests
|
61
|
+
#
|
62
|
+
def sections_cache
|
63
|
+
RequestStore.store[:sections_cache] ||= {}.with_indifferent_access
|
64
|
+
end
|
65
|
+
|
66
|
+
# Only store components cache for the request duration to avoid expired
|
67
|
+
# references to AR objects between requests
|
68
|
+
#
|
69
|
+
def components_cache
|
70
|
+
RequestStore.store[:components_cache] ||= {}.with_indifferent_access
|
71
|
+
end
|
72
|
+
|
73
|
+
def components_installed?
|
74
|
+
tables_exist = %w(component/base component_section).all? do |name|
|
75
|
+
Para.const_get(name.camelize).table_exists?
|
76
|
+
end
|
77
|
+
|
78
|
+
unless tables_exist
|
79
|
+
Rails.logger.warn(
|
80
|
+
"Para migrations are not installed.\n" \
|
81
|
+
"Skipping components definition until next app reload."
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
tables_exist
|
86
|
+
end
|
87
|
+
|
88
|
+
def eager_load_components!
|
89
|
+
$LOAD_PATH.each do |path|
|
90
|
+
next unless path.match(/\/components$/)
|
91
|
+
|
92
|
+
glob = File.join(path, '**', '*_component.rb')
|
93
|
+
|
94
|
+
Dir[glob].each do |file|
|
95
|
+
load(file)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Section
|
101
|
+
attr_accessor :identifier, :model
|
102
|
+
|
103
|
+
def initialize(identifier, &block)
|
104
|
+
self.identifier = identifier.to_s
|
105
|
+
instance_eval(&block)
|
106
|
+
end
|
107
|
+
|
108
|
+
def component(*args)
|
109
|
+
components << Component.new(*args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def components
|
113
|
+
@components ||= []
|
114
|
+
end
|
115
|
+
|
116
|
+
def refresh(attributes = {})
|
117
|
+
self.model = ComponentSection.where(identifier: identifier).first_or_initialize
|
118
|
+
model.assign_attributes(attributes)
|
119
|
+
model.save!
|
120
|
+
|
121
|
+
components.each_with_index do |component, index|
|
122
|
+
component.refresh(component_section: model, position: index)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class Component
|
128
|
+
attr_accessor :identifier, :type, :options, :model
|
129
|
+
|
130
|
+
def initialize(identifier, type, options = {})
|
131
|
+
self.identifier = identifier.to_s
|
132
|
+
self.type = Para::Component.registered_components[type]
|
133
|
+
self.options = options
|
134
|
+
end
|
135
|
+
|
136
|
+
def refresh(attributes = {})
|
137
|
+
self.model = type.where(identifier: identifier).first_or_initialize
|
138
|
+
model.assign_attributes(attributes.merge(options_with_defaults))
|
139
|
+
model.save!
|
140
|
+
end
|
141
|
+
|
142
|
+
# Ensures unset :configuration store options are set to nil to allow
|
143
|
+
# removing a configuration option from the components.rb file
|
144
|
+
#
|
145
|
+
def options_with_defaults
|
146
|
+
configurable_keys = type.local_stored_attributes.try(:[], :configuration) || []
|
147
|
+
configurable_keys += options.keys
|
148
|
+
configurable_keys.uniq!
|
149
|
+
|
150
|
+
configurable_keys.each_with_object({}) do |key, hash|
|
151
|
+
hash[key] = options[key]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/lib/para/config.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Para
|
2
|
+
module Config
|
3
|
+
mattr_accessor :authenticate_admin_method
|
4
|
+
@@authenticate_admin_method = :authenticate_admin_user!
|
5
|
+
|
6
|
+
mattr_accessor :current_admin_method
|
7
|
+
@@current_admin_method = :current_admin_user
|
8
|
+
|
9
|
+
mattr_accessor :pagination_theme
|
10
|
+
@@pagination_theme = 'twitter-bootstrap-3'
|
11
|
+
|
12
|
+
mattr_accessor :admin_title
|
13
|
+
@@admin_title = nil
|
14
|
+
|
15
|
+
mattr_accessor :default_tree_max_depth
|
16
|
+
@@default_tree_max_depth = 3
|
17
|
+
|
18
|
+
mattr_accessor :resource_name_methods
|
19
|
+
@@resource_name_methods = [:name, :title]
|
20
|
+
end
|
21
|
+
end
|
data/lib/para/engine.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Para
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
initializer 'add vendor path to assets pipeline' do |app|
|
4
|
+
%w(javascripts stylesheets).each do |folder|
|
5
|
+
app.config.assets.paths << File.expand_path(
|
6
|
+
"../../../vendor/assets/#{ folder }",
|
7
|
+
__FILE__
|
8
|
+
)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer 'Para precompile hook', group: :all do |app|
|
13
|
+
app.config.assets.precompile += %w(
|
14
|
+
para/admin.js
|
15
|
+
para/admin.css
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
initializer 'Para Orderable Mixin' do
|
20
|
+
ActiveSupport.on_load(:active_record) do
|
21
|
+
include Para::ActiveRecordOrderableMixin
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
initializer 'Para Cloneable' do
|
26
|
+
ActiveSupport.on_load(:active_record) do
|
27
|
+
include Para::Cloneable
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
initializer 'Extend paperclip attachment definition' do
|
32
|
+
return unless Kernel.const_defined?('Paperclip')
|
33
|
+
|
34
|
+
ActiveSupport.on_load(:active_record) do
|
35
|
+
::Paperclip::HasAttachedFile.send(
|
36
|
+
:include, Para::Ext::Paperclip::HasAttachedFileMixin
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
initializer 'Extend cancan ControllerResource class' do
|
42
|
+
return unless Kernel.const_defined?('CanCan')
|
43
|
+
|
44
|
+
ActiveSupport.on_load(:active_record) do
|
45
|
+
::CanCan::ControllerResource.send(
|
46
|
+
:include, Para::Ext::Cancan::ControllerResource
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
initializer 'Build components tree' do |app|
|
52
|
+
components_config_path = Rails.root.join('config', 'components.rb')
|
53
|
+
|
54
|
+
app.config.to_prepare do
|
55
|
+
require components_config_path if File.exist?(components_config_path)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/para/errors.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Para
|
2
|
+
class BaseException < StandardError; end
|
3
|
+
|
4
|
+
class ComponentNotFound < BaseException
|
5
|
+
attr_accessor :type
|
6
|
+
|
7
|
+
def initialize(type)
|
8
|
+
@type = type
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
"Component not found for type : #{ type }"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Para
|
2
|
+
module Exporter
|
3
|
+
class Base
|
4
|
+
attr_reader :resources
|
5
|
+
|
6
|
+
def initialize(resources)
|
7
|
+
@resources = resources
|
8
|
+
end
|
9
|
+
|
10
|
+
def disposition
|
11
|
+
'inline'
|
12
|
+
end
|
13
|
+
|
14
|
+
def extension
|
15
|
+
raise '#extension must be defined to create the export file name'
|
16
|
+
end
|
17
|
+
|
18
|
+
def file_name
|
19
|
+
@file_name ||= [name, extension].join('.')
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.register_base_exporter(type, exporter)
|
23
|
+
Exporter.base_exporters[type] = exporter
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Para
|
4
|
+
module Exporter
|
5
|
+
class Csv < Base
|
6
|
+
register_base_exporter :csv, self
|
7
|
+
|
8
|
+
def extension
|
9
|
+
'csv'
|
10
|
+
end
|
11
|
+
|
12
|
+
def mime_type
|
13
|
+
'text/csv'
|
14
|
+
end
|
15
|
+
|
16
|
+
def export_type
|
17
|
+
:excel
|
18
|
+
end
|
19
|
+
|
20
|
+
def render
|
21
|
+
CSV.generate do |csv|
|
22
|
+
csv << headers
|
23
|
+
|
24
|
+
resources.each do |resource|
|
25
|
+
csv << row_for(resource)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def headers
|
33
|
+
fields.map do |field|
|
34
|
+
encode(User.human_attribute_name(field))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def row_for(resource)
|
39
|
+
fields.map do |field|
|
40
|
+
encode(resource.send(field))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def encode(string)
|
45
|
+
string.presence && string.to_s.encode('UTF-8')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Para
|
2
|
+
module Exporter
|
3
|
+
class MissingExporterError < StandardError
|
4
|
+
attr_accessor :model_name, :format, :exporter_name
|
5
|
+
|
6
|
+
def initialize(model_name, format, exporter_name)
|
7
|
+
@model_name = model_name
|
8
|
+
@format = format
|
9
|
+
@exporter_name = exporter_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
"No exporter found for model \"#{ model_name }\" and format " +
|
14
|
+
"\"#{ format }\". Please create the #{ exporter_name } class " +
|
15
|
+
"manually or with the following command : " +
|
16
|
+
"`rails g para:exporter #{ model_name.underscore } #{ format }"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.for(model_name, format)
|
21
|
+
exporter_name = name_for(model_name, format)
|
22
|
+
|
23
|
+
begin
|
24
|
+
const_get(exporter_name)
|
25
|
+
rescue NameError => e
|
26
|
+
if e.message == "uninitialized constant Para::Exporter::#{ exporter_name }"
|
27
|
+
raise MissingExporterError.new(model_name, format, exporter_name)
|
28
|
+
else
|
29
|
+
raise e
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.name_for(model_name, format)
|
35
|
+
[
|
36
|
+
'',
|
37
|
+
format_exporter_name(format),
|
38
|
+
model_exporter_name(model_name)
|
39
|
+
].join('::')
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.model_exporter_name(model_name)
|
43
|
+
[model_name.to_s.pluralize, 'Exporter'].join
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.format_exporter_name(format)
|
47
|
+
format.to_s.camelize
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.base_exporters
|
51
|
+
@base_exporters ||= {}.with_indifferent_access
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
require 'para/exporter/base'
|
57
|
+
require 'para/exporter/csv'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Para
|
2
|
+
module Ext
|
3
|
+
module Cancan
|
4
|
+
module ControllerResource
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
alias_method_chain :assign_attributes, :parent_missing_management
|
9
|
+
end
|
10
|
+
|
11
|
+
# Todo : Document why this extension was added ?
|
12
|
+
def assign_attributes_with_parent_missing_management(resource)
|
13
|
+
if @options[:singleton] && parent_resource && resource.respond_to?(:"#{ parent_name }=")
|
14
|
+
resource.send(:"#{ parent_name }=", parent_resource)
|
15
|
+
end
|
16
|
+
|
17
|
+
initial_attributes.each do |attr_name, value|
|
18
|
+
resource.send(:"#{ attr_name }=", value)
|
19
|
+
end
|
20
|
+
|
21
|
+
resource
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Para
|
2
|
+
module Ext
|
3
|
+
module Paperclip
|
4
|
+
module HasAttachedFileMixin
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
alias_method_chain :define, :removeable_management
|
9
|
+
end
|
10
|
+
|
11
|
+
def define_with_removeable_management
|
12
|
+
define_without_removeable_management
|
13
|
+
define_removeable
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def define_removeable
|
19
|
+
return if @options[:removeable] == false
|
20
|
+
|
21
|
+
attachment_name = @name
|
22
|
+
klass = @klass
|
23
|
+
# Define the setter to remove the attachment
|
24
|
+
#
|
25
|
+
@klass.send :define_method, :"remove_#{ attachment_name }=" do |value|
|
26
|
+
if value == '1'
|
27
|
+
removed_attachments << attachment_name
|
28
|
+
|
29
|
+
# Notify ActiveRecord that the model has changed so nested models
|
30
|
+
# get to run validation hooks and attachments are cleared
|
31
|
+
send(:"#{ attachment_name }_file_name_will_change!")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@klass.send :define_method, :"remove_#{ attachment_name }" do |value|
|
36
|
+
removed_attachments.include?(attachment_name) ? '1' : nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Lazy method initialization on attachment target class
|
40
|
+
#
|
41
|
+
unless @klass.method_defined?(:removed_attachments)
|
42
|
+
# List of all removed attachments for the current instance
|
43
|
+
#
|
44
|
+
@klass.send :define_method, :removed_attachments do
|
45
|
+
@removed_attachments ||= []
|
46
|
+
end
|
47
|
+
|
48
|
+
# Define before validation hook to clear removed attachments before
|
49
|
+
# the instance is validated
|
50
|
+
#
|
51
|
+
@klass.send :define_method, :clear_removed_attachments do
|
52
|
+
removed_attachments.each do |name|
|
53
|
+
if send(:"#{ name }?")
|
54
|
+
send(name).clear
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
removed_attachments.clear
|
59
|
+
end
|
60
|
+
|
61
|
+
@klass.send(:before_validation, :clear_removed_attachments)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/para/ext.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Para
|
2
|
+
# References all framework extensions to make our code work well with other
|
3
|
+
# libraries.
|
4
|
+
#
|
5
|
+
# One goal is to have the minimum of them, but sometimes, without them, we
|
6
|
+
# may need to make our own code dirty
|
7
|
+
#
|
8
|
+
module Ext
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'para/ext/cancan'
|
13
|
+
require 'para/ext/paperclip'
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Para
|
2
|
+
module FormBuilder
|
3
|
+
module Containers
|
4
|
+
def fieldset(&block)
|
5
|
+
template.content_tag(:div, class: 'block form-inputs') do
|
6
|
+
template.capture(&block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def actions(options = {}, &block)
|
11
|
+
template.content_tag(:div, class: 'block form-actions form-group') do
|
12
|
+
template.content_tag(:div, class: 'col-sm-9 col-sm-offset-3') do
|
13
|
+
if block
|
14
|
+
template.capture(&block)
|
15
|
+
else
|
16
|
+
if options.empty?
|
17
|
+
options[:only] = template.instance_variable_get(:@component).default_form_actions
|
18
|
+
end
|
19
|
+
actions_buttons_for(options).join("\n").html_safe
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def actions_buttons_for(options)
|
26
|
+
names = [:submit, :submit_and_edit, :submit_and_add_another, :cancel]
|
27
|
+
|
28
|
+
names.select! { |name| Array.wrap(options[:only]).include?(name) } if options[:only]
|
29
|
+
names.reject! { |name| Array.wrap(options[:except]).include?(name) } if options[:except]
|
30
|
+
buttons = names.map { |name| send(:"para_#{ name }_button") }
|
31
|
+
buttons.unshift(return_to_hidden_field)
|
32
|
+
buttons
|
33
|
+
end
|
34
|
+
|
35
|
+
def return_to_hidden_field
|
36
|
+
template.hidden_field_tag(:return_to, return_to_path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def para_submit_button(options = {})
|
40
|
+
button(:submit, I18n.t('para.shared.save'), name: '_save', class: 'btn-success')
|
41
|
+
end
|
42
|
+
|
43
|
+
def para_submit_and_edit_button
|
44
|
+
button(:submit, I18n.t('para.shared.save_and_edit'), name: '_save_and_edit', class: 'btn-primary')
|
45
|
+
end
|
46
|
+
|
47
|
+
def para_submit_and_add_another_button
|
48
|
+
button(:submit, I18n.t('para.shared.save_and_add_another_button'), name: '_save_and_add_another', class: 'btn-primary')
|
49
|
+
end
|
50
|
+
|
51
|
+
def para_cancel_button
|
52
|
+
template.link_to(
|
53
|
+
I18n.t('para.shared.cancel'),
|
54
|
+
return_to_path,
|
55
|
+
class: 'btn btn-danger'
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def return_to_path
|
60
|
+
template.params[:return_to].presence || component_path
|
61
|
+
end
|
62
|
+
|
63
|
+
def component_path
|
64
|
+
if (component = template.instance_variable_get(:@component))
|
65
|
+
component.path
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Para
|
2
|
+
module FormBuilder
|
3
|
+
module NestedForm
|
4
|
+
def nested_fields
|
5
|
+
@nested_fields ||= fields.reject do |field|
|
6
|
+
inverse_of?(field.name)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def nested_resource_name
|
11
|
+
@nested_resource_name ||= begin
|
12
|
+
name_method = Para.config.resource_name_methods.find do |method_name|
|
13
|
+
object.respond_to?(method_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
(name_method && object.try(name_method).presence) || default_resource_name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def nested_resource_dom_id
|
21
|
+
return "" unless nested?
|
22
|
+
|
23
|
+
@nested_resource_dom_id ||= [
|
24
|
+
object.class.model_name.singular,
|
25
|
+
(Time.now.to_f * 1000).to_i,
|
26
|
+
(object.id || "_new_#{ nested_attribute_name }_id")
|
27
|
+
].join('-')
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_association_button
|
31
|
+
return "" unless allow_destroy?
|
32
|
+
|
33
|
+
template.content_tag(:div, class: 'panel-btns pull-right') do
|
34
|
+
template.link_to_remove_association(
|
35
|
+
self, wrapper_class: 'form-fields', class: 'btn btn-danger'
|
36
|
+
) do
|
37
|
+
template.content_tag(:i, '', class: 'fa fa-trash') +
|
38
|
+
I18n.t('para.form.nested.remove')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def allow_destroy?
|
44
|
+
return false unless nested?
|
45
|
+
|
46
|
+
nested_options = parent_object.nested_attributes_options
|
47
|
+
relation = nested_options[nested_attribute_name]
|
48
|
+
relation && relation[:allow_destroy]
|
49
|
+
end
|
50
|
+
|
51
|
+
def inverse_of?(field_name)
|
52
|
+
return false unless nested?
|
53
|
+
|
54
|
+
reflection = parent_object.class.reflect_on_association(nested_attribute_name)
|
55
|
+
reflection && (reflection.options[:inverse_of] == field_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def nested?
|
59
|
+
nested_attribute_name.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
def nested_attribute_name
|
63
|
+
options[:nested_attribute_name]
|
64
|
+
end
|
65
|
+
|
66
|
+
def parent_object
|
67
|
+
nested? && options[:parent_builder] && options[:parent_builder].object
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def default_resource_name
|
73
|
+
model_name = object.class.model_name.human
|
74
|
+
id_or_new = (id = object.id) ? id : I18n.t('para.form.nested.new')
|
75
|
+
|
76
|
+
[model_name, id_or_new].join(' - ')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Para
|
2
|
+
module FormBuilder
|
3
|
+
module Ordering
|
4
|
+
def orderable?
|
5
|
+
options.fetch(:orderable, object.class.orderable?)
|
6
|
+
end
|
7
|
+
|
8
|
+
def reorder_anchor(options = {})
|
9
|
+
return "" unless orderable?
|
10
|
+
options[:form] = self
|
11
|
+
template.reorder_anchor(options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|