binda 0.0.7 → 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 (301) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +850 -18
  3. data/Rakefile +1 -13
  4. data/app/assets/javascripts/binda/application.js +23 -0
  5. data/app/assets/javascripts/binda/components/bootstrap.js +9 -0
  6. data/app/assets/javascripts/binda/components/field_group_editor.js +18 -0
  7. data/app/assets/javascripts/binda/components/fileupload.js +182 -0
  8. data/app/assets/javascripts/binda/components/form_item.js +91 -23
  9. data/app/assets/javascripts/binda/components/form_item_choice.js +15 -8
  10. data/app/assets/javascripts/binda/components/form_item_editor.js +6 -6
  11. data/app/assets/javascripts/binda/components/form_item_image.js +23 -0
  12. data/app/assets/javascripts/binda/components/form_item_repeater.js +70 -23
  13. data/app/assets/javascripts/binda/components/login-shader.js +193 -0
  14. data/app/assets/javascripts/binda/components/login_form.js +116 -0
  15. data/app/assets/javascripts/binda/components/radio-toggle.js +18 -0
  16. data/app/assets/javascripts/binda/components/select2.js +22 -0
  17. data/app/assets/javascripts/binda/components/sortable.js +53 -8
  18. data/app/assets/javascripts/binda/dist/binda.bundle.js +914 -107
  19. data/app/assets/javascripts/binda/index.js +26 -3
  20. data/app/assets/stylesheets/binda/application.scss +6 -0
  21. data/app/assets/stylesheets/binda/components/assets_manager.scss +46 -36
  22. data/app/assets/stylesheets/binda/components/b-btn.scss +114 -0
  23. data/app/assets/stylesheets/binda/components/button.scss +4 -1
  24. data/app/assets/stylesheets/binda/components/fileupload.scss +134 -0
  25. data/app/assets/stylesheets/binda/components/form_item.scss +233 -168
  26. data/app/assets/stylesheets/binda/components/form_item_image.scss +0 -0
  27. data/app/assets/stylesheets/binda/components/form_item_video.scss +25 -0
  28. data/app/assets/stylesheets/binda/components/main_container.scss +3 -0
  29. data/app/assets/stylesheets/binda/components/main_content.scss +1 -2
  30. data/app/assets/stylesheets/binda/components/main_header.scss +21 -20
  31. data/app/assets/stylesheets/binda/components/main_sidebar.scss +121 -0
  32. data/app/assets/stylesheets/binda/components/main_table.scss +84 -28
  33. data/app/assets/stylesheets/binda/components/popup_warning.scss +71 -0
  34. data/app/assets/stylesheets/binda/components/select2.scss +126 -0
  35. data/app/assets/stylesheets/binda/components/sortable.scss +83 -56
  36. data/app/assets/stylesheets/binda/components/standard-form.scss +228 -0
  37. data/app/assets/stylesheets/binda/components/texts.scss +6 -0
  38. data/app/assets/stylesheets/binda/controllers/components_index.scss +3 -0
  39. data/app/assets/stylesheets/binda/controllers/users_sessions_new.scss +309 -0
  40. data/app/assets/stylesheets/binda/index.scss +16 -8
  41. data/app/assets/stylesheets/binda/settings/bootstrap_overrides.scss +0 -0
  42. data/app/assets/stylesheets/binda/settings/buttons.scss +36 -0
  43. data/app/assets/stylesheets/binda/settings/common.scss +36 -79
  44. data/app/assets/stylesheets/binda/settings/fonts.scss +88 -31
  45. data/app/assets/stylesheets/binda/settings/tiny_mce_overrides.scss +82 -0
  46. data/app/assets/stylesheets/binda/settings/variables.scss +36 -31
  47. data/app/controllers/binda/application_controller.rb +0 -2
  48. data/app/controllers/binda/assets_controller.rb +1 -1
  49. data/app/controllers/binda/boards_controller.rb +15 -4
  50. data/app/controllers/binda/components_controller.rb +30 -15
  51. data/app/controllers/binda/field_groups_controller.rb +19 -4
  52. data/app/controllers/binda/images_controller.rb +68 -0
  53. data/app/controllers/binda/repeaters_controller.rb +3 -12
  54. data/app/controllers/binda/structures_controller.rb +8 -1
  55. data/app/controllers/binda/videos_controller.rb +68 -0
  56. data/app/controllers/concerns/binda/fieldable_helpers.rb +113 -28
  57. data/app/controllers/concerns/binda/maintenance_helpers.rb +34 -0
  58. data/app/helpers/binda/components_helper.rb +26 -2
  59. data/app/helpers/binda/field_groups_helper.rb +16 -0
  60. data/app/helpers/binda/structures_helper.rb +6 -0
  61. data/app/models/binda/asset.rb +0 -4
  62. data/app/models/binda/b.rb +5 -0
  63. data/app/models/binda/board.rb +1 -18
  64. data/app/models/binda/choice.rb +48 -12
  65. data/app/models/binda/component.rb +20 -0
  66. data/app/models/binda/field_group.rb +2 -2
  67. data/app/models/binda/field_setting.rb +61 -28
  68. data/app/models/binda/gallery.rb +0 -2
  69. data/app/models/binda/image.rb +7 -0
  70. data/app/models/binda/relation.rb +79 -0
  71. data/app/models/binda/relation_link.rb +11 -0
  72. data/app/models/binda/repeater.rb +1 -1
  73. data/app/models/binda/selection.rb +0 -9
  74. data/app/models/binda/structure.rb +41 -39
  75. data/app/models/binda/video.rb +7 -0
  76. data/app/models/concerns/binda/default_helpers.rb +186 -0
  77. data/app/models/concerns/binda/fieldable_associations.rb +276 -30
  78. data/app/uploaders/binda/{asset → image}/image_uploader.rb +27 -9
  79. data/app/uploaders/binda/video/video_uploader.rb +51 -0
  80. data/app/views/binda/boards/edit.html.erb +6 -1
  81. data/app/views/binda/categories/_form.html.erb +41 -40
  82. data/app/views/binda/categories/edit.html.erb +1 -1
  83. data/app/views/binda/categories/index.html.erb +16 -13
  84. data/app/views/binda/components/edit.html.erb +6 -3
  85. data/app/views/binda/components/index.html.erb +69 -28
  86. data/app/views/binda/components/new.html.erb +1 -1
  87. data/app/views/binda/components/sort_index.html.erb +46 -0
  88. data/app/views/binda/field_groups/_form_body.html.erb +65 -44
  89. data/app/views/binda/field_groups/_form_item.html.erb +66 -23
  90. data/app/views/binda/field_groups/_form_item_choice.erb +34 -15
  91. data/app/views/binda/field_groups/_form_section.html.erb +6 -10
  92. data/app/views/binda/field_groups/_form_section_repeater.html.erb +3 -3
  93. data/app/views/binda/field_groups/edit.html.erb +1 -1
  94. data/app/views/binda/field_settings/_form_body.html.erb +5 -5
  95. data/app/views/binda/field_settings/edit.html.erb +1 -1
  96. data/app/views/binda/fieldable/_form_body.html.erb +93 -79
  97. data/app/views/binda/fieldable/_form_item_date.html.erb +14 -14
  98. data/app/views/binda/fieldable/_form_item_image.html.erb +35 -0
  99. data/app/views/binda/fieldable/_form_item_new_repeater.html.erb +12 -5
  100. data/app/views/binda/fieldable/_form_item_relation.html.erb +44 -0
  101. data/app/views/binda/fieldable/_form_item_repeater.html.erb +80 -58
  102. data/app/views/binda/fieldable/_form_item_selections.html.erb +109 -87
  103. data/app/views/binda/fieldable/_form_item_string.html.erb +19 -17
  104. data/app/views/binda/fieldable/_form_item_text.html.erb +19 -16
  105. data/app/views/binda/fieldable/_form_item_video.html.erb +32 -0
  106. data/app/views/binda/fieldable/_form_section.html.erb +27 -5
  107. data/app/views/binda/fieldable/_form_section_repeater.html.erb +30 -10
  108. data/app/views/binda/fieldable/_form_sidebar.html.erb +44 -20
  109. data/app/views/binda/manage/users/_form_body.html.erb +42 -37
  110. data/app/views/binda/manage/users/edit.html.erb +1 -1
  111. data/app/views/binda/manage/users/index.html.erb +21 -21
  112. data/app/views/binda/structures/_form_body.html.erb +54 -60
  113. data/app/views/binda/structures/_form_section.html.erb +32 -18
  114. data/app/views/binda/structures/_form_sidebar.html.erb +35 -0
  115. data/app/views/binda/structures/edit.html.erb +2 -2
  116. data/app/views/binda/structures/index.html.erb +21 -16
  117. data/app/views/binda/structures/sort_index.html.erb +36 -0
  118. data/app/views/{users → binda/users}/confirmations/new.html.erb +1 -1
  119. data/app/views/{users → binda/users}/mailer/confirmation_instructions.html.erb +0 -0
  120. data/app/views/{users → binda/users}/mailer/password_change.html.erb +0 -0
  121. data/app/views/{users → binda/users}/mailer/reset_password_instructions.html.erb +0 -0
  122. data/app/views/{users → binda/users}/mailer/unlock_instructions.html.erb +0 -0
  123. data/app/views/{users → binda/users}/passwords/edit.html.erb +1 -1
  124. data/app/views/{users → binda/users}/passwords/new.html.erb +1 -1
  125. data/app/views/{users → binda/users}/registrations/edit.html.erb +0 -0
  126. data/app/views/{users → binda/users}/registrations/new.html.erb +1 -1
  127. data/app/views/binda/users/sessions/_background.html.erb +139 -0
  128. data/app/views/binda/users/sessions/new.html.erb +62 -0
  129. data/app/views/{users → binda/users}/shared/_links.html.erb +3 -3
  130. data/app/views/{users → binda/users}/unlocks/new.html.erb +1 -1
  131. data/app/views/kaminari/_first_page.html.erb +11 -0
  132. data/app/views/kaminari/_gap.html.erb +8 -0
  133. data/app/views/kaminari/_last_page.html.erb +11 -0
  134. data/app/views/kaminari/_next_page.html.erb +11 -0
  135. data/app/views/kaminari/_page.html.erb +12 -0
  136. data/app/views/kaminari/_paginator.html.erb +19 -0
  137. data/app/views/kaminari/_prev_page.html.erb +11 -0
  138. data/app/views/layouts/binda/_flash.html.erb +1 -1
  139. data/app/views/layouts/binda/_header.html.erb +2 -6
  140. data/app/views/layouts/binda/_sidebar.html.erb +11 -4
  141. data/app/views/layouts/binda/application.html.erb +15 -7
  142. data/config/autoprefixer.yml +5 -0
  143. data/config/database.yml.travis +3 -0
  144. data/config/initializers/assets.rb +1 -0
  145. data/config/initializers/autoprefixer.yml +5 -0
  146. data/config/initializers/carrierwave.rb +32 -0
  147. data/config/initializers/devise.rb +14 -10
  148. data/config/initializers/devise_patch.rb +8 -8
  149. data/config/initializers/simple_form.rb +4 -0
  150. data/config/initializers/simple_form__fileupload.rb +121 -0
  151. data/config/initializers/{simple_form_bootstrap.rb → simple_form_custom.rb} +79 -50
  152. data/config/locales/en.yml +61 -3
  153. data/config/locales/it.yml +51 -0
  154. data/config/locales/simple_form.en.yml +3 -3
  155. data/config/routes.rb +41 -24
  156. data/db/migrate/1_create_binda_tables.rb +18 -7
  157. data/db/migrate/20171214140451_add_preview_mode.rb +5 -0
  158. data/lib/binda.rb +1 -1
  159. data/lib/binda/engine.rb +19 -23
  160. data/lib/binda/version.rb +2 -2
  161. data/lib/generators/binda/install/install_generator.rb +59 -54
  162. data/lib/generators/binda/install/templates/config/initializers/carrierwave.rb +11 -2
  163. data/lib/generators/binda/maintenance/USAGE +8 -0
  164. data/lib/generators/binda/maintenance/maintenance_generator.rb +50 -0
  165. data/lib/generators/binda/maintenance/templates/app/assets/javascripts/maintenance.js +0 -0
  166. data/lib/generators/binda/maintenance/templates/app/assets/stylesheets/maintenance.scss +0 -0
  167. data/lib/generators/binda/maintenance/templates/app/views/layouts/maintenance.html.erb +16 -0
  168. data/lib/generators/binda/maintenance/templates/config/initializers/maintenance.rb +1 -0
  169. data/lib/generators/binda/setup/setup_generator.rb +41 -30
  170. data/lib/tasks/add_default_helpers_class_task.rake +11 -0
  171. data/lib/tasks/add_video_feature_task.rake +15 -0
  172. data/lib/tasks/remove_orphan_fields_task.rake +16 -0
  173. data/lib/tasks/set_repeater_position_task.rake +13 -0
  174. data/lib/tasks/upgrade_to_v007_task.rake +72 -0
  175. data/lib/tasks/user_tasks.rake +14 -0
  176. data/vendor/assets/fonts/font-awesome/fa-brands-400.eot +0 -0
  177. data/vendor/assets/fonts/font-awesome/fa-brands-400.svg +978 -0
  178. data/vendor/assets/fonts/font-awesome/fa-brands-400.ttf +0 -0
  179. data/vendor/assets/fonts/font-awesome/fa-brands-400.woff +0 -0
  180. data/vendor/assets/fonts/font-awesome/fa-brands-400.woff2 +0 -0
  181. data/vendor/assets/fonts/font-awesome/fa-regular-400.eot +0 -0
  182. data/vendor/assets/fonts/font-awesome/fa-regular-400.svg +363 -0
  183. data/vendor/assets/fonts/font-awesome/fa-regular-400.ttf +0 -0
  184. data/vendor/assets/fonts/font-awesome/fa-regular-400.woff +0 -0
  185. data/vendor/assets/fonts/font-awesome/fa-regular-400.woff2 +0 -0
  186. data/vendor/assets/fonts/font-awesome/fa-solid-900.eot +0 -0
  187. data/vendor/assets/fonts/font-awesome/fa-solid-900.svg +1410 -0
  188. data/vendor/assets/fonts/font-awesome/fa-solid-900.ttf +0 -0
  189. data/vendor/assets/fonts/font-awesome/fa-solid-900.woff +0 -0
  190. data/vendor/assets/fonts/font-awesome/fa-solid-900.woff2 +0 -0
  191. data/vendor/assets/javascripts/GSAP/CSSPlugin.min.js +13 -0
  192. data/vendor/assets/javascripts/GSAP/EasePack.min.js +12 -0
  193. data/vendor/assets/javascripts/GSAP/ScrollToPlugin.min.js +12 -0
  194. data/vendor/assets/javascripts/GSAP/TweenLite.min.js +12 -0
  195. data/vendor/assets/javascripts/select2/i18n/af.js +3 -0
  196. data/vendor/assets/javascripts/select2/i18n/ar.js +3 -0
  197. data/vendor/assets/javascripts/select2/i18n/az.js +3 -0
  198. data/vendor/assets/javascripts/select2/i18n/bg.js +3 -0
  199. data/vendor/assets/javascripts/select2/i18n/bs.js +3 -0
  200. data/vendor/assets/javascripts/select2/i18n/ca.js +3 -0
  201. data/vendor/assets/javascripts/select2/i18n/cs.js +3 -0
  202. data/vendor/assets/javascripts/select2/i18n/da.js +3 -0
  203. data/vendor/assets/javascripts/select2/i18n/de.js +3 -0
  204. data/vendor/assets/javascripts/select2/i18n/dsb.js +3 -0
  205. data/vendor/assets/javascripts/select2/i18n/el.js +3 -0
  206. data/vendor/assets/javascripts/select2/i18n/en.js +3 -0
  207. data/vendor/assets/javascripts/select2/i18n/es.js +3 -0
  208. data/vendor/assets/javascripts/select2/i18n/et.js +3 -0
  209. data/vendor/assets/javascripts/select2/i18n/eu.js +3 -0
  210. data/vendor/assets/javascripts/select2/i18n/fa.js +3 -0
  211. data/vendor/assets/javascripts/select2/i18n/fi.js +3 -0
  212. data/vendor/assets/javascripts/select2/i18n/fr.js +3 -0
  213. data/vendor/assets/javascripts/select2/i18n/gl.js +3 -0
  214. data/vendor/assets/javascripts/select2/i18n/he.js +3 -0
  215. data/vendor/assets/javascripts/select2/i18n/hi.js +3 -0
  216. data/vendor/assets/javascripts/select2/i18n/hr.js +3 -0
  217. data/vendor/assets/javascripts/select2/i18n/hsb.js +3 -0
  218. data/vendor/assets/javascripts/select2/i18n/hu.js +3 -0
  219. data/vendor/assets/javascripts/select2/i18n/hy.js +3 -0
  220. data/vendor/assets/javascripts/select2/i18n/id.js +3 -0
  221. data/vendor/assets/javascripts/select2/i18n/is.js +3 -0
  222. data/vendor/assets/javascripts/select2/i18n/it.js +3 -0
  223. data/vendor/assets/javascripts/select2/i18n/ja.js +3 -0
  224. data/vendor/assets/javascripts/select2/i18n/km.js +3 -0
  225. data/vendor/assets/javascripts/select2/i18n/ko.js +3 -0
  226. data/vendor/assets/javascripts/select2/i18n/lt.js +3 -0
  227. data/vendor/assets/javascripts/select2/i18n/lv.js +3 -0
  228. data/vendor/assets/javascripts/select2/i18n/mk.js +3 -0
  229. data/vendor/assets/javascripts/select2/i18n/ms.js +3 -0
  230. data/vendor/assets/javascripts/select2/i18n/nb.js +3 -0
  231. data/vendor/assets/javascripts/select2/i18n/nl.js +3 -0
  232. data/vendor/assets/javascripts/select2/i18n/pl.js +3 -0
  233. data/vendor/assets/javascripts/select2/i18n/ps.js +3 -0
  234. data/vendor/assets/javascripts/select2/i18n/pt-BR.js +3 -0
  235. data/vendor/assets/javascripts/select2/i18n/pt.js +3 -0
  236. data/vendor/assets/javascripts/select2/i18n/ro.js +3 -0
  237. data/vendor/assets/javascripts/select2/i18n/ru.js +3 -0
  238. data/vendor/assets/javascripts/select2/i18n/sk.js +3 -0
  239. data/vendor/assets/javascripts/select2/i18n/sl.js +3 -0
  240. data/vendor/assets/javascripts/select2/i18n/sr-Cyrl.js +3 -0
  241. data/vendor/assets/javascripts/select2/i18n/sr.js +3 -0
  242. data/vendor/assets/javascripts/select2/i18n/sv.js +3 -0
  243. data/vendor/assets/javascripts/select2/i18n/th.js +3 -0
  244. data/vendor/assets/javascripts/select2/i18n/tr.js +3 -0
  245. data/vendor/assets/javascripts/select2/i18n/uk.js +3 -0
  246. data/vendor/assets/javascripts/select2/i18n/vi.js +3 -0
  247. data/vendor/assets/javascripts/select2/i18n/zh-CN.js +3 -0
  248. data/vendor/assets/javascripts/select2/i18n/zh-TW.js +3 -0
  249. data/vendor/assets/javascripts/select2/select2.full.min.js +1 -0
  250. data/vendor/assets/javascripts/select2/select2.min.js +1 -0
  251. data/vendor/assets/javascripts/tether.min.js +1 -0
  252. data/vendor/assets/stylesheets/{bootstrap-select.css → bootstrap/bootstrap-select.css} +0 -0
  253. data/vendor/assets/stylesheets/font-awesome/_animated.scss +6 -20
  254. data/vendor/assets/stylesheets/font-awesome/_bordered-pulled.scss +6 -11
  255. data/vendor/assets/stylesheets/font-awesome/_core.scss +11 -7
  256. data/vendor/assets/stylesheets/font-awesome/_fixed-width.scss +1 -1
  257. data/vendor/assets/stylesheets/font-awesome/_icons.scss +785 -787
  258. data/vendor/assets/stylesheets/font-awesome/_larger.scss +16 -6
  259. data/vendor/assets/stylesheets/font-awesome/_list.scss +7 -8
  260. data/vendor/assets/stylesheets/font-awesome/_mixins.scss +17 -20
  261. data/vendor/assets/stylesheets/font-awesome/_rotated-flipped.scss +9 -6
  262. data/vendor/assets/stylesheets/font-awesome/_screen-reader.scss +2 -2
  263. data/vendor/assets/stylesheets/font-awesome/_stacked.scss +19 -8
  264. data/vendor/assets/stylesheets/font-awesome/_variables.scss +795 -795
  265. data/vendor/assets/stylesheets/font-awesome/fa-brands.scss +21 -0
  266. data/vendor/assets/stylesheets/font-awesome/fa-regular.scss +22 -0
  267. data/vendor/assets/stylesheets/font-awesome/fa-solid.scss +23 -0
  268. data/vendor/assets/stylesheets/font-awesome/fontawesome.scss +20 -0
  269. data/vendor/assets/stylesheets/select2/select2.min.css +1 -0
  270. metadata +278 -125
  271. data/app/assets/javascripts/binda/components/form_item_asset.js +0 -25
  272. data/app/assets/stylesheets/binda/application.css +0 -6
  273. data/app/assets/stylesheets/binda/components/form_body.scss +0 -2
  274. data/app/assets/stylesheets/binda/components/form_item_asset.scss +0 -20
  275. data/app/assets/stylesheets/binda/components/form_section.scss +0 -40
  276. data/app/assets/stylesheets/binda/components/sidebar.scss +0 -131
  277. data/app/assets/stylesheets/binda/layout/components_index.scss +0 -9
  278. data/app/assets/stylesheets/binda/layout/dashboard.scss +0 -7
  279. data/app/assets/stylesheets/binda/layout/users_sign_in.scss +0 -29
  280. data/app/controllers/binda/bindings_controller.rb +0 -62
  281. data/app/controllers/concerns/binda/default_helpers.rb +0 -226
  282. data/app/helpers/binda/bindings_helper.rb +0 -4
  283. data/app/models/binda/binda.rb +0 -7
  284. data/app/models/binda/binding.rb +0 -24
  285. data/app/views/binda/bindings/_form.html.erb +0 -32
  286. data/app/views/binda/bindings/edit.html.erb +0 -6
  287. data/app/views/binda/bindings/index.html.erb +0 -31
  288. data/app/views/binda/bindings/new.html.erb +0 -5
  289. data/app/views/binda/bindings/show.html.erb +0 -19
  290. data/app/views/binda/field_groups/index.html.erb +0 -34
  291. data/app/views/binda/fieldable/_form_item_asset.html.erb +0 -20
  292. data/app/views/users/sessions/new.html.erb +0 -27
  293. data/lib/tasks/binda.rake +0 -79
  294. data/vendor/assets/fonts/font-awesome/FontAwesome.otf +0 -0
  295. data/vendor/assets/fonts/font-awesome/fontawesome-webfont.eot +0 -0
  296. data/vendor/assets/fonts/font-awesome/fontawesome-webfont.svg +0 -2671
  297. data/vendor/assets/fonts/font-awesome/fontawesome-webfont.ttf +0 -0
  298. data/vendor/assets/fonts/font-awesome/fontawesome-webfont.woff +0 -0
  299. data/vendor/assets/fonts/font-awesome/fontawesome-webfont.woff2 +0 -0
  300. data/vendor/assets/stylesheets/font-awesome/_path.scss +0 -15
  301. data/vendor/assets/stylesheets/font-awesome/font-awesome.scss +0 -18
@@ -2,8 +2,6 @@ module Binda
2
2
  class Gallery < ApplicationRecord
3
3
 
4
4
  # Associations
5
- has_many :bindings
6
- has_many :assets, through: :bindings
7
5
  belongs_to :fieldable, polymorphic: true
8
6
  belongs_to :field_setting
9
7
 
@@ -0,0 +1,7 @@
1
+ module Binda
2
+ class Image < Asset
3
+
4
+ mount_uploader :image, ImageUploader
5
+
6
+ end
7
+ end
@@ -0,0 +1,79 @@
1
+ module Binda
2
+ # Relation
3
+ #
4
+ # This model is used to connect components and boards to each others.
5
+ #
6
+ # `Relation` gathers all connection a `Component` record has with other records (being
7
+ # another `Component` or `Board`). It's possible to have multiple relations for the
8
+ # same component and each one can gather connections to several other components.
9
+ # Any relation has one owner and several dependents. A relation is a one-direction operation
10
+ # meaning that an owner can choose its dependents, not the other way around.
11
+ #
12
+ # Reference: https://www.railstutorial.org/book/following_users
13
+ #
14
+ # @example: owner = Binda::Component.first
15
+ # dependent = Binda::Component.last
16
+ # relation_setting = Binda::FieldSetting.create(field_type: relation, field_group_id: owner.structure.field_groups.first)
17
+ # relation = owner.relations.where(field_setting_id: relation_setting.id).first
18
+ # relation.dependent_components << dependent
19
+ class Relation < ApplicationRecord
20
+
21
+ belongs_to :fieldable, polymorphic: true
22
+ belongs_to :field_setting
23
+
24
+ # Relations are the connection between a Owner to its Dependents
25
+ # The Active Relation connects a Relation to a Dependent (which is can be a Component or a Board)
26
+ # The Passive Relation connects a Relation to a Owner (which is can be a Component or a Board)
27
+ has_many :dependent_relations, class_name: "RelationLink",
28
+ dependent: :destroy,
29
+ as: :owner
30
+
31
+
32
+ # Dependents are connected to a owner in a Passive Relation
33
+ # Strictly speaking you cannot choose an Owner for a Dependent,
34
+ # you can do just the opposite: choose a Dependent starting from a Owner
35
+ #
36
+ # The current version support components and boards separately
37
+ has_many :dependent_components, through: :dependent_relations,
38
+ source: :dependent,
39
+ source_type: "Binda::Component"
40
+
41
+
42
+ # Owner are connected to its Dependents in a Active Relation
43
+ # meaning its possible to connect a Owner to as many Dependents
44
+ # as it's needed.
45
+ #
46
+ # The current version support components and boards separately
47
+ has_many :dependent_boards, through: :dependent_relations,
48
+ source: :dependent,
49
+ source_type: "Binda::Board"
50
+
51
+
52
+ # Owner are connected to its Dependents in a Active Relation
53
+ # meaning its possible to connect a Owner to as many Dependents
54
+ # as it's needed.
55
+ #
56
+ # The current version support components and boards separately
57
+ has_many :dependent_repeaters, through: :dependent_relations,
58
+ source: :dependent,
59
+ source_type: "Binda::Repeater"
60
+
61
+
62
+ # Makes sure that the group of related components doesn't include the component that owns the group
63
+ # in other words makes sure that the component doesn't relate to itself
64
+ validates_each :dependent_component_ids do |model, attr, value|
65
+ if model.fieldable_type == 'Binda::Component' && value.include?(model.fieldable_id)
66
+ model.errors.add(attr, "#{FieldSetting.find(model.field_setting_id).name.capitalize} contains a reference to the current #{model.fieldable_type.constantize.find(model.fieldable_id).structure.name.capitalize}")
67
+ end
68
+ end
69
+
70
+ # Makes sure that the group of related components doesn't include the component that owns the group
71
+ # in other words makes sure that the component doesn't relate to itself
72
+ validates_each :dependent_board_ids do |model, attr, value|
73
+ if model.fieldable_type == 'Binda::Board' && value.include?(model.fieldable_id)
74
+ model.errors.add(attr, "#{FieldSetting.find(model.field_setting_id).name.capitalize} contains a reference to the current #{model.fieldable_type.constantize.find(model.fieldable_id).structure.name.capitalize}")
75
+ end
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,11 @@
1
+ module Binda
2
+ class RelationLink < ApplicationRecord
3
+
4
+ belongs_to :owner, class_name: "Binda::Relation"
5
+ belongs_to :dependent, polymorphic: true
6
+
7
+ validates :owner_id, presence: true
8
+ validates :dependent_id, presence: true
9
+
10
+ end
11
+ end
@@ -13,11 +13,11 @@ module Binda
13
13
 
14
14
  after_create :set_default_position
15
15
 
16
-
17
16
  # Set default position after create
18
17
  #
19
18
  # This methods ensure that every repeater instance has an explicit position.
20
19
  # The latest repeater created gets the highest position number.
20
+ # The first position is 1 (not 0).
21
21
  #
22
22
  # @return [object] Repeater instance
23
23
  def set_default_position
@@ -4,14 +4,5 @@ module Binda
4
4
  has_and_belongs_to_many :choices
5
5
  belongs_to :field_setting
6
6
 
7
- after_create :set_choice
8
-
9
- # Set the default choice if the field must have at least one.
10
- # This method run after the record is created.
11
- def set_choice
12
- return if self.field_setting.allow_null
13
- self.choices << self.field_setting.default_choice if self.choices.empty?
14
- end
15
-
16
7
  end
17
8
  end
@@ -2,10 +2,12 @@ module Binda
2
2
  class Structure < ApplicationRecord
3
3
 
4
4
  # Associations
5
- has_many :components
6
- has_one :board
7
- has_many :categories
8
- has_many :field_groups
5
+ has_many :components, dependent: :destroy
6
+ has_one :board, dependent: :destroy
7
+ has_many :categories, dependent: :destroy
8
+ has_many :field_groups, dependent: :destroy
9
+
10
+ has_and_belongs_to_many :field_settings
9
11
 
10
12
  # Validations
11
13
  validates :name, presence: true
@@ -19,15 +21,15 @@ module Binda
19
21
 
20
22
  after_create :add_default_field_group
21
23
  after_create :add_instance_details
22
- after_create :set_default_position
24
+ after_create :set_default_position
23
25
 
24
26
  # Friendly id preference on slug generation
25
27
  #
26
28
  # Method inherited from friendly id
27
29
  # @see https://github.com/norman/friendly_id/issues/436
28
- def should_generate_new_friendly_id?
29
- slug.blank? || name_changed?
30
- end
30
+ def should_generate_new_friendly_id?
31
+ slug.blank? || name_changed?
32
+ end
31
33
 
32
34
  #
33
35
  # Sets the validation rules to accept and save an attribute
@@ -51,39 +53,39 @@ module Binda
51
53
  # field group called 'General Details' after the structure is created.
52
54
  # @return [redirect]
53
55
  def add_default_field_group
54
- # Creates a default empty field group
55
- field_group = self.field_groups.build( name: 'General Details', position: 1 )
56
- # Unless there is a problem...
57
- unless field_group.save
58
- return redirect_to structure_path( self.slug ), flash: { error: 'General Details group hasn\'t been created' }
59
- end
60
- end
56
+ # Creates a default empty field group
57
+ field_group = self.field_groups.build( name: 'General Details', position: 1 )
58
+ # Unless there is a problem...
59
+ unless field_group.save
60
+ return redirect_to structure_path( self.slug ), flash: { error: 'General Details group hasn\'t been created' }
61
+ end
62
+ end
61
63
 
62
- # Add details based on instance type
63
- #
64
- # If instance_type is set to 'setting' it generates a default Binda::Setting instance after creation.
65
- # The generated instance will be associated to the structure and named after it.
66
- # It also disable categories (this could be a different method, or method could be more explicit)
67
- def add_instance_details
68
- if self.instance_type == 'board'
69
- self.update_attribute 'has_categories', false
70
- board = self.build_board( name: self.name )
71
- unless board.save
72
- return redirect_to structure_path( self.slug ), flash: { error: 'The board instance hasn\'t been created' }
73
- end
74
- end
75
- end
64
+ # Add details based on instance type
65
+ #
66
+ # If instance_type is set to 'setting' it generates a default Binda::Setting instance after creation.
67
+ # The generated instance will be associated to the structure and named after it.
68
+ # It also disable categories (this could be a different method, or method could be more explicit)
69
+ def add_instance_details
70
+ if self.instance_type == 'board'
71
+ self.update_attribute 'has_categories', false
72
+ board = self.build_board( name: self.name )
73
+ unless board.save
74
+ return redirect_to structure_path( self.slug ), flash: { error: 'The board instance hasn\'t been created' }
75
+ end
76
+ end
77
+ end
76
78
 
77
- # Set default position after create
78
- #
79
- # This methods ensure that every repeater instance has an explicit position.
80
- # The latest repeater created gets the highest position number.
81
- #
82
- # @return [object] Repeater instance
83
- def set_default_position
84
- position = Structure.all.length
85
- self.update_attribute 'position', position
86
- end
79
+ # Set default position after create
80
+ #
81
+ # This methods ensure that every repeater instance has an explicit position.
82
+ # The latest repeater created gets the highest position number.
83
+ #
84
+ # @return [object] Repeater instance
85
+ def set_default_position
86
+ position = Structure.all.length
87
+ self.update_attribute 'position', position
88
+ end
87
89
 
88
90
  end
89
91
  end
@@ -0,0 +1,7 @@
1
+ module Binda
2
+ class Video < Asset
3
+
4
+ mount_uploader :video, VideoUploader
5
+
6
+ end
7
+ end
@@ -0,0 +1,186 @@
1
+ module Binda
2
+ # Binda comes with a bunch of helpers to make life easier.
3
+ module DefaultHelpers
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ end
8
+
9
+ class_methods do
10
+
11
+ # Get components
12
+ #
13
+ # This method retrieves **all components** belonging to a specific structure.
14
+ # See the following example.
15
+ #
16
+ # @example Assuming you have two structures 'page' and 'post':
17
+ # B.get_components()
18
+ # # returns all components from all structures
19
+ #
20
+ # B.get_components(['page', 'post'])
21
+ # # returns pages and posts, i.e. all components belonging to 'page' and 'post' structures
22
+ #
23
+ # B.get_components('page')
24
+ # # returns all pages
25
+ #
26
+ # B.get_components('page').find_by(slug: 'my-first-page')
27
+ # # returns `my-first-page`
28
+ #
29
+ # # expand query
30
+ # B.get_components('page').where(publish_state: 'published').order('position')
31
+ #
32
+ # # reduce N+1 query issue by including dependencies
33
+ # B.get_components('page').includes(:strings, :texts, repeaters: [:images, :selections])
34
+ #
35
+ # @param slug [string] The slug of the structure to which the components belong
36
+ # @param slug [array] The slugs of the structures to which the components belongs
37
+ #
38
+ # @return [ActiveRelation Object] if slug is nil or is an array
39
+ #
40
+ def get_components slug = nil
41
+ if slug.nil?
42
+ Component.all
43
+ else
44
+ # Generate query
45
+ Component.where( structure_id: Structure.where( slug: slug ) )
46
+ end
47
+ end
48
+
49
+ # Get boards
50
+ #
51
+ # This method retrieves **boards**.
52
+ # See the following example.
53
+ #
54
+ # @example Assuming you have a `default-dashboard`
55
+ # B.get_boards()
56
+ # # returns all boards of all structures
57
+ #
58
+ # B.get_boards('default-dashboard').first
59
+ # # returns the board
60
+ #
61
+ # # reduce N+1 query issue by including dependencies
62
+ # B.get_boards('default-dashboard').includes(:strings, :texts, repeaters: [:images, :selections]).first
63
+ #
64
+ # @param slug [string] The slug of the structure on which the board is based
65
+ # @param slug [array] The slugs of the structures to which the board belongs
66
+ #
67
+ # @return [ActiveRelation Object] if slug is nil or is an array
68
+ #
69
+ def get_boards slug = nil
70
+ if slug.nil?
71
+ Board.all
72
+ else
73
+ Board.where( structure_id: Structure.where( slug: slug ) )
74
+ end
75
+ end
76
+
77
+ # Get categories
78
+ #
79
+ # This method retrieves **categories**.
80
+ # See the following example.
81
+ #
82
+ # @example Assuming you have two structures 'page' and 'post':
83
+ # B.get_categories()
84
+ # # returns all categories belonging to all structures
85
+ #
86
+ # B.get_categories('page')
87
+ # # returns all categories belonging to the 'page' structure
88
+ #
89
+ # B.get_categories(['page', 'post'])
90
+ # # returns all categories belonging to 'page' structure and the ones belonging to 'post' structure
91
+ #
92
+ # @param slug [string] The slug of the structure to which categories belong
93
+ # @param slug [array] The slugs of the structures to which categories belong
94
+ #
95
+ # @return [ActiveRelation Object]
96
+ #
97
+ def get_categories slug = nil
98
+ if slug.nil?
99
+ Category.all
100
+ else
101
+ Category.where( structure_id: Structure.where( slug: slug ) )
102
+ end
103
+ end
104
+
105
+ # Get field settings
106
+ #
107
+ # This method retrieves **field settings**.
108
+ # See the following example.
109
+ #
110
+ # @example Assuming you have two field settings 'subtitle' and 'description':
111
+ # B.get_field_settings()
112
+ # # returns all field settings
113
+ #
114
+ # B.get_field_settings('subtitle')
115
+ # # returns an ActiveRelation (a sort of Array) containing the 'subtitle' field setting
116
+ #
117
+ # @param slug [string] The slug of a specific field setting
118
+ # @param slug [array] The slugs of the selected field settings
119
+ #
120
+ # @return [ActiveRelation Object]
121
+ #
122
+ def get_field_settings slug = nil
123
+ if slug.nil?
124
+ FieldSetting.all
125
+ else
126
+ FieldSetting.where(slug: slug)
127
+ end
128
+ end
129
+
130
+ # Get each owner of all relations with the specified slug (or slugs)
131
+ #
132
+ # @param slug [string] The slug of the field setting to which the relations belong
133
+ # @param slug [array] The slugs of the field settings to which the relations belong
134
+ #
135
+ # @return [Array]
136
+ #
137
+ def get_relation_owners field_slug
138
+ owner_class = Structure.includes(field_groups: :field_settings)
139
+ .where(binda_field_settings: {id: FieldSetting.where(slug: field_slug)})
140
+ .first
141
+ obj = "Binda::#{owner_class.instance_type.classify}".constantize
142
+ .distinct
143
+ .includes(relations: :dependent_components)
144
+ .where(binda_relations: {field_setting_id: FieldSetting.where(slug: field_slug)})
145
+ raise ArgumentError, "There isn't any instance with a relation associated to the current slug (#{field_slug}).", caller if obj.nil?
146
+ return obj
147
+ end
148
+
149
+
150
+ # Get each dependent of all relations with the specified slug (or slugs)
151
+ #
152
+ # This can be useful to retrieve only the instances which have a owner. For example, you have several
153
+ # 'event' components, where each one is related to several 'artist' components with a 'partecipants'
154
+ # relation field where every event owns some artists.
155
+ # If you want to retrieve all artists which have been involved in at least one event you can try with
156
+ # `B.get_relation_dependents('partecipants')`.
157
+ #
158
+ # You can also ask for all instance type of dependent or specify 'components' or 'boards' using
159
+ # the second parameter.
160
+ #
161
+ # @param slug [string] The slug of the field setting to which the relations belong
162
+ #
163
+ # @return [Array]
164
+ #
165
+ def get_relation_dependents field_slug, instance_type = nil
166
+ raise ArgumentError, "There isn't any instance named: #{instance_type}. Make sure is either 'component' or 'board'", caller if !instance_type.nil? && ['board','component'].include?(instance_type)
167
+
168
+ dependents = []
169
+ if instance_type != 'board'
170
+ dependent_components = Component.distinct
171
+ .includes(:owner_components)
172
+ .where(binda_relations: {field_setting_id: FieldSetting.where(slug: field_slug)})
173
+ dependents = [ *dependents, *dependent_components ]
174
+ elsif instance_type != 'component'
175
+ dependent_boards = Board.distinct
176
+ .includes(:owner_components)
177
+ .where(binda_relations: {field_setting_id: FieldSetting.where(slug: field_slug)})
178
+ dependents = [ *dependents, *dependent_boards ]
179
+ end
180
+
181
+ raise ArgumentError, "There isn't any instance with a relation associated to the current slug (#{field_slug}).", caller unless dependents.any?
182
+ return dependents
183
+ end
184
+ end
185
+ end
186
+ end
@@ -15,22 +15,74 @@ module Binda
15
15
  # - get_fieldables (see here below)
16
16
  # - get_field_types (see here below)
17
17
  # - component_params (app/controllers/binda/components_controller.rb)
18
+
18
19
  has_many :texts, as: :fieldable, dependent: :delete_all
19
20
  has_many :strings, as: :fieldable, dependent: :delete_all
20
21
  has_many :dates, as: :fieldable, dependent: :delete_all
21
22
  has_many :galleries, as: :fieldable, dependent: :delete_all
22
23
  has_many :assets, as: :fieldable, dependent: :delete_all
24
+ has_many :images, as: :fieldable, dependent: :delete_all
25
+ has_many :videos, as: :fieldable, dependent: :delete_all
23
26
  has_many :radios, as: :fieldable, dependent: :delete_all
24
27
  has_many :selections, as: :fieldable, dependent: :delete_all
25
28
  has_many :checkboxes, as: :fieldable, dependent: :delete_all
26
29
  # Repeaters need destroy_all, not delete_all
27
30
  has_many :repeaters, as: :fieldable, dependent: :destroy
31
+ has_many :relations, as: :fieldable, dependent: :destroy
32
+
33
+
34
+ has_many :owner_relations, class_name: "RelationLink",
35
+ dependent: :destroy,
36
+ as: :dependent
37
+
38
+ # Owner are connected to its Dependents in a Active Relation
39
+ # meaning its possible to connect a Owner to as many Dependents
40
+ # as it's needed.
41
+ #
42
+ # The current version support components and boards separately
43
+ has_many :owner_components, through: :owner_relations,
44
+ source: :owner
28
45
 
29
- # has_many :bindings
30
- # has_many :assets, class_name: 'Admin::Asset', through: :bindings
46
+ has_many :owner_boards, through: :owner_relations,
47
+ source: :owner
31
48
 
32
- accepts_nested_attributes_for :texts, :strings, :dates, :assets, :galleries, :repeaters, :radios, :selections, :checkboxes, allow_destroy: true
49
+ has_many :owner_repeaters, through: :owner_relations,
50
+ source: :owner
33
51
 
52
+
53
+ accepts_nested_attributes_for :texts, :strings, :dates, :assets, :images, :videos, :galleries, :repeaters, :radios, :selections, :checkboxes, :relations, allow_destroy: true
54
+
55
+ validates_associated :texts
56
+ validates_associated :strings
57
+ validates_associated :dates
58
+ validates_associated :assets
59
+ validates_associated :images
60
+ validates_associated :videos
61
+ validates_associated :repeaters
62
+ validates_associated :radios
63
+ validates_associated :selections
64
+ validates_associated :checkboxes
65
+ validates_associated :relations
66
+
67
+ after_save :generate_fields
68
+
69
+ # Uncomment these "validate do" loop to better debug validation.
70
+ # This makes method gather errors of the associated records and
71
+ # make them available to the instance object. After using this method
72
+ # you will be able to see the actual error inside `instance.errors` array.
73
+ # Example: @component.errors #=> [ ... ]
74
+ #
75
+ # validate do |instance|
76
+ # instance.texts.each do |text|
77
+ # binding.pry
78
+ # next if text.valid?
79
+ # text.errors.full_messages.each do |msg|
80
+ # # you can customize the error message here:
81
+ # errors[:base] << "Error in #{text.field_setting.name} (text): #{msg}"
82
+ # end
83
+ # end
84
+ # end
85
+
34
86
  end
35
87
 
36
88
  # Get the object related to that field setting
@@ -40,15 +92,15 @@ module Binda
40
92
  # @return [string] Returns the content of the text
41
93
  # @return [error] Raise an error if no record is found
42
94
  def get_text field_slug
43
- obj = self.texts.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type = 'Binda::Text' }
95
+ obj = self.texts.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type != 'Binda::String' }
44
96
  unless obj.nil?
45
97
  obj.content
46
98
  else
47
99
  you_mean_string = !self.strings.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type = 'Binda::String' }.nil?
48
100
  if you_mean_string
49
- raise ArgumentError, "This slug is associated to a string not a text. Use get_string() instead.", caller
101
+ raise ArgumentError, "This slug (#{field_slug}) is associated to a string not a text. Use get_string() instead on instance (#{self.class.name} ##{self.id}).", caller
50
102
  else
51
- raise ArgumentError, "There isn't any text associated to the current slug.", caller
103
+ raise ArgumentError, "There isn't any text associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller
52
104
  end
53
105
  end
54
106
  end
@@ -58,7 +110,8 @@ module Binda
58
110
  # @param field_slug [string] The slug of the field setting
59
111
  # @return [boolean]
60
112
  def has_text field_slug
61
- obj = self.texts.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type = 'Binda::Text' }
113
+ obj = self.texts.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type != 'Binda::String' }
114
+ raise ArgumentError, "There isn't any text associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
62
115
  if obj.present?
63
116
  return !obj.content.blank?
64
117
  else
@@ -73,15 +126,15 @@ module Binda
73
126
  # @return [string] Returns the content of the string
74
127
  # @return [error] Raise an error if no record is found
75
128
  def get_string field_slug
76
- obj = self.strings.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type = 'Binda::String' }
129
+ obj = self.strings.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type == 'Binda::String' }
77
130
  unless obj.nil?
78
131
  obj.content
79
132
  else
80
133
  you_mean_text = !self.strings.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type = 'Binda::Text' }.nil?
81
134
  if you_mean_text
82
- raise ArgumentError, "This slug is associated to a text not a string. Use get_text() instead.", caller
135
+ raise ArgumentError, "This slug (#{field_slug}) is associated to a text not a string. Use get_text() instead on instance (#{self.class.name} ##{self.id}).", caller
83
136
  else
84
- raise ArgumentError, "There isn't any string associated to the current slug.", caller
137
+ raise ArgumentError, "There isn't any string associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller
85
138
  end
86
139
  end
87
140
  end
@@ -91,7 +144,8 @@ module Binda
91
144
  # @param field_slug [string] The slug of the field setting
92
145
  # @return [boolean]
93
146
  def has_string field_slug
94
- obj = self.strings.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type = 'Binda::String' }
147
+ obj = self.strings.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) && t.type == 'Binda::String' }
148
+ raise ArgumentError, "There isn't any string associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
95
149
  if obj.present?
96
150
  return !obj.content.blank?
97
151
  else
@@ -104,8 +158,11 @@ module Binda
104
158
  # @param field_slug [string] The slug of the field setting
105
159
  # @return [boolean]
106
160
  def has_image field_slug
107
- obj = self.assets.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }.image
108
- return obj.present?
161
+ obj = self.images.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
162
+ # Alternative query
163
+ # obj = Image.where(field_setting_id: FieldSetting.get_id( field_slug ), fieldable_id: self.id, fieldable_type: self.class.to_s ).first
164
+ raise ArgumentError, "There isn't any image associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
165
+ return obj.image.present?
109
166
  end
110
167
 
111
168
  # Get the image url based on the size provided,
@@ -139,7 +196,23 @@ module Binda
139
196
  # @return [string] The info requested if present
140
197
  # @return [boolean] Returns false if no info is found or if image isn't found
141
198
  def get_image_info field_slug, size, info
142
- obj = self.assets.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
199
+ obj = self.images.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
200
+ # Alternative query
201
+ # obj = Image.where(field_setting_id: FieldSetting.get_id( field_slug ), fieldable_id: self.id, fieldable_type: self.class.to_s ).first
202
+ raise ArgumentError, "There isn't any image associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
203
+ if obj.image.present?
204
+ if obj.image.respond_to?(size) && %w[thumb medium large].include?(size)
205
+ obj.image.send(size).send(info)
206
+ else
207
+ obj.image.send(info)
208
+ end
209
+ end
210
+ end
211
+
212
+ def get_image_dimension field_slug
213
+ obj = self.images.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
214
+
215
+ raise ArgumentError, "There isn't any image associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
143
216
  if obj.image.present?
144
217
  if obj.image.respond_to?(size) && %w[thumb medium large].include?(size)
145
218
  obj.image.send(size).send(info)
@@ -149,6 +222,56 @@ module Binda
149
222
  end
150
223
  end
151
224
 
225
+ # Check if the field has an attached video
226
+ #
227
+ # @param field_slug [string] The slug of the field setting
228
+ # @return [boolean]
229
+ def has_video field_slug
230
+ obj = self.videos.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
231
+ # Alternative query
232
+ # obj = Image.where(field_setting_id: FieldSetting.get_id( field_slug ), fieldable_id: self.id, fieldable_type: self.class.to_s ).first
233
+ raise ArgumentError, "There isn't any video associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
234
+ return obj.video.present?
235
+ end
236
+
237
+ # Get the video url based on the size provided,
238
+ # default is Carrierwave default (usually the real size)
239
+ #
240
+ # @param field_slug [string] The slug of the field setting
241
+ # @param size [string] The size. It can be 'thumb' 200x200 cropped,
242
+ # 'medium' 700x700 max size, 'large' 1400x1400 max size, or blank
243
+ # @return [string] The url of the video
244
+ def get_video_url field_slug
245
+ get_video_info( field_slug, 'url' )
246
+ end
247
+
248
+ # Get the video path based on the size provided,
249
+ # default is Carrierwave default (usually the real size)
250
+ #
251
+ # @param field_slug [string] The slug of the field setting
252
+ # @param size [string] The size. It can be 'thumb' 200x200 cropped,
253
+ # 'medium' 700x700 max size, 'large' 1400x1400 max size, or blank
254
+ # @return [string] The url of the video
255
+ def get_video_path field_slug
256
+ get_video_info( field_slug, 'path' )
257
+ end
258
+
259
+ # Get the object related to that field setting
260
+ #
261
+ # @param field_slug [string] The slug of the field setting
262
+ # @param info [string] String of the info to be retrieved
263
+ # @return [string] The info requested if present
264
+ # @return [boolean] Returns false if no info is found or if image isn't found
265
+ def get_video_info field_slug, info
266
+ obj = self.videos.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
267
+ # Alternative query
268
+ # obj = video.where(field_setting_id: FieldSetting.get_id( field_slug ), fieldable_id: self.id, fieldable_type: self.class.to_s ).first
269
+ raise ArgumentError, "There isn't any video associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
270
+ if obj.video.present?
271
+ obj.video.send(info)
272
+ end
273
+ end
274
+
152
275
  # Check if the field has an attached date
153
276
  #
154
277
  # @param field_slug [string] The slug of the field setting
@@ -156,6 +279,7 @@ module Binda
156
279
  # @return [boolean] Reutrn false if nothing is found
157
280
  def has_date field_slug
158
281
  obj = self.dates.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
282
+ raise ArgumentError, "There isn't any date associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
159
283
  if obj.present?
160
284
  return !obj.date.nil?
161
285
  else
@@ -169,7 +293,8 @@ module Binda
169
293
  # @return [boolean]
170
294
  def get_date field_slug
171
295
  obj = self.dates.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
172
- obj.date unless obj.nil?
296
+ raise ArgumentError, "There isn't any date associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
297
+ obj.date
173
298
  end
174
299
 
175
300
  # Check if exists any repeater with that slug
@@ -178,6 +303,7 @@ module Binda
178
303
  # @return [boolean]
179
304
  def has_repeater field_slug
180
305
  obj = self.repeaters.find_all{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
306
+ raise ArgumentError, "There isn't any repeater associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
181
307
  return obj.present?
182
308
  end
183
309
 
@@ -187,7 +313,8 @@ module Binda
187
313
  # @return [hash]
188
314
  def get_repeater field_slug
189
315
  obj = self.repeaters.find_all{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
190
- obj.sort_by(&:position) unless obj.nil?
316
+ raise ArgumentError, "There isn't any repeater associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
317
+ obj.sort_by(&:position)
191
318
  end
192
319
 
193
320
  # Get the radio choice
@@ -196,46 +323,165 @@ module Binda
196
323
  # only the first one will be retrieved.
197
324
  #
198
325
  # @param field_slug [string] The slug of the field setting
199
- # @return [hash] A hash of containing the label and value of the selected choice.
326
+ # @return [hash] A hash of containing the label and value of the selected choice. `{ label: 'the label', value: 'the value'}`
200
327
  def get_radio_choice field_slug
201
- obj = self.radios.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
328
+ field_setting = FieldSetting.find_by(slug:field_slug)
329
+ obj = self.radios.find{ |t| t.field_setting_id == field_setting.id }
330
+ raise ArgumentError, "There isn't any radio associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
331
+ raise "There isn't any choice available for the current radio (#{field_slug}) on instance (#{self.class.name} ##{self.id})." unless field_setting.choices.any?
202
332
  return { label: obj.choices.first.label, value: obj.choices.first.value }
203
333
  end
204
334
 
205
335
  # Get the select choices
206
336
  #
207
337
  # @param field_slug [string] The slug of the field setting
208
- # @return [hash] A hash of containing the label and value of the selected choice.
338
+ # @return [hash] A hash of containing the label and value of the selected choice. `{ label: 'the label', 'value': 'the value'}`
209
339
  def get_selection_choice field_slug
210
- # select cannot be chosen has variable name, therefore is prefixed with 's'
211
- obj = self.selections.find{ |t| t.field_setting_id = FieldSetting.get_id( field_slug ) }
340
+ field_setting = FieldSetting.find_by(slug:field_slug)
341
+ obj = self.selections.find{ |t| t.field_setting_id == field_setting.id }
342
+ raise ArgumentError, "There isn't any radio associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
343
+ raise "There isn't any choice available for the current radio (#{field_slug}) on instance (#{self.class.name} ##{self.id})." unless field_setting.choices.any?
212
344
  return { label: obj.choices.first.label, value: obj.choices.first.value }
213
345
  end
214
346
 
215
347
  # Get the checkbox choice
216
348
  #
217
349
  # @param field_slug [string] The slug of the field setting
218
- # @return [hash] A hash of labels and values of the selected choices.
350
+ # @return [array] An array of labels and values of the selected choices. `[{ label: '1st label', value: '1st-value'}, { label: '2nd label', value: '2nd-value'}]`
219
351
  def get_checkbox_choices field_slug
220
- obj = self.checkboxes.find{ |t| t.field_setting_id = FieldSetting.get_id( field_slug ) }
221
- obj_hash = {}
352
+ field_setting = FieldSetting.find_by(slug:field_slug)
353
+ obj = self.checkboxes.find{ |t| t.field_setting_id == field_setting.id }
354
+ raise ArgumentError, "There isn't any checkbox associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
355
+ raise "There isn't any choice available for the current radio (#{field_slug}) on instance (#{self.class.name} ##{self.id})." unless field_setting.choices.any?
356
+ obj_array = []
222
357
  obj.choices.order('label').each do |o|
223
- obj_hash << { label: obj.choices.first.label, value: obj.choices.first.value }
358
+ obj_array << { label: o.label, value: o.value }
224
359
  end
225
- return obj_hash
360
+ return obj_array
361
+ end
362
+
363
+ # Check if has related components
364
+ #
365
+ # @param field_slug [string] The slug of the field setting
366
+ # @return [boolean]
367
+ def has_related_components field_slug
368
+ obj = self.relations.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
369
+ raise ArgumentError, "There isn't any related field associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
370
+ return obj.dependent_relations.any?
371
+ end
372
+
373
+ # Alias for has_related_components
374
+ def has_dependent_components field_slug
375
+ has_related_components field_slug
376
+ end
377
+
378
+ # Get related components
379
+ #
380
+ # @param field_slug [string] The slug of the field setting
381
+ # @return [array] An array of components
382
+ def get_related_components field_slug
383
+ obj = self.relations.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
384
+ raise ArgumentError, "There isn't any related field associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
385
+ return obj.dependent_relations.map{|relation| relation.dependent}
386
+ end
387
+
388
+ # Alias for get_related_components
389
+ def get_dependent_components field_slug
390
+ get_related_components field_slug
391
+ end
392
+
393
+ # Get all components which owns a relation where the current instance is a dependent
394
+ #
395
+ # @param field_slug [string] The slug of the field setting of the relation
396
+ # @return [array] An array of components and/or boards
397
+ def get_owner_components field_slug
398
+ # obj = self.owner_relations.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
399
+ obj = Relation.where(field_setting_id: B.get_field_settings(field_slug)).includes(dependent_relations: :dependent).where(binda_relation_links: {dependent_type: self.class.name})
400
+ raise ArgumentError, "There isn't any relation associated to the current slug (#{field_slug}) where the current instance (#{self.class.name} ##{self.id}) is a dependent.", caller if obj.nil?
401
+ return obj
402
+ end
403
+
404
+ # Check if has related boards
405
+ #
406
+ # @param field_slug [string] The slug of the field setting
407
+ # @return [boolean]
408
+ def has_related_boards field_slug
409
+ obj = self.relations.find{ |t| t.field_setting_id == FieldSetting.get_id( field_slug ) }
410
+ raise ArgumentError, "There isn't any related field associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
411
+ return obj.dependent_relations.any?
412
+ end
413
+
414
+ # Get related boards
415
+ #
416
+ # @param field_slug [string] The slug of the field setting
417
+ # @return [array] An array of boards
418
+ def get_related_boards field_slug
419
+ obj = self.relations.find{ |t| t.field_setting_idid == FieldSetting.get_id( field_slug ) }
420
+ raise ArgumentError, "There isn't any related field associated to the current slug (#{field_slug}) on instance (#{self.class.name} ##{self.id}).", caller if obj.nil?
421
+ return obj.dependent_relations.map{|relation| relation.dependent}
226
422
  end
227
423
 
228
424
  # Find or create a field by field setting and field type
229
- # This is used in Binda's form
425
+ #
426
+ # This is used in Binda's editor views.
427
+ #
428
+ # Please, check the code to know more about the way this method works as it's pretty complex yet important.
230
429
  #
231
430
  # @param field_setting_id [string] The field setting id
232
431
  # @param field_type [string] THe field type
233
432
  def find_or_create_a_field_by field_setting_id, field_type
234
- if FieldSetting.get_field_classes.include?( field_type.capitalize ) && field_setting_id.is_a?( Integer )
235
- self.send( field_type.pluralize ).find_or_create_by( field_setting_id: field_setting_id )
433
+ if FieldSetting.get_field_classes.include?( field_type.classify ) && field_setting_id.is_a?( Integer )
434
+ # It's mandatory to use `select{}.first`!!!
435
+ # If you use any ActiveRecord method (like `where` of `find`) the validation errors are wiped out
436
+ # from the object and not rendered next to the form in the editor view
437
+ obj = self.send( field_type.pluralize ).select{|rf| rf.field_setting_id == field_setting_id}.first
438
+ if obj.nil?
439
+ # As we are using the `select{}.first` method, asynchronous requests passing through
440
+ # this method will create some inconsistentcy between this `self` object and the real object.
441
+ # In other words `self` should be reloaded, but we won't reload it otherwise we will
442
+ # erase the errors shipped with it initially. Therefore we will check again with `find_or_create_by!`.
443
+ # This leads to another issue, which is: errors coming from asynchronous requests won't be considered
444
+ # as the `self` object is the initial one, not the "updated" one. At the current time this is not
445
+ # a problem because the only asynchronous requests are for brand new records which don't need validation.
446
+ return self.send( field_type.pluralize ).find_or_create_by!( field_setting_id: field_setting_id )
447
+ else
448
+ return obj
449
+ end
236
450
  else
237
- raise ArgumentError, "One parameter in find_or_create_a_field_by() is not correct.", caller
451
+ raise ArgumentError, "One parameter in find_or_create_a_field_by() is not correct on instance (#{self.class.name} ##{self.id}).", caller
238
452
  end
239
453
  end
454
+
455
+ # This method is called upon the creation/update of a fieldable record (component, board or repeater)
456
+ # and generates all fields related to each field settings which belongs to it.
457
+ #
458
+ # This avoids any situation in which, for example, a component have a field setting for a text
459
+ # but there is no text (meaning `Binda::Text` instance) that correspond to that field setting.
460
+ # This causes issues when looping a bunch of components which will thow a error if you try to access
461
+ # a component field, as some might have it some might not. This make sure that you can always expect
462
+ # to find a field instance which might be empty, but certainly it exists.
463
+ #
464
+ # TODO check if find_or_create_a_field_by method should be used instead (it's used in editors views)
465
+ #
466
+ def generate_fields
467
+ # If this is a component or a board
468
+ if self.respond_to?('structure')
469
+ field_settings = FieldSetting.where(field_group_id: FieldGroup.where(structure_id: self.structure.id))
470
+ field_settings.each do |field_setting|
471
+ "Binda::#{field_setting.field_type.classify}".constantize.find_or_create_by!(
472
+ fieldable_id: self.id, fieldable_type: self.class.name, field_setting_id: field_setting.id )
473
+ end
474
+ # If this is a repeater
475
+ else
476
+ self.field_setting.children.each do |field_setting|
477
+ "Binda::#{field_setting.field_type.classify}".constantize.find_or_create_by!(
478
+ fieldable_id: self.id, fieldable_type: self.class.name, field_setting_id: field_setting.id )
479
+ end
480
+ end
481
+ end
482
+
483
+ # TODO: Update all helpers replacing `find` method with ruby `select`.
484
+ # This should improve performance avoiding generating useless ActiveRecord objects.
485
+
240
486
  end
241
487
  end