tramway 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (180) hide show
  1. checksums.yaml +5 -5
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +889 -15
  4. data/Rakefile +8 -8
  5. data/app/assets/config/tramway_core_manifest.js +2 -0
  6. data/app/assets/images/tramway/mona_lisa_from_prado.jpg +0 -0
  7. data/app/assets/images/tramway/mona_lisa_from_prado_square.jpg +0 -0
  8. data/app/assets/javascripts/bootstrap-datepicker-1.8.0.js +2035 -0
  9. data/app/assets/javascripts/bootstrap-datepicker-1.8.0.ru.min.js +64 -0
  10. data/app/assets/javascripts/ckeditor/config.js +30 -0
  11. data/app/assets/javascripts/ckeditor/plugins/image/dialogs/image.js +1259 -0
  12. data/app/assets/javascripts/ckeditor/plugins/image/icons/hidpi/image.png +0 -0
  13. data/app/assets/javascripts/ckeditor/plugins/image/icons/image.png +0 -0
  14. data/app/assets/javascripts/ckeditor/plugins/image/images/noimage.png +0 -0
  15. data/app/assets/javascripts/ckeditor/plugins/image/lang/en.js +25 -0
  16. data/app/assets/javascripts/ckeditor/plugins/image/lang/ru.js +25 -0
  17. data/app/assets/javascripts/ckeditor/plugins/image/plugin.js +184 -0
  18. data/app/assets/javascripts/ckeditor/plugins/youtube/images/icon-hdpi.png +0 -0
  19. data/app/assets/javascripts/ckeditor/plugins/youtube/images/icon.png +0 -0
  20. data/app/assets/javascripts/ckeditor/plugins/youtube/lang/en.js +25 -0
  21. data/app/assets/javascripts/ckeditor/plugins/youtube/lang/ru.js +25 -0
  22. data/app/assets/javascripts/ckeditor/plugins/youtube/plugin.js +449 -0
  23. data/app/assets/javascripts/tramway/application.js +60 -0
  24. data/app/assets/stylesheets/tramway/application.sass +60 -0
  25. data/app/assets/stylesheets/tramway/bootstrap-datepicker-1.8.0.css +477 -0
  26. data/app/controllers/concerns/auth_management.rb +26 -0
  27. data/app/controllers/concerns/filtering.rb +43 -0
  28. data/app/controllers/tramway/application_controller.rb +142 -0
  29. data/app/controllers/tramway/export/application_controller.rb +5 -0
  30. data/app/controllers/tramway/has_and_belongs_to_many_records_controller.rb +26 -0
  31. data/app/controllers/tramway/records_controller.rb +86 -0
  32. data/app/controllers/tramway/sessions_controller.rb +49 -0
  33. data/app/controllers/tramway/singletons_controller.rb +48 -0
  34. data/app/controllers/tramway/welcome_controller.rb +17 -0
  35. data/app/decorators/tramway/application_decorated_collection.rb +15 -0
  36. data/app/decorators/tramway/application_decorator.rb +124 -0
  37. data/app/decorators/tramway/associations/class_helper.rb +45 -0
  38. data/app/decorators/tramway/associations/object_helper.rb +58 -0
  39. data/app/decorators/tramway/attributes/view_helper.rb +30 -0
  40. data/app/decorators/tramway/concerns/attributes_decorator_helper.rb +97 -0
  41. data/app/decorators/tramway/concerns/table_builder.rb +33 -0
  42. data/app/decorators/tramway/default/values_helper.rb +23 -0
  43. data/app/decorators/tramway/delegating/class_helper.rb +9 -0
  44. data/app/decorators/tramway/user_decorator.rb +49 -0
  45. data/app/forms/admin/tramway/user_form.rb +24 -0
  46. data/app/forms/tramway/application_form.rb +124 -0
  47. data/app/forms/tramway/application_forms/association_class_helpers.rb +7 -0
  48. data/app/forms/tramway/application_forms/association_object_helpers.rb +36 -0
  49. data/app/forms/tramway/application_forms/constant_class_actions.rb +7 -0
  50. data/app/forms/tramway/application_forms/constant_object_actions.rb +20 -0
  51. data/app/forms/tramway/application_forms/frontend.rb +12 -0
  52. data/app/forms/tramway/application_forms/object_helpers.rb +15 -0
  53. data/app/forms/tramway/application_forms/properties_object_helper.rb +16 -0
  54. data/app/forms/tramway/application_forms/submit_helper.rb +26 -0
  55. data/app/forms/tramway/extendable_form.rb +15 -0
  56. data/app/forms/tramway/extendable_forms_helpers/class_builder.rb +34 -0
  57. data/app/forms/tramway/extendable_forms_helpers/ignored_properties_helper.rb +11 -0
  58. data/app/forms/tramway/extendable_forms_helpers/more_properties_helper.rb +31 -0
  59. data/app/forms/tramway/extendable_forms_helpers/properties_helper.rb +16 -0
  60. data/app/forms/tramway/extendable_forms_helpers/submit/class_helpers.rb +18 -0
  61. data/app/forms/tramway/extendable_forms_helpers/submit/object_helpers.rb +21 -0
  62. data/app/forms/tramway/extendable_forms_helpers/validators.rb +40 -0
  63. data/app/forms/tramway/extended_application_form.rb +23 -0
  64. data/app/forms/tramway/form_creator.rb +7 -0
  65. data/app/forms/tramway/session_form.rb +26 -0
  66. data/app/helpers/tramway/actions_helper.rb +45 -0
  67. data/app/helpers/tramway/additional_buttons_builder.rb +12 -0
  68. data/app/helpers/tramway/application_helper.rb +27 -0
  69. data/app/helpers/tramway/cases_helper.rb +13 -0
  70. data/app/helpers/tramway/copy_to_clipboard_helper.rb +11 -0
  71. data/app/helpers/tramway/focus_generator_helper.rb +10 -0
  72. data/app/helpers/tramway/frontend_helper.rb +26 -0
  73. data/app/helpers/tramway/inputs/associations_helper.rb +30 -0
  74. data/app/helpers/tramway/inputs/polymorphic_associations_helper.rb +24 -0
  75. data/app/helpers/tramway/inputs_helper.rb +96 -0
  76. data/app/helpers/tramway/model_helper.rb +7 -0
  77. data/app/helpers/tramway/navbar_helper.rb +11 -0
  78. data/app/helpers/tramway/records_helper.rb +120 -0
  79. data/app/helpers/tramway/russian_cases_helper.rb +26 -0
  80. data/app/helpers/tramway/singleton_helper.rb +12 -0
  81. data/app/helpers/tramway/state_machine_buttons_helper.rb +61 -0
  82. data/app/helpers/tramway/title_helper.rb +25 -0
  83. data/app/inputs/date_picker_input.rb +4 -0
  84. data/app/inputs/multiple_file_input.rb +7 -0
  85. data/app/models/tramway/application_record.rb +60 -0
  86. data/app/models/tramway/user.rb +23 -0
  87. data/app/uploaders/application_uploader.rb +22 -0
  88. data/app/uploaders/file_uploader.rb +4 -0
  89. data/app/uploaders/ico_uploader.rb +7 -0
  90. data/app/uploaders/image_defaults.rb +14 -0
  91. data/app/uploaders/photo_uploader.rb +54 -0
  92. data/app/views/layouts/tramway/application.html.haml +32 -0
  93. data/app/views/layouts/tramway/shared/_navbar.html.haml +46 -0
  94. data/app/views/tramway/404.haml +1 -0
  95. data/app/views/tramway/records/_form.html.haml +22 -0
  96. data/app/views/tramway/records/_list.html.haml +38 -0
  97. data/app/views/tramway/records/_search.html.haml +34 -0
  98. data/app/views/tramway/records/edit.html.haml +1 -0
  99. data/app/views/tramway/records/index.html.haml +31 -0
  100. data/app/views/tramway/records/new.html.haml +1 -0
  101. data/app/views/tramway/records/show.html.haml +1 -0
  102. data/app/views/tramway/sessions/new.html.haml +9 -0
  103. data/app/views/tramway/shared/_input.html.haml +34 -0
  104. data/app/views/tramway/shared/_input_extended.html.haml +14 -0
  105. data/app/views/tramway/shared/_messages.html.haml +10 -0
  106. data/app/views/tramway/shared/_show.html.haml +35 -0
  107. data/app/views/tramway/shared/errors/server_error.html.haml +12 -0
  108. data/app/views/tramway/shared/input_extended_types/_checkbox.html.haml +1 -0
  109. data/app/views/tramway/shared/input_extended_types/_select.html.haml +16 -0
  110. data/app/views/tramway/shared/input_extended_types/_simple.html.haml +4 -0
  111. data/app/views/tramway/shared/input_extended_types/_yes_no.html.haml +4 -0
  112. data/app/views/tramway/shared/show/_attribute_tr.html.haml +9 -0
  113. data/app/views/tramway/shared/show/associations/_habtm_row.html.haml +14 -0
  114. data/app/views/tramway/shared/show/associations/_list.html.haml +21 -0
  115. data/app/views/tramway/shared/show/associations/_row.html.haml +19 -0
  116. data/app/views/tramway/shared/show/associations/_table_row.html.haml +21 -0
  117. data/app/views/tramway/singletons/_form.html.haml +15 -0
  118. data/app/views/tramway/singletons/edit.html.haml +1 -0
  119. data/app/views/tramway/singletons/new.html.haml +1 -0
  120. data/app/views/tramway/singletons/show.html.haml +1 -0
  121. data/app/views/tramway/welcome/index.html.haml +4 -0
  122. data/config/initializers/assets.rb +7 -0
  123. data/config/initializers/carrierwave.rb +5 -0
  124. data/config/initializers/ckeditor.rb +9 -0
  125. data/config/initializers/plurals.rb +25 -0
  126. data/config/locales/en/collections.yml +4 -0
  127. data/config/locales/en/date.yml +16 -0
  128. data/config/locales/en/dates.yml +10 -0
  129. data/config/locales/en/helpers.yml +26 -0
  130. data/config/locales/en/locale.yml +11 -0
  131. data/config/locales/en/messages.yml +7 -0
  132. data/config/locales/en/models.yml +9 -0
  133. data/config/locales/en/simple_form_extension.yml +8 -0
  134. data/config/locales/en/state_machines.yml +8 -0
  135. data/config/locales/ru/collections.yml +4 -0
  136. data/config/locales/ru/date.yml +16 -0
  137. data/config/locales/ru/dates.yml +10 -0
  138. data/config/locales/ru/helpers.yml +30 -0
  139. data/config/locales/ru/locale.yml +6 -0
  140. data/config/locales/ru/messages.yml +7 -0
  141. data/config/locales/ru/models.yml +21 -0
  142. data/config/locales/ru/simple_form_extension.yml +8 -0
  143. data/config/locales/ru/state_machines.yml +8 -0
  144. data/config/routes.rb +13 -0
  145. data/lib/string.rb +18 -0
  146. data/lib/tramway/application.rb +10 -0
  147. data/lib/tramway/class_name_helpers.rb +15 -0
  148. data/lib/tramway/collection.rb +9 -0
  149. data/lib/tramway/collections/helper.rb +21 -0
  150. data/lib/tramway/collections.rb +4 -0
  151. data/lib/tramway/engine.rb +14 -0
  152. data/lib/tramway/error.rb +32 -0
  153. data/lib/tramway/forms.rb +5 -0
  154. data/lib/tramway/generators/install_generator.rb +49 -0
  155. data/lib/tramway/generators/model_generator.rb +105 -0
  156. data/lib/tramway/generators/templates/create_tramway_users.rb +18 -0
  157. data/lib/tramway/generators/templates/decorator.rb.erb +56 -0
  158. data/lib/tramway/generators/templates/form.rb.erb +22 -0
  159. data/lib/tramway/generators/templates/initializers/simple_form.rb +23 -0
  160. data/lib/tramway/generators/templates/initializers/simple_form_bootstrap.rb +133 -0
  161. data/lib/tramway/generators.rb +4 -0
  162. data/lib/tramway/navbar.rb +44 -0
  163. data/lib/tramway/notifications.rb +12 -0
  164. data/lib/tramway/record_routes_helper.rb +23 -0
  165. data/lib/tramway/records_models.rb +52 -0
  166. data/lib/tramway/singleton_models.rb +32 -0
  167. data/lib/tramway/spec/helpers/navbar_helper.rb +7 -0
  168. data/lib/tramway/spec/helpers/tramway_helpers.rb +47 -0
  169. data/lib/tramway/version.rb +3 -1
  170. data/lib/tramway/welcome_page_actions.rb +5 -0
  171. data/lib/tramway/yaml/errors.yml +49 -0
  172. data/lib/tramway.rb +143 -2
  173. data/lib/validators/presence_validator.rb +9 -0
  174. metadata +582 -25
  175. data/.gitignore +0 -9
  176. data/.travis.yml +0 -5
  177. data/Gemfile +0 -6
  178. data/bin/console +0 -14
  179. data/bin/setup +0 -8
  180. data/tramway.gemspec +0 -26
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::ApplicationHelper
4
+ include FontAwesome5::Rails::IconHelper
5
+ include SmartButtons
6
+ include Tramway::AuthManagement
7
+
8
+ helpers = Dir["#{Tramway.root}/app/helpers/tramway/*_helper.rb"]
9
+ helpers.each do |helper|
10
+ module_name = helper.split('/')[-2..].join('/')[0..-4].camelize.constantize
11
+
12
+ include module_name unless Tramway::ApplicationHelper
13
+ end
14
+
15
+ def object_type(object)
16
+ object_class_name = if object.class.ancestors.include? ::Tramway::ApplicationDecorator
17
+ object.class.model_class.name
18
+ else
19
+ object.class.name
20
+ end
21
+ if Tramway.available_models_for(@application_engine || @application.name).map(&:to_s).include?(object_class_name)
22
+ :record
23
+ else
24
+ :singleton
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::CasesHelper
4
+ def plural(word)
5
+ if I18n.locale == :ru
6
+ russian_plural word
7
+ elsif word.respond_to?(:model_name)
8
+ word.model_name.human.pluralize(I18n.locale)
9
+ else
10
+ word.human.pluralize(I18n.locale)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::CopyToClipboardHelper
4
+ def copy_to_clipboard(id)
5
+ button_tag class: 'btn btn-info clipboard-btn',
6
+ data: { clipboard_action: 'copy', clipboard_target: "##{id}" },
7
+ style: 'margin-left: 15px' do
8
+ fa_icon 'copy'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ::Tramway
4
+ module FocusGeneratorHelper
5
+ # FIXME: create independent focus generator
6
+ def focus_selector(index)
7
+ "table:nth-child(2)>tbody>tr:nth-child(#{index - 1})"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::FrontendHelper
4
+ def react_params(form, action, method)
5
+ form.properties.each_with_object({ action: react_params_url(form, action), method: method,
6
+ authenticity_token: form_authenticity_token }) do |property, hash|
7
+ case property[1]
8
+ when :association
9
+ hash.merge!(
10
+ property[0] => {
11
+ collection: build_collection_for_association(form, property[0])
12
+ }
13
+ )
14
+ end
15
+ end.merge model: form.model.attributes
16
+ end
17
+
18
+ def react_params_url(form, action)
19
+ case action
20
+ when :create
21
+ Tramway::Engine.routes.url_helpers.records_path(model: form.model.class)
22
+ else
23
+ Tramway::Engine.routes.url_helpers.record_path(form.model.id, model: form.model.class)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::Inputs::AssociationsHelper
4
+ def build_collection_for_association(form_object, property)
5
+ full_class_name_association = form_object.class.full_class_name_association(property)
6
+ check_valid_association full_class_name_association
7
+ full_class_name_association.send("#{current_user.role}_scope", current_user.id).map do |obj|
8
+ decorator_class(full_class_name_association).decorate obj
9
+ end.sort_by do |association|
10
+ association.name || "#{association.class.name} ##{association.id}"
11
+ end
12
+ end
13
+
14
+ def build_value_for_association(form_object, property, value)
15
+ form_object.send(property) || form_object.model.send("#{property}_id") || value
16
+ end
17
+
18
+ def check_valid_association(full_class_name_association)
19
+ unless full_class_name_association
20
+ Tramway::Error.raise_error(
21
+ :tramway, :inputs_helpers, :association_params, :defined_with_property_method, property: property
22
+ )
23
+ end
24
+ return unless full_class_name_association.is_a? Array
25
+
26
+ Tramway::Error.raise_error(
27
+ :tramway, :inputs_helpers, :association_params, :used_polymorphic_association, property: property
28
+ )
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::Inputs::PolymorphicAssociationsHelper
4
+ def build_collection_for_polymorphic_association(form_object, property)
5
+ object_names = full_class_names(form_object, property).map do |class_name|
6
+ class_name.send("#{current_user.role}_scope", current_user.id).map do |obj|
7
+ decorator_class(class_name).decorate obj
8
+ end
9
+ end.flatten
10
+ object_names.sort_by { |obj| obj.name.to_s }
11
+ end
12
+
13
+ def build_value_for_polymorphic_association(form_object, property, value)
14
+ if form_object.send(property).present?
15
+ "#{form_object.send(property).class.to_s.underscore}_#{form_object.send(property).id}"
16
+ elsif value[:type].present? && value[:id].present?
17
+ "#{value[:type]&.underscore}_#{value[:id]}"
18
+ end
19
+ end
20
+
21
+ def full_class_names(form_object, property)
22
+ form_object.model.class.send("#{property}_type").values.map(&:constantize)
23
+ end
24
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::InputsHelper
4
+ include Tramway::Inputs::AssociationsHelper
5
+ include Tramway::Inputs::PolymorphicAssociationsHelper
6
+
7
+ def association_params(form_object:, property:, value:, object:, options: {})
8
+ full_class_name_association = form_object.class.full_class_name_association(property)
9
+
10
+ value = current_user&.id if full_class_name_association.to_s == 'Tramway::User'
11
+
12
+ build_input_attributes(object: object, property: property, options: options,
13
+ value: build_value_for_association(form_object, property, value),
14
+ collection: build_collection_for_association(form_object, property),
15
+ include_blank: true,
16
+ selected: form_object.model.send("#{property}_id") || value)
17
+ end
18
+
19
+ def polymorphic_association_params(object:, form_object:, property:, value:, options: {})
20
+ build_input_attributes object: object, property: property,
21
+ selected: build_value_for_polymorphic_association(form_object, property, value),
22
+ value: build_value_for_polymorphic_association(form_object, property, value),
23
+ collection: build_collection_for_polymorphic_association(form_object, property),
24
+ options: options.merge(
25
+ as: :select,
26
+ include_blank: true,
27
+ label_method: ->(obj) { "#{obj.class.model_name.human} | #{obj.name}" },
28
+ value_method: lambda { |obj|
29
+ "#{obj.class.to_s.underscore.sub(/_decorator$/, '')}_#{obj.id}"
30
+ }
31
+ )
32
+ end
33
+
34
+ def build_input_attributes(**options)
35
+ {
36
+ label: false,
37
+ input_html: {
38
+ name: "#{options[:object]}[#{options[:property]}]",
39
+ id: "#{options[:object]}_#{options[:property]}",
40
+ value: options[:value]
41
+ },
42
+ selected: options[:selected],
43
+ collection: options[:collection]
44
+ }.merge options[:options]
45
+ end
46
+
47
+ def value_from_params(model_class:, property:, type:)
48
+ case type
49
+ when :polymorphic_association, 'polymorphic_association'
50
+ {
51
+ id: params.dig(model_class.to_s.underscore, property.to_s),
52
+ type: params.dig(model_class.to_s.underscore, "#{property}_type")
53
+ }
54
+ else
55
+ params.dig(model_class.to_s.underscore, property.to_s)
56
+ end
57
+ end
58
+
59
+ def default_params(property:, object:, form_object:, value:, options: {})
60
+ {
61
+ label: false,
62
+ input_html: {
63
+ name: "#{object}[#{property}]",
64
+ id: "#{object}_#{property}",
65
+ value: (form_object.send(property) || form_object.model.send(property) || value)
66
+ },
67
+ selected: (form_object.model.send(property) || value)
68
+ }.merge options
69
+ end
70
+
71
+ def simple_params(**options)
72
+ builded_options = { as: options[:type], label: false,
73
+ input_html: {
74
+ name: "#{options[:object]}[#{options[:property]}]",
75
+ id: "#{options[:object]}_#{options[:property]}",
76
+ value: build_simple_value(
77
+ *options.values_at(:form_object, :property, :value),
78
+ options.dig(:options, :input_html, :value)
79
+ )
80
+ } }
81
+ merge_with_user_options builded_options, options
82
+ end
83
+
84
+ def build_simple_value(form_object, property, value, input_html_value)
85
+ value_to_add = input_html_value || value
86
+ value_to_add || form_object.send(property)
87
+ end
88
+
89
+ def merge_with_user_options(builded_options, options)
90
+ if options[:options].present?
91
+ options[:options][:input_html]&.delete(:value)
92
+ builded_options.merge!(options) || {}
93
+ end
94
+ builded_options
95
+ end
96
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::ModelHelper
4
+ def tramway_model?(model_class)
5
+ model_class.ancestors.include? Tramway::ApplicationRecord
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::NavbarHelper
4
+ def customized_admin_navbar_given?
5
+ customized_admin_navbar.present?
6
+ end
7
+
8
+ def customized_admin_navbar
9
+ ::Tramway.customized_admin_navbar
10
+ end
11
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::RecordsHelper
4
+ # FIXME: replace to module
5
+ def current_model_record_path(*args, **options)
6
+ record_path args, options.merge(model: params[:model])
7
+ end
8
+
9
+ def edit_current_model_record_path(*args, **options)
10
+ edit_record_path args, options.merge(model: params[:model])
11
+ end
12
+
13
+ def new_current_model_record_path(*args, **options)
14
+ new_record_path args, options.merge(model: params[:model])
15
+ end
16
+
17
+ def current_model_records_path(*args, **options)
18
+ records_path args, options.merge(model: params[:model])
19
+ end
20
+
21
+ def public_path(record)
22
+ record.public_path || try("#{record.class.name.underscore.gsub('/', '_')}_path", record)
23
+ end
24
+
25
+ def model_class
26
+ params[:model].constantize
27
+ end
28
+
29
+ def decorator_class(model_name = nil)
30
+ "#{model_name || model_class}Decorator".constantize
31
+ end
32
+
33
+ def to_path(constant)
34
+ constant.name.underscore.gsub '/', '_'
35
+ end
36
+
37
+ def search_tab_title(count)
38
+ "#{t('helpers.scope.found')} / #{count}"
39
+ end
40
+
41
+ def searchable_model?(model_class)
42
+ model_class.methods.include? :full_text_search
43
+ end
44
+
45
+ def build_options_for_select(name, collection)
46
+ selected_value = params[:list_filters].present? ? params[:list_filters][name] : nil
47
+ options_for_select(collection, selected_value)
48
+ end
49
+
50
+ def index_path_of_model(model_class, tab, filter)
51
+ if tab
52
+ records_path model: model_class, filter: filter, scope: tab
53
+ else
54
+ records_path model: model_class, filter: filter
55
+ end
56
+ end
57
+
58
+ def collection_human_name(model_name:, collection_name:)
59
+ if t("default.collections.#{collection_name}").include?('<span')
60
+ t("collections.#{model_name}.#{collection_name}").pluralize(:ru)
61
+ else
62
+ t("default.collections.#{collection_name}")
63
+ end
64
+ end
65
+
66
+ def tab_title(model_class, tab, count, _state_method = :state)
67
+ model = model_class.name.underscore
68
+ name = collection_human_name model_name: model, collection_name: tab
69
+ "#{name} / #{count}"
70
+ end
71
+
72
+ def active_tab(tab, index)
73
+ return :active if params[:scope].nil? && index.zero?
74
+ return :active if params[:search].nil? && params[:scope].to_s == tab.to_s
75
+ end
76
+
77
+ def new_associated_record_path(object:, association:, as:)
78
+ unless association.options[:class_name].present?
79
+ raise "You should set `class_name` for #{association.name} association"
80
+ end
81
+
82
+ new_record_path(**new_record_path_options(object, association, as))
83
+ end
84
+
85
+ def new_record_path_options(object, association, as)
86
+ hash = {
87
+ model: association.class_name,
88
+ redirect: current_model_record_path(object.model)
89
+ }
90
+
91
+ if association.options[:as].present? # polymorphic? conditiion
92
+ hash.merge! options_for_polymorphic_link object, association
93
+ else
94
+ hash.merge! options_for_isomorphic_link object, association, as
95
+ end
96
+
97
+ hash
98
+ end
99
+
100
+ def options_for_polymorphic_link(object, association)
101
+ {
102
+ association.options[:class_name].underscore => {
103
+ association.options[:as] => object.id,
104
+ association.type => object.class.model_name
105
+ }
106
+ }
107
+ end
108
+
109
+ def options_for_isomorphic_link(object, association, as)
110
+ {
111
+ association.options[:class_name].underscore => {
112
+ as || object.model.class.name.underscore.gsub('/', '_') => object.id
113
+ }
114
+ }
115
+ end
116
+
117
+ def there_any_filters?(model_class)
118
+ decorator_class(model_class).list_filters&.any?
119
+ end
120
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::RussianCasesHelper
4
+ def case_word(model_name, case_name)
5
+ word_case = I18n.t("cases.#{model_name.name.underscore}.#{case_name}")
6
+ return word_case if word_case.present?
7
+
8
+ raise "There is not #{case_name} implementation for \"#{model_name}\""
9
+ end
10
+
11
+ def genitive(word)
12
+ case_word word, :genitive
13
+ end
14
+
15
+ def instrumental(word)
16
+ case_word word, :instrumental
17
+ end
18
+
19
+ def dative(word)
20
+ case_word word, :dative
21
+ end
22
+
23
+ def russian_plural(word)
24
+ case_word word, :plural
25
+ end
26
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::SingletonHelper
4
+ # FIXME: replace to module
5
+ def current_model_singleton_path(*args, **options)
6
+ singleton_path args, options.merge(model: params[:model])
7
+ end
8
+
9
+ def edit_current_model_singleton_path(*args, **options)
10
+ edit_singleton_path args, options.merge(model: params[:model])
11
+ end
12
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::StateMachineButtonsHelper
4
+ def state_events_buttons(object, **options)
5
+ options.each do |(key, value)|
6
+ define_singleton_method(key) { value }
7
+ end
8
+ content_tag(:div, class: 'btn-group-vertical') do
9
+ transitions(object, state_method, options[:without]).each do |event|
10
+ button(
11
+ object,
12
+ event: event[:event],
13
+ model_name: object.class.model_name.name.underscore,
14
+ form_options: button_options,
15
+ **options
16
+ )
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def excepted_actions(without)
24
+ return unless without
25
+
26
+ without.is_a?(Array) ? without.map(&:to_sym) : [without.to_sym]
27
+ end
28
+
29
+ def transitions(object, state_method, without)
30
+ object.model.aasm(state_method).permitted_transitions.reject do |t|
31
+ excepted_actions(without).present? && excepted_actions(without).include?(t[:event])
32
+ end
33
+ end
34
+
35
+ def button(object, **options)
36
+ event = options[:event]
37
+
38
+ concat(
39
+ patch_button(patch_button_options(object, event, options)) do
40
+ model = object.model
41
+ class_name = model.class.name.underscore
42
+ actual_event = model.aasm(options[:state_method]).events.select { |ev| ev.name == event }.first.name
43
+ I18n.t("state_machines.#{class_name}.#{options[:state_method]}.events.#{actual_event}")
44
+ end
45
+ )
46
+ end
47
+
48
+ def patch_button_options(object, event, options)
49
+ attributes = { aasm_event: event }
50
+ css_class = "btn btn-sm btn-xs btn-#{object.public_send("#{options[:state_method]}_button_color", event)}"
51
+
52
+ {
53
+ record: object.model,
54
+ attributes: attributes,
55
+ model_name: options[:model_name],
56
+ button_options: { class: css_class },
57
+ form_options: options[:form_options],
58
+ url: Tramway::Engine.routes.url_helpers.record_path(object.id, options[:parameters])
59
+ }
60
+ end
61
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tramway::TitleHelper
4
+ def title(page_title = default_title)
5
+ @application ||= Tramway.application&.model_class&.first || Tramway.application
6
+ if @application.present?
7
+ title_text = "#{page_title} | #{@application.try(:title) || @application.public_name}"
8
+ content_for(:title) { title_text }
9
+ else
10
+ Tramway::Error.raise_error(:tramway, :title_helper, :title, :you_should_set_tramway_core_application)
11
+ end
12
+ end
13
+
14
+ def default_title
15
+ t('.title')
16
+ end
17
+
18
+ def page_title(action, model_name)
19
+ if I18n.locale == :ru
20
+ "#{t("helpers.actions.#{action}")} #{genitive(model_name)}"
21
+ else
22
+ "#{t("helpers.actions.#{action}")} #{model_name.model_name.human.downcase}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DatePickerInput < SimpleForm::Inputs::StringInput
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class MultipleFileInput < SimpleForm::Inputs::FileInput
4
+ def input(wrapper_options)
5
+ super(wrapper_options.merge! multiple: true)
6
+ end
7
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'carrierwave/orm/activerecord' if defined?(CarrierWave::Mount)
4
+
5
+ class Tramway::ApplicationRecord < ActiveRecord::Base
6
+ self.abstract_class = true
7
+ audited
8
+ extend ::Enumerize
9
+ include AASM
10
+ acts_as_paranoid
11
+
12
+ scope :created_by_user, lambda { |user_id|
13
+ joins(:audits).where('audits.action = \'create\' AND audits.user_id = ?', user_id)
14
+ }
15
+ scope :admin_scope, ->(_arg) { all }
16
+
17
+ include ::PgSearch::Model
18
+
19
+ def creator
20
+ creation_event = audits.where(action: :create).first
21
+ creation_event.user if creation_event.user_id.present?
22
+ end
23
+
24
+ # FIXME: detect inhertited locales
25
+ class << self
26
+ def human_attribute_name(attribute_name, *_args)
27
+ excepted_attributes = %w[created_at updated_at]
28
+ if attribute_name.to_s.in? excepted_attributes
29
+ I18n.t "activerecord.attributes.tramway/application_record.#{attribute_name}"
30
+ else
31
+ super attribute_name
32
+ end
33
+ end
34
+
35
+ def search_by(*attributes, **associations)
36
+ pg_search_scope :full_text_search,
37
+ against: attributes,
38
+ associated_against: associations,
39
+ using: %i[tsearch trigram]
40
+ end
41
+
42
+ def uploader(attribute_name, uploader_name, **options)
43
+ mount_uploader attribute_name, "#{uploader_name.to_s.camelize}Uploader".constantize
44
+ @versions = options[:versions] if uploader_name == :photo
45
+ @extensions = options[:extensions]
46
+ end
47
+
48
+ def photo_versions
49
+ @versions
50
+ end
51
+
52
+ def file_extensions
53
+ @extensions
54
+ end
55
+
56
+ def state_machines_names
57
+ AASM::StateMachineStore.fetch(self).machine_names
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Tramway::User < ::Tramway::ApplicationRecord
4
+ has_secure_password
5
+
6
+ if defined? Tramway::Conference
7
+ has_many :social_networks, as: :record,
8
+ class_name: 'Tramway::Profiles::SocialNetwork'
9
+ end
10
+
11
+ scope :admins, -> { where role: :admin }
12
+ scope :simple_users, -> { where role: :user }
13
+
14
+ enumerize :role, in: %i[user admin], default: :admin
15
+
16
+ def admin?
17
+ role.admin?
18
+ end
19
+
20
+ def full_name
21
+ "#{first_name} #{last_name}"
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'carrierwave'
4
+
5
+ class ApplicationUploader < CarrierWave::Uploader::Base
6
+ storage :file
7
+
8
+ def store_dir
9
+ "system/uploads/#{model.class.model_name.to_s.underscore}/#{mounted_as}/#{id_directory}"
10
+ end
11
+
12
+ private
13
+
14
+ def id_directory
15
+ if model.respond_to?(:uuid)
16
+ model.reload unless model.uuid.present?
17
+ model.uuid
18
+ else
19
+ model.id
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FileUploader < ApplicationUploader
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class IcoUploader < ApplicationUploader
4
+ def extension_allowlist
5
+ model.class.file_extensions || %w[ico]
6
+ end
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ImageDefaults
4
+ include CarrierWave::MiniMagick
5
+
6
+ def default_url
7
+ "/images/fallback/#{model.class.model_name.to_s.underscore}/" <<
8
+ [mounted_as, version_name].compact.join('_') << '.gif'
9
+ end
10
+
11
+ def extension_allowlist
12
+ model.class.file_extensions || %w[jpg jpeg gif png]
13
+ end
14
+ end