This repository has been archived by the owner on Jul 19, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6
/
fieldable_associations.rb
152 lines (132 loc) · 6.74 KB
/
fieldable_associations.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
module Binda
# Fieldable associations are Binda's core feature.
#
# They provide classes like `Binda::Component` and `Binda::Board` with a collection of fields
# (texts, assets, dates and so on) to store data in a simple yet powerful way.
module FieldableAssociations
extend ActiveSupport::Concern
included do
include FieldableAssociationHelpers
# Fieldable Associations
#
# If you add a new field remember to update:
# - get_fieldables (see here below)
# - get_field_types (see here below)
# - component_params (app/controllers/binda/components_controller.rb)
has_many :texts, as: :fieldable, dependent: :delete_all
has_many :strings, as: :fieldable, dependent: :delete_all
has_many :dates, as: :fieldable, dependent: :delete_all
has_many :galleries, as: :fieldable, dependent: :delete_all
has_many :assets, as: :fieldable, dependent: :delete_all
has_many :images, as: :fieldable, dependent: :delete_all
has_many :videos, as: :fieldable, dependent: :delete_all
has_many :audios, as: :fieldable, dependent: :delete_all
has_many :radios, as: :fieldable, dependent: :delete_all
has_many :selections, as: :fieldable, dependent: :delete_all
has_many :checkboxes, as: :fieldable, dependent: :delete_all
has_many :svgs, as: :fieldable, dependent: :delete_all
# Repeaters need destroy_all, not delete_all
has_many :repeaters, as: :fieldable, dependent: :destroy
has_many :relations, as: :fieldable, dependent: :destroy
has_many :owner_relations, class_name: "RelationLink",
dependent: :destroy,
as: :dependent
# Owner are connected to its Dependents in a Active Relation
# meaning its possible to connect a Owner to as many Dependents
# as it's needed.
#
# The current version support components and boards separately
has_many :owner_components, through: :owner_relations,
source: :owner
has_many :owner_boards, through: :owner_relations,
source: :owner
has_many :owner_repeaters, through: :owner_relations,
source: :owner
accepts_nested_attributes_for :texts, :strings, :dates, :assets, :images, :videos, :audios, :galleries, :repeaters, :radios, :selections, :checkboxes, :relations, :svgs, allow_destroy: true
validates_associated :texts
validates_associated :strings
validates_associated :dates
validates_associated :assets
validates_associated :images
validates_associated :videos
validates_associated :audios
validates_associated :repeaters
validates_associated :radios
validates_associated :selections
validates_associated :checkboxes
validates_associated :relations
validates_associated :svgs
after_save :generate_fields
# Uncomment these "validate do" loop to better debug validation.
# This makes method gather errors of the associated records and
# make them available to the instance object. After using this method
# you will be able to see the actual error inside `instance.errors` array.
# Example: @component.errors #=> [ ... ]
#
# validate do |instance|
# instance.texts.each do |text|
# binding.pry
# next if text.valid?
# text.errors.full_messages.each do |msg|
# # you can customize the error message here:
# errors[:base] << "Error in #{text.field_setting.name} (text): #{msg}"
# end
# end
# end
end
# Find or create a field by field setting and field type
#
# This is used in Binda's editor views.
#
# Please, check the code to know more about the way this method works as it's pretty complex yet important.
#
# @param field_setting_id [string] The field setting id
# @param field_type [string] THe field type
def find_or_create_a_field_by(field_setting_id, field_type)
if FieldSetting.get_field_classes.include?( field_type.classify ) && field_setting_id.is_a?( Integer )
get_field(field_type, field_setting_id)
else
raise ArgumentError, "One parameter in find_or_create_a_field_by() is not correct on instance (#{self.class.name} ##{self.id}).", caller
end
end
# This method is called upon the creation/update of a fieldable record (component, board or repeater)
# and generates all fields related to each field settings which belongs to it.
#
# This avoids any situation in which, for example, a component have a field setting for a text
# but there is no text (meaning `Binda::Text` instance) that correspond to that field setting.
# This causes issues when looping a bunch of components which will thow a error if you try to access
# a component field, as some might have it some might not. This make sure that you can always expect
# to find a field instance which might be empty, but certainly it exists.
#
# TODO check if find_or_create_a_field_by method should be used instead (it's used in editors views)
#
def generate_fields
GenerateFieldsJob.perform_later self
end
# TODO: Update all helpers replacing `find` method with ruby `select`.
# This should improve performance avoiding generating useless ActiveRecord objects.
private
# Get field based on field type and field setting
# @param field_type [string]
# @param field_setting_id [integer]
# @return [ActiveRecord]
def get_field(field_type, field_setting_id)
# It's mandatory to use `select{}.first`!!!
# If you use any ActiveRecord method (like `where` of `find`) the validation errors are wiped out
# from the object and not rendered next to the form in the editor view
obj = self.send( field_type.pluralize ).select{|rf| rf.field_setting_id == field_setting_id}.first
if obj.nil?
# As we are using the `select{}.first` method, asynchronous requests passing through
# this method will create some inconsistentcy between this `self` object and the real object.
# In other words `self` should be reloaded, but we won't reload it otherwise we will
# erase the errors shipped with it initially. Therefore we will check again with `find_or_create_by!`.
# This leads to another issue, which is: errors coming from asynchronous requests won't be considered
# as the `self` object is the initial one, not the "updated" one. At the current time this is not
# a problem because the only asynchronous requests are for brand new records which don't need validation.
return self.send( field_type.pluralize ).find_or_create_by!( field_setting_id: field_setting_id )
else
return obj
end
end
end
end