udongo 0.1.0

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 (280) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +26 -0
  4. data/app/assets/fonts/FontAwesome.otf +0 -0
  5. data/app/assets/fonts/fontawesome-webfont.eot +0 -0
  6. data/app/assets/fonts/fontawesome-webfont.svg +685 -0
  7. data/app/assets/fonts/fontawesome-webfont.ttf +0 -0
  8. data/app/assets/fonts/fontawesome-webfont.woff +0 -0
  9. data/app/assets/fonts/fontawesome-webfont.woff2 +0 -0
  10. data/app/assets/javascripts/backend/application.js +22 -0
  11. data/app/assets/javascripts/backend/bootstrap.js +3560 -0
  12. data/app/assets/javascripts/backend/datepickers.js +67 -0
  13. data/app/assets/javascripts/backend/general.js +7 -0
  14. data/app/assets/javascripts/backend/pages.js +7 -0
  15. data/app/assets/javascripts/backend/plugins/autocomplete.js +147 -0
  16. data/app/assets/javascripts/backend/plugins/tagbox.js +52 -0
  17. data/app/assets/javascripts/backend/sortable.js +46 -0
  18. data/app/assets/javascripts/backend/tags.js +37 -0
  19. data/app/assets/javascripts/backend/tree.js +158 -0
  20. data/app/assets/stylesheets/backend/application.scss +18 -0
  21. data/app/assets/stylesheets/backend/bootstrap.scss +6211 -0
  22. data/app/assets/stylesheets/backend/components/_breadcrumb.scss +7 -0
  23. data/app/assets/stylesheets/backend/components/_form.scss +23 -0
  24. data/app/assets/stylesheets/backend/components/_nav.scss +3 -0
  25. data/app/assets/stylesheets/backend/components/_navbar.scss +3 -0
  26. data/app/assets/stylesheets/backend/components/_tabs.scss +3 -0
  27. data/app/assets/stylesheets/backend/font-awesome.scss +2182 -0
  28. data/app/assets/stylesheets/backend/pages/_flexible_content.scss +11 -0
  29. data/app/assets/stylesheets/backend/pages/_logged_in.scss +3 -0
  30. data/app/assets/stylesheets/backend/pages/_login.scss +3 -0
  31. data/app/assets/stylesheets/backend/udongo.scss +17 -0
  32. data/app/controllers/backend/admins_controller.rb +56 -0
  33. data/app/controllers/backend/content/rows/columns_controller.rb +75 -0
  34. data/app/controllers/backend/content/rows/images_controller.rb +6 -0
  35. data/app/controllers/backend/content/rows/texts_controller.rb +6 -0
  36. data/app/controllers/backend/content/rows_controller.rb +45 -0
  37. data/app/controllers/backend/dashboard_controller.rb +2 -0
  38. data/app/controllers/backend/email_templates_controller.rb +50 -0
  39. data/app/controllers/backend/emails_controller.rb +18 -0
  40. data/app/controllers/backend/navigation/items_controller.rb +64 -0
  41. data/app/controllers/backend/navigations_controller.rb +7 -0
  42. data/app/controllers/backend/pages_controller.rb +89 -0
  43. data/app/controllers/backend/redirects_controller.rb +50 -0
  44. data/app/controllers/backend/seo_controller.rb +6 -0
  45. data/app/controllers/backend/sessions_controller.rb +22 -0
  46. data/app/controllers/backend/snippets_controller.rb +49 -0
  47. data/app/controllers/backend/tagbox_controller.rb +52 -0
  48. data/app/controllers/backend/webserver_controller.rb +6 -0
  49. data/app/controllers/backend_controller.rb +34 -0
  50. data/app/controllers/catch_all_controller.rb +12 -0
  51. data/app/controllers/concerns/backend/content_type_controller.rb +44 -0
  52. data/app/controllers/concerns/backend/positionable_controller.rb +12 -0
  53. data/app/controllers/concerns/backend/translatable_controller.rb +41 -0
  54. data/app/controllers/concerns/pagination_controller.rb +38 -0
  55. data/app/controllers/redirects_controller.rb +18 -0
  56. data/app/decorators/content_image_decorator.rb +7 -0
  57. data/app/decorators/content_text_decorator.rb +11 -0
  58. data/app/decorators/navigation_item_decorator.rb +29 -0
  59. data/app/decorators/page_decorator.rb +29 -0
  60. data/app/decorators/redirect_decorator.rb +13 -0
  61. data/app/decorators/snippet_decorator.rb +11 -0
  62. data/app/forms/backend/email_template_translation_form.rb +11 -0
  63. data/app/forms/backend/navigation_item_translation_form.rb +8 -0
  64. data/app/forms/backend/page_translation_form.rb +17 -0
  65. data/app/forms/backend/snippet_translation_form.rb +8 -0
  66. data/app/helpers/backend/dropdown_helper.rb +9 -0
  67. data/app/helpers/backend/pagination_helper.rb +17 -0
  68. data/app/helpers/icon_helper.rb +6 -0
  69. data/app/helpers/link_helper.rb +33 -0
  70. data/app/helpers/snippet_helper.rb +5 -0
  71. data/app/helpers/udongo_helper.rb +27 -0
  72. data/app/inputs/date_picker_input.rb +30 -0
  73. data/app/inputs/date_range_picker_input.rb +13 -0
  74. data/app/mailers/general_mailer.rb +13 -0
  75. data/app/models/address.rb +5 -0
  76. data/app/models/admin.rb +9 -0
  77. data/app/models/ckeditor/asset.rb +7 -0
  78. data/app/models/ckeditor/attachment_file.rb +7 -0
  79. data/app/models/ckeditor/picture.rb +7 -0
  80. data/app/models/comment.rb +39 -0
  81. data/app/models/concerns/cacheable.rb +37 -0
  82. data/app/models/concerns/commentable.rb +9 -0
  83. data/app/models/concerns/content_type.rb +13 -0
  84. data/app/models/concerns/deletable.rb +13 -0
  85. data/app/models/concerns/draggable.rb +13 -0
  86. data/app/models/concerns/emailable.rb +7 -0
  87. data/app/models/concerns/flexible_content.rb +9 -0
  88. data/app/models/concerns/locale.rb +10 -0
  89. data/app/models/concerns/loggable.rb +13 -0
  90. data/app/models/concerns/notable.rb +9 -0
  91. data/app/models/concerns/parentable.rb +39 -0
  92. data/app/models/concerns/person.rb +7 -0
  93. data/app/models/concerns/publishable.rb +22 -0
  94. data/app/models/concerns/seo.rb +31 -0
  95. data/app/models/concerns/sortable.rb +30 -0
  96. data/app/models/concerns/spammable.rb +38 -0
  97. data/app/models/concerns/storable/collection.rb +117 -0
  98. data/app/models/concerns/storable/config.rb +40 -0
  99. data/app/models/concerns/storable.rb +32 -0
  100. data/app/models/concerns/taggable.rb +29 -0
  101. data/app/models/concerns/translatable/collection.rb +60 -0
  102. data/app/models/concerns/translatable/config.rb +19 -0
  103. data/app/models/concerns/translatable.rb +39 -0
  104. data/app/models/concerns/visible.rb +24 -0
  105. data/app/models/content_column.rb +14 -0
  106. data/app/models/content_image.rb +9 -0
  107. data/app/models/content_row.rb +11 -0
  108. data/app/models/content_text.rb +7 -0
  109. data/app/models/email.rb +13 -0
  110. data/app/models/email_template.rb +10 -0
  111. data/app/models/form.rb +9 -0
  112. data/app/models/form_field.rb +15 -0
  113. data/app/models/form_field_validation.rb +11 -0
  114. data/app/models/form_submission.rb +16 -0
  115. data/app/models/form_submission_data.rb +5 -0
  116. data/app/models/log.rb +7 -0
  117. data/app/models/meta.rb +11 -0
  118. data/app/models/navigation.rb +6 -0
  119. data/app/models/navigation_item.rb +12 -0
  120. data/app/models/note.rb +7 -0
  121. data/app/models/page.rb +23 -0
  122. data/app/models/queued_task.rb +45 -0
  123. data/app/models/redirect.rb +16 -0
  124. data/app/models/setting.rb +13 -0
  125. data/app/models/snippet.rb +10 -0
  126. data/app/models/store.rb +10 -0
  127. data/app/models/tag.rb +6 -0
  128. data/app/models/tagged_item.rb +8 -0
  129. data/app/models/translation.rb +10 -0
  130. data/app/uploaders/ckeditor_attachment_file_uploader.rb +36 -0
  131. data/app/uploaders/ckeditor_picture_uploader.rb +47 -0
  132. data/app/uploaders/content_image_uploader.rb +52 -0
  133. data/app/validators/email_validator.rb +7 -0
  134. data/app/validators/url_validator.rb +13 -0
  135. data/app/views/backend/_breadcrumbs.html.erb +18 -0
  136. data/app/views/backend/_flash_messages.html.erb +9 -0
  137. data/app/views/backend/_form_actions.html.erb +4 -0
  138. data/app/views/backend/_form_errors.html.erb +15 -0
  139. data/app/views/backend/_navigation.html.erb +8 -0
  140. data/app/views/backend/_seo_form.html.erb +13 -0
  141. data/app/views/backend/_tags.html.erb +18 -0
  142. data/app/views/backend/admins/_form.html.erb +32 -0
  143. data/app/views/backend/admins/edit.html.erb +4 -0
  144. data/app/views/backend/admins/index.html.erb +33 -0
  145. data/app/views/backend/admins/new.html.erb +4 -0
  146. data/app/views/backend/content/_image.html.erb +17 -0
  147. data/app/views/backend/content/_rows.html.erb +63 -0
  148. data/app/views/backend/content/_text.html.erb +1 -0
  149. data/app/views/backend/content/rows/columns/edit.html.erb +25 -0
  150. data/app/views/backend/content/rows/columns/new.html.erb +25 -0
  151. data/app/views/backend/content/rows/images/edit.html.erb +22 -0
  152. data/app/views/backend/content/rows/texts/edit.html.erb +13 -0
  153. data/app/views/backend/dashboard/show.html.erb +1 -0
  154. data/app/views/backend/email_templates/_form.html.erb +31 -0
  155. data/app/views/backend/email_templates/_tabs.html.erb +21 -0
  156. data/app/views/backend/email_templates/edit.html.erb +6 -0
  157. data/app/views/backend/email_templates/edit_translation.html.erb +51 -0
  158. data/app/views/backend/email_templates/index.html.erb +27 -0
  159. data/app/views/backend/email_templates/new.html.erb +4 -0
  160. data/app/views/backend/emails/index.html.erb +50 -0
  161. data/app/views/backend/emails/show.html.erb +68 -0
  162. data/app/views/backend/navigation/items/_form.html.erb +22 -0
  163. data/app/views/backend/navigation/items/_tabs.html.erb +21 -0
  164. data/app/views/backend/navigation/items/edit.html.erb +7 -0
  165. data/app/views/backend/navigation/items/edit_translation.html.erb +21 -0
  166. data/app/views/backend/navigation/items/new.html.erb +4 -0
  167. data/app/views/backend/navigations/index.html.erb +59 -0
  168. data/app/views/backend/pages/_form.html.erb +30 -0
  169. data/app/views/backend/pages/_tabs.html.erb +21 -0
  170. data/app/views/backend/pages/edit.html.erb +6 -0
  171. data/app/views/backend/pages/edit_translation.html.erb +35 -0
  172. data/app/views/backend/pages/index.html.erb +19 -0
  173. data/app/views/backend/pages/new.html.erb +4 -0
  174. data/app/views/backend/redirects/_filter.html.erb +21 -0
  175. data/app/views/backend/redirects/_form.html.erb +36 -0
  176. data/app/views/backend/redirects/edit.html.erb +4 -0
  177. data/app/views/backend/redirects/index.html.erb +50 -0
  178. data/app/views/backend/redirects/new.html.erb +3 -0
  179. data/app/views/backend/sessions/new.html.erb +19 -0
  180. data/app/views/backend/snippets/_form.html.erb +18 -0
  181. data/app/views/backend/snippets/_tabs.html.erb +21 -0
  182. data/app/views/backend/snippets/edit.html.erb +6 -0
  183. data/app/views/backend/snippets/edit_translation.html.erb +23 -0
  184. data/app/views/backend/snippets/index.html.erb +26 -0
  185. data/app/views/backend/snippets/new.html.erb +4 -0
  186. data/app/views/layouts/backend/_top_navigation.html.erb +27 -0
  187. data/app/views/layouts/backend/application.html.erb +27 -0
  188. data/app/views/layouts/backend/lightbox.html.erb +21 -0
  189. data/app/views/layouts/backend/login.html.erb +21 -0
  190. data/app/views/layouts/frontend/application.html.erb +16 -0
  191. data/config/initializers/assets.rb +13 -0
  192. data/config/initializers/ckeditor.rb +38 -0
  193. data/config/initializers/core_ext/string.rb +8 -0
  194. data/config/initializers/inflections.rb +20 -0
  195. data/config/initializers/simple_form_bootstrap.rb +150 -0
  196. data/config/locales/de.yml +196 -0
  197. data/config/locales/en.yml +206 -0
  198. data/config/locales/fr.yml +214 -0
  199. data/config/locales/nl.yml +212 -0
  200. data/config/locales/nl_backend.yml +106 -0
  201. data/config/locales/nl_forms.yml +68 -0
  202. data/config/routes.rb +83 -0
  203. data/config/spring.rb +1 -0
  204. data/db/migrate/20150404133314_create_udongo_admins.rb +12 -0
  205. data/db/migrate/20150404203549_remove_udongo_admins.rb +5 -0
  206. data/db/migrate/20150404203648_add_admins_table.rb +12 -0
  207. data/db/migrate/20150405202002_create_logs.rb +15 -0
  208. data/db/migrate/20150411152042_create_addresses.rb +19 -0
  209. data/db/migrate/20150411152619_create_comments.rb +21 -0
  210. data/db/migrate/20150411200227_create_notes.rb +14 -0
  211. data/db/migrate/20150412094419_create_settings.rb +13 -0
  212. data/db/migrate/20150412095257_create_meta.rb +21 -0
  213. data/db/migrate/20150413190019_create_tags.rb +15 -0
  214. data/db/migrate/20150413190713_create_tagged_items.rb +14 -0
  215. data/db/migrate/20150417113400_create_translations.rb +15 -0
  216. data/db/migrate/20150515113332_content_tables.rb +42 -0
  217. data/db/migrate/20150515134044_create_pages.rb +22 -0
  218. data/db/migrate/20150620121834_remove_trashed_from_page.rb +5 -0
  219. data/db/migrate/20150629120253_add_locales_to_translatables.rb +5 -0
  220. data/db/migrate/20150706083925_create_snippets.rb +13 -0
  221. data/db/migrate/20150706084145_add_locales_to_snippet.rb +5 -0
  222. data/db/migrate/20150706085213_add_description_to_snippet.rb +5 -0
  223. data/db/migrate/20150706085733_change_snippet_desc_field.rb +5 -0
  224. data/db/migrate/20150706093016_change_desc_column_for_snippet.rb +5 -0
  225. data/db/migrate/20150706113918_create_ckeditor_assets.rb +26 -0
  226. data/db/migrate/20150824090457_create_redirects.rb +12 -0
  227. data/db/migrate/20150824113723_rename_source_and_destination_to_redirects.rb +6 -0
  228. data/db/migrate/20150825094254_create_shortened_urls.rb +11 -0
  229. data/db/migrate/20150826084004_remove_shortened_urls.rb +5 -0
  230. data/db/migrate/20150826084234_add_times_used_to_redirect.rb +5 -0
  231. data/db/migrate/20150826134153_add_index_on_page_visible.rb +5 -0
  232. data/db/migrate/20151003191846_add_some_indexes.rb +15 -0
  233. data/db/migrate/20151006131720_add_editable_content_to_page.rb +5 -0
  234. data/db/migrate/20151007090541_create_navigations.rb +10 -0
  235. data/db/migrate/20151007091131_create_navigation_items.rb +12 -0
  236. data/db/migrate/20151007092529_add_index_to_nav_items.rb +5 -0
  237. data/db/migrate/20151007093812_change_nav_description_type.rb +5 -0
  238. data/db/migrate/20151007173557_add_route_to_page.rb +5 -0
  239. data/db/migrate/20151008114433_add_extra_to_navigation_item.rb +5 -0
  240. data/db/migrate/20151009114823_create_forms.rb +9 -0
  241. data/db/migrate/20151009115326_create_form_fields.rb +12 -0
  242. data/db/migrate/20151009115803_create_form_field_validations.rb +11 -0
  243. data/db/migrate/20151009131957_create_form_submissions.rb +10 -0
  244. data/db/migrate/20151009132657_create_form_submission_data.rb +11 -0
  245. data/db/migrate/20151009140754_rename_form_field_id_to_field_id_for_form_field_validations.rb +5 -0
  246. data/db/migrate/20151009141123_rename_form_submission_id_to_submission_id_for_form_submission_data.rb +5 -0
  247. data/db/migrate/20151009141822_add_locales_to_form_fields.rb +5 -0
  248. data/db/migrate/20151009142242_add_locales_to_form_field_validations.rb +5 -0
  249. data/db/migrate/20151023135358_add_locales_to_forms.rb +5 -0
  250. data/db/migrate/20151117190950_add_external_reference_to_tags.rb +6 -0
  251. data/db/migrate/20151205202815_rename_nav_name_to_identifier.rb +5 -0
  252. data/db/migrate/20160108100104_create_stores.rb +13 -0
  253. data/db/migrate/20160125154648_rename_name_to_identifier_for_forms.rb +5 -0
  254. data/db/migrate/20160128080029_create_email_templates.rb +18 -0
  255. data/db/migrate/20160201121021_create_emails.rb +17 -0
  256. data/db/migrate/20160201123128_create_queued_tasks.rb +13 -0
  257. data/db/migrate/20160205132009_reoraganize_snippet_html_fields.rb +7 -0
  258. data/db/migrate/20160303141417_add_data_to_logs.rb +5 -0
  259. data/db/migrate/20160513110925_addd_more_column_widths.rb +9 -0
  260. data/lib/generators/udongo/form/form_generator.rb +63 -0
  261. data/lib/generators/udongo/form/templates/form.rb +18 -0
  262. data/lib/tasks/task_extras.rb +10 -0
  263. data/lib/tasks/udongo_tasks.rake +41 -0
  264. data/lib/udongo/active_model_simulator.rb +21 -0
  265. data/lib/udongo/assets/loader.rb +40 -0
  266. data/lib/udongo/assets/precompiler.rb +50 -0
  267. data/lib/udongo/breadcrumb.rb +27 -0
  268. data/lib/udongo/config.rb +26 -0
  269. data/lib/udongo/crypt.rb +19 -0
  270. data/lib/udongo/cryptography.rb +5 -0
  271. data/lib/udongo/email_vars/address.rb +19 -0
  272. data/lib/udongo/email_vars/form_submission.rb +18 -0
  273. data/lib/udongo/email_vars_parser.rb +31 -0
  274. data/lib/udongo/engine.rb +10 -0
  275. data/lib/udongo/meta_info.rb +12 -0
  276. data/lib/udongo/object_path.rb +24 -0
  277. data/lib/udongo/version.rb +3 -0
  278. data/lib/udongo/will_paginate/renderer.rb +26 -0
  279. data/lib/udongo.rb +44 -0
  280. metadata +772 -0
@@ -0,0 +1,7 @@
1
+ module Concerns::Emailable
2
+ extend ActiveSupport::Concern
3
+
4
+ def email_vars
5
+ @email_vars ||= "Udongo::EmailVars::#{self.class.name}".constantize.new self
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Concerns
2
+ module FlexibleContent
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :content_rows, as: :rowable, dependent: :destroy
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ # This concern is applicable when your model has a 'locale' field.
2
+ module Concerns
3
+ module Locale
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ scope :by_locale, ->(l) { where(locale: l) }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Concerns
2
+ module Loggable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :logs, as: :loggable, dependent: :destroy
7
+ end
8
+
9
+ def log(message, category: nil, data: nil)
10
+ logs.create!(content: message, category: category, data: data)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module Concerns
2
+ module Notable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :notes, as: :notable, dependent: :destroy
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ # If you want to use this concern, you need to add an integer field 'parent_id'
2
+ # to the model.
3
+ module Concerns
4
+ module Parentable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ has_many :children, class_name: self.name, foreign_key: :parent_id, dependent: :destroy
9
+ belongs_to :parent, class_name: self.name
10
+ end
11
+
12
+ def depth
13
+ parent.nil? ? 0 : 1 + parent.depth
14
+ end
15
+
16
+ def parentable?
17
+ true
18
+ end
19
+
20
+ def parents(instance: nil, list: [], include_self: true)
21
+ instance = self unless instance.present?
22
+ list << instance if include_self
23
+ instance.parent ? parents(instance: instance.parent, list: list) : list
24
+ end
25
+
26
+ module ClassMethods
27
+ def flat_tree(parent_id: nil, list: [])
28
+ children = name.constantize.where(parent_id: parent_id)
29
+
30
+ children.each do |c|
31
+ list << c
32
+ list = self.flat_tree(parent_id: c.id, list: list) if c.children.any?
33
+ end
34
+
35
+ list
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Concerns
2
+ module Person
3
+ def full_name
4
+ "#{first_name} #{last_name}"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ module Concerns
2
+ module Publishable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ scope :published, ->{ where('published_at < ?', Time.zone.now).order('published_at DESC') }
7
+ scope :not_published, ->{ where('published_at > ? OR published_at IS NULL OR published_at = ?', Time.zone.now, '') }
8
+ end
9
+
10
+ def publish!(time = Time.zone.now)
11
+ update_attribute :published_at, time
12
+ end
13
+
14
+ def unpublish!
15
+ update_attribute :published_at, nil
16
+ end
17
+
18
+ def published?
19
+ published_at && published_at.past?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ module Concerns
2
+ module Seo
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :meta, as: :sluggable, dependent: :destroy
7
+ end
8
+
9
+ module ClassMethods
10
+ def find_by_slug(slug, locale: I18n.locale)
11
+ joins(:meta).where(
12
+ 'meta.locale' => locale,
13
+ 'meta.slug' => slug,
14
+ 'meta.sluggable_type' => self.name
15
+ ).first
16
+ end
17
+
18
+ def find_by_slug!(slug, locale: I18n.locale)
19
+ find_by_slug(slug, locale: locale) || raise('No such record found')
20
+ end
21
+ end
22
+
23
+ def seo(locale = I18n.locale)
24
+ @seo_collections = {} unless @seo_collections
25
+ return @seo_collections[locale.to_sym] if @seo_collections[locale.to_sym]
26
+
27
+ existing_meta = meta.find_by(locale: locale)
28
+ @seo_collections[locale.to_sym] = existing_meta ? existing_meta : meta.new(locale: locale)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # If you want to use this concern, you need to add an integer field 'position'
2
+ # to the model.
3
+ module Concerns
4
+ module Sortable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ acts_as_list
9
+ default_scope ->{ order(:position) }
10
+ end
11
+
12
+ module ClassMethods
13
+ def sortable(*args)
14
+ acts_as_list *args
15
+ end
16
+ end
17
+
18
+ def set_position(new_position, parent_id = nil)
19
+ if respond_to?(:draggable?) && !draggable? || position == new_position.to_i
20
+ return false
21
+ end
22
+
23
+ if respond_to?(:parentable?) && parentable?
24
+ update_attribute :parent_id, parent_id
25
+ end
26
+
27
+ set_list_position new_position
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ # If you want to use this module, you need to add a boolean field
2
+ # 'marked_as_spam' to the model.
3
+ module Concerns
4
+ module Spammable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include Rakismet::Model
9
+
10
+ scope :spam, ->{ where(marked_as_spam: true) }
11
+ scope :not_spam, -> { where('marked_as_spam = 0 OR marked_as_spam IS NULL') }
12
+
13
+ after_initialize :spam_defaults
14
+ end
15
+
16
+ module ClassMethods
17
+ def spammable(*args)
18
+ rakismet_attrs *args
19
+ end
20
+ end
21
+
22
+ def mark_as_spam!
23
+ update_attribute :marked_as_spam, true
24
+ end
25
+
26
+ def unmark_as_spam!
27
+ update_attribute :marked_as_spam, false
28
+ end
29
+
30
+ private
31
+
32
+ def spam_defaults
33
+ if self.new_record?
34
+ self.marked_as_spam = false if marked_as_spam.nil?
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,117 @@
1
+ module Concerns
2
+ module Storable
3
+ class Collection
4
+ def initialize(parent, config)
5
+ @parent = parent
6
+ @config = config
7
+ @stores = {}
8
+ end
9
+
10
+ def method_missing(method_sym, *arguments, &block)
11
+ ascii_name = method_sym.to_s.gsub('=', '').to_sym
12
+
13
+ if @config.fields.keys.include?(ascii_name)
14
+ if method_sym.to_s.include?('=')
15
+ write(ascii_name, arguments.first)
16
+ else
17
+ read(method_sym)
18
+ end
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ def read(field)
25
+ field = field.to_sym
26
+ init_field(field.to_sym)
27
+ object = @stores[field]
28
+
29
+ unless object.value.nil?
30
+ return object.value if klasses_match(@config.fields[field][:klass], object.value)
31
+ end
32
+
33
+ object.value = Marshal.load(Marshal.dump(@config.fields[field][:default]))
34
+ object.value
35
+ end
36
+
37
+ def write(field, value)
38
+ field = field.to_sym
39
+ init_field(field)
40
+ value = transform_value(@config.fields[field][:klass], value)
41
+
42
+ @stores[field].value = value
43
+ end
44
+
45
+ def save
46
+ @stores.map { |field,object| object.save }
47
+ end
48
+
49
+ private
50
+
51
+ def init_field(field)
52
+ unless @stores[field.to_sym]
53
+ raise "The field '#{field.to_s}' is not allowed." unless @config.allowed?(field)
54
+
55
+ @stores[field] = ::Store.find_or_create_by!(
56
+ storable_type: @parent.class.name,
57
+ storable_id: @parent.id,
58
+ klass: @config.fields[field.to_sym][:klass],
59
+ name: field
60
+ )
61
+ end
62
+ end
63
+
64
+ def klasses_match(klass, value)
65
+ Concerns::Storable::Config::KLASSES[klass].include? value.class
66
+ end
67
+
68
+ def transform_value(klass, value)
69
+ send("#{klass}_value", value)
70
+ end
71
+
72
+ def string_value(value)
73
+ value.to_s if value.is_a?(String) || value.is_a?(Symbol)
74
+ end
75
+
76
+ def integer_value(value)
77
+ value if value.is_a?(Fixnum)
78
+ end
79
+
80
+ def float_value(value)
81
+ value if value.is_a?(Float)
82
+ end
83
+
84
+ def array_value(value)
85
+ value if value.is_a?(Array)
86
+ end
87
+
88
+ def boolean_value(value)
89
+ if value === true || value == '1' || value == 1
90
+ true
91
+ elsif value === false || value == '0' || value == 0
92
+ false
93
+ end
94
+ end
95
+
96
+ def date_value(value)
97
+ return value if value.is_a?(Date)
98
+
99
+ begin
100
+ Date.parse(value)
101
+ rescue
102
+ nil
103
+ end
104
+ end
105
+
106
+ def date_time_value(value)
107
+ return value if value.is_a?(DateTime)
108
+
109
+ begin
110
+ DateTime.parse(value)
111
+ rescue
112
+ nil
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,40 @@
1
+ module Concerns
2
+ module Storable
3
+ class Config
4
+ attr_reader :fields
5
+
6
+ KLASSES = {
7
+ array: [Array],
8
+ boolean: [TrueClass, FalseClass],
9
+ date: [Date],
10
+ date_time: [DateTime],
11
+ float: [Float],
12
+ integer: [Fixnum],
13
+ string: [String]
14
+ }
15
+
16
+ def initialize
17
+ @fields = {}
18
+ end
19
+
20
+ def add(field, klass, default = nil)
21
+ raise "#{klass} is not a valid storable config klass" unless valid_klass?(klass)
22
+
23
+ @fields[field.to_sym] = {
24
+ klass: klass.to_sym,
25
+ default: default
26
+ }
27
+ end
28
+
29
+ def allowed?(field)
30
+ @fields.keys.include?(field.to_sym)
31
+ end
32
+
33
+ private
34
+
35
+ def valid_klass?(value)
36
+ KLASSES.keys.include?(value.to_sym)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ module Concerns
2
+ module Storable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :stores, as: :storable, dependent: :destroy
7
+ after_save { store.save }
8
+ end
9
+
10
+ def store
11
+ @store_collection ||= Concerns::Storable::Collection.new(
12
+ self, self.class.store_config
13
+ )
14
+ end
15
+
16
+ def storable?
17
+ true
18
+ end
19
+
20
+ module ClassMethods
21
+ def storable_field(name, klass, default = nil)
22
+ delegate name, to: :store
23
+ delegate "#{name}=", to: :store
24
+ store_config.add(name, klass, default)
25
+ end
26
+
27
+ def store_config
28
+ @store_config ||= Concerns::Storable::Config.new
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ module Concerns
2
+ module Taggable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ has_many :tagged_items, as: :taggable
7
+ end
8
+
9
+ def related(locale = I18n.locale)
10
+ tagged_items = TaggedItem.where(tag_id: tags(locale).map(&:id), taggable_type: self.class.name)
11
+ tagged_items.to_a.inject([]) do |memo,i|
12
+ memo << self.class.find(i.taggable_id) unless i.taggable_id == id
13
+ memo
14
+ end.uniq
15
+ end
16
+
17
+ def tags(locale = I18n.locale)
18
+ tagged_items.select { |i| i.tag.locale == locale.to_s }.map(&:tag)
19
+ end
20
+
21
+ def tags_string(locale = I18n.locale)
22
+ tags(locale).map(&:name).join(',')
23
+ end
24
+
25
+ def taggable?
26
+ true
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,60 @@
1
+ module Concerns
2
+ module Translatable
3
+ class Collection
4
+ def initialize(parent, config, locale)
5
+ @parent = parent
6
+ @config = config
7
+ @locale = locale
8
+ @translations = {}
9
+ end
10
+
11
+ def method_missing(method_sym, *arguments, &block)
12
+ ascii_name = method_sym.to_s.gsub('=', '').to_sym
13
+
14
+ if @config.fields.include?(ascii_name)
15
+ if method_sym.to_s.include?('=')
16
+ write(ascii_name, arguments.first)
17
+ else
18
+ read(method_sym)
19
+ end
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def read(field)
26
+ init_field(field.to_sym)
27
+ @translations[field.to_sym].value
28
+ end
29
+
30
+ def write(field, value)
31
+ init_field(field.to_sym)
32
+ @translations[field.to_sym].value = value
33
+ end
34
+
35
+ def save
36
+ @translations.map { |field,object| object.save }
37
+
38
+ @parent.update_attribute(
39
+ :locales,
40
+ @parent.translations.where.not(value: nil).pluck(:locale).uniq.sort
41
+ )
42
+ end
43
+
44
+ private
45
+
46
+ def init_field(field)
47
+ unless @translations[field.to_sym]
48
+ raise "The field '#{field.to_s}' is not allowed." unless @config.allowed?(field)
49
+
50
+ @translations[field] = Translation.find_or_create_by!(
51
+ translatable_type: @parent.class.name,
52
+ translatable_id: @parent.id,
53
+ locale: @locale,
54
+ name: field
55
+ )
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ module Concerns
2
+ module Translatable
3
+ class Config
4
+ attr_reader :fields
5
+
6
+ def initialize
7
+ @fields = []
8
+ end
9
+
10
+ def add(field)
11
+ @fields << field.to_sym unless @fields.include?(field.to_sym)
12
+ end
13
+
14
+ def allowed?(field)
15
+ @fields.include?(field.to_sym)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,39 @@
1
+ # To use this module you need to add a text column to your model. This will be
2
+ # used to determine in which locales the translations exist.
3
+ module Concerns
4
+ module Translatable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ serialize :locales, Array
9
+ has_many :translations, as: :translatable, dependent: :destroy
10
+ scope :within_locale, ->(locale) { where('locales LIKE ?', "%#{locale}%")}
11
+ end
12
+
13
+ def translation(locale = I18n.locale)
14
+ @translation_collections = {} unless @translation_collections
15
+ @translation_collections[locale.to_sym] ||= Concerns::Translatable::Collection.new(
16
+ self, self.class.translation_config, locale
17
+ )
18
+ end
19
+
20
+ def translatable?
21
+ true
22
+ end
23
+
24
+ module ClassMethods
25
+ def translatable_field(name)
26
+ delegate name, to: :translation
27
+ translation_config.add(name)
28
+ end
29
+
30
+ def translatable_fields(*args)
31
+ args.each { |name| translatable_field(name) }
32
+ end
33
+
34
+ def translation_config
35
+ @translation_config ||= Concerns::Translatable::Config.new
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ # If you want to use this concern, you need to add a boolean field 'visible' to
2
+ # the model.
3
+ module Concerns
4
+ module Visible
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ scope :visible, ->{ where(visible: true) }
9
+ scope :hidden, ->{ where('visible = 0 OR visible IS NULL') }
10
+ end
11
+
12
+ def hidden?
13
+ !visible?
14
+ end
15
+
16
+ def hide!
17
+ update_attribute :visible, false
18
+ end
19
+
20
+ def show!
21
+ update_attribute :visible, true
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ class ContentColumn < ActiveRecord::Base
2
+ include Concerns::Sortable
3
+ sortable(scope: :row_id)
4
+
5
+ belongs_to :row, class_name: 'ContentRow', touch: true
6
+ belongs_to :content, polymorphic: true, dependent: :destroy
7
+
8
+ validates :row, presence: true
9
+ validates :width_xs, :width_sm, :width_md, :width_lg, :width_xl,
10
+ presence: true,
11
+ numericality: { greater_than: 0, less_than_or_equal_to: 12, only_integer: true }
12
+
13
+ default_scope -> { order(:position) }
14
+ end
@@ -0,0 +1,9 @@
1
+ class ContentImage < ActiveRecord::Base
2
+ include Concerns::ContentType
3
+
4
+ mount_uploader :file, ContentImageUploader
5
+
6
+ def content_type
7
+ :image
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ class ContentRow < ActiveRecord::Base
2
+ include Concerns::Locale
3
+
4
+ include Concerns::Sortable
5
+ sortable scope: [:locale, :rowable_type, :rowable_id]
6
+
7
+ belongs_to :rowable, polymorphic: true, touch: true
8
+ has_many :columns, class_name: 'ContentColumn', foreign_key: :row_id, dependent: :destroy
9
+
10
+ validates :locale, presence: true
11
+ end
@@ -0,0 +1,7 @@
1
+ class ContentText < ActiveRecord::Base
2
+ include Concerns::ContentType
3
+
4
+ def content_type
5
+ :text
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ class Email < ActiveRecord::Base
2
+ validates :from_name, :to_name, :subject, :plain_content, :html_content,
3
+ presence: true
4
+ validates :from_email, :to_email,
5
+ format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
6
+
7
+ scope :sent, -> { where('sent_at IS NOT NULL') }
8
+ scope :not_sent, -> { where('sent_at IS NULL') }
9
+
10
+ def mark_as_sent!
11
+ update_attribute :sent_at, DateTime.now
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ class EmailTemplate < ActiveRecord::Base
2
+ include Concerns::Translatable
3
+ translatable_fields :subject, :plain_content, :html_content
4
+
5
+ include Concerns::Sortable
6
+
7
+ validates :description, :from_name, presence: true
8
+ validates :identifier, presence: true, uniqueness: { case_sensitive: false }
9
+ validates :from_email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i }
10
+ end
@@ -0,0 +1,9 @@
1
+ class Form < ActiveRecord::Base
2
+ include Concerns::Translatable
3
+ translatable_fields :success_message
4
+
5
+ has_many :fields, class_name: 'FormField', dependent: :destroy
6
+ has_many :submissions, class_name: 'FormSubmission', dependent: :destroy
7
+
8
+ validates :identifier, presence: true
9
+ end