active_fields 2.0.1 → 3.0.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -2
  3. data/Appraisals +19 -0
  4. data/CHANGELOG.md +6 -1
  5. data/README.md +113 -12
  6. data/Rakefile +0 -2
  7. data/app/models/concerns/active_fields/customizable_concern.rb +59 -11
  8. data/app/models/concerns/active_fields/field_concern.rb +38 -4
  9. data/app/models/concerns/active_fields/value_concern.rb +8 -1
  10. data/db/migrate/20250229230000_add_scope_to_active_fields.rb +14 -0
  11. data/gemfiles/rails_7.1.gemfile +15 -0
  12. data/gemfiles/rails_7.2.gemfile +15 -0
  13. data/gemfiles/rails_8.0.gemfile +15 -0
  14. data/gemfiles/rails_8.1.gemfile +15 -0
  15. data/lib/active_fields/casters/date_array_caster.rb +2 -2
  16. data/lib/active_fields/casters/date_time_array_caster.rb +2 -2
  17. data/lib/active_fields/casters/decimal_array_caster.rb +2 -2
  18. data/lib/active_fields/casters/enum_array_caster.rb +2 -2
  19. data/lib/active_fields/casters/integer_array_caster.rb +2 -2
  20. data/lib/active_fields/casters/text_array_caster.rb +2 -2
  21. data/lib/active_fields/config.rb +2 -2
  22. data/lib/active_fields/finders/array_finder.rb +8 -2
  23. data/lib/active_fields/has_active_fields.rb +3 -1
  24. data/lib/active_fields/version.rb +1 -1
  25. data/lib/generators/active_fields/scaffold/templates/controllers/active_fields_controller.rb +18 -18
  26. data/lib/generators/active_fields/scaffold/templates/javascript/controllers/active_field_form_controller.js +19 -0
  27. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_boolean.html.erb +22 -1
  28. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_date.html.erb +22 -1
  29. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_date_array.html.erb +22 -1
  30. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_datetime.html.erb +22 -1
  31. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_datetime_array.html.erb +22 -1
  32. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_decimal.html.erb +22 -1
  33. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_decimal_array.html.erb +22 -1
  34. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_enum.html.erb +22 -1
  35. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_enum_array.html.erb +22 -1
  36. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_integer.html.erb +22 -1
  37. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_integer_array.html.erb +22 -1
  38. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_text.html.erb +22 -1
  39. data/lib/generators/active_fields/scaffold/templates/views/active_fields/forms/_text_array.html.erb +22 -1
  40. data/lib/generators/active_fields/scaffold/templates/views/active_fields/index.html.erb +2 -0
  41. metadata +24 -4
  42. data/CODE_OF_CONDUCT.md +0 -84
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c94bb84092249c9db341f69ec79233da8440b8ef74c00475c5c8acb6e42a290
4
- data.tar.gz: 7653a7859137a53d465ccc2398f055f064105949f0a3be7e1e7fc5f3636ee240
3
+ metadata.gz: 6eaf6bd2afd182c6429e472d3f2b1a3dd70bd7a7baadd9a2bbc7dc421a39a7d7
4
+ data.tar.gz: 6d77769c25f2c97bae25bee0ed66792fd719f4738610fa9b6f1c8d92f77f673a
5
5
  SHA512:
6
- metadata.gz: 57eecfc4cb3cf040dfcc2daf59a804b33ea5b3f9744cde6931c8fa0f9777eeeb067d3895b33ebbf927c5b74c66290f00546986d472d038c11df700d8092631d7
7
- data.tar.gz: 1e923ede58bd193079334b3fcb9e193fdd61b56d6d73ffdf977a626618d2e308787641c8194ebc806113141b9dd66a3e5fbea106c0d0ded699c866a10a6b8d0b
6
+ metadata.gz: 78359ebaf1b2048564eed04a8de2464aa4650d3e16a03c9f701041daaae96c7c30924fd685d15aed7180dfccf340e86509db1a0bf08dcebd1fd72ee763b1d9a9
7
+ data.tar.gz: 51680b929b2de155eb75e16efa328ff76e28dc0112f315ce350c378cc6fe4f77e7fee040c58a23626af7026b0afc008beac6e0a16be8892cdb89c192eaed0a5b
data/.rubocop.yml CHANGED
@@ -3,17 +3,24 @@ plugins:
3
3
  - rubocop-rails
4
4
  - rubocop-rake
5
5
  - rubocop-rspec
6
+ - rubocop-factory_bot
7
+ - rubocop-rspec_rails
6
8
 
7
9
  inherit_gem:
8
10
  rubocop-shopify: rubocop.yml
9
11
 
10
12
  AllCops:
11
- TargetRubyVersion: 3.3
12
- TargetRailsVersion: 8.0
13
+ TargetRubyVersion: 3.4
14
+ TargetRailsVersion: 8.1
13
15
  NewCops: enable
14
16
  Exclude:
17
+ - "gemfiles/*"
15
18
  - "spec/dummy/db/schema.rb"
16
19
 
20
+ Layout/LineLength:
21
+ Enabled: true
22
+ Max: 120
23
+
17
24
  Layout/EmptyLinesAroundAccessModifier:
18
25
  EnforcedStyle: around
19
26
 
@@ -47,3 +54,9 @@ RSpec/ContextWording:
47
54
 
48
55
  RSpec/MultipleExpectations:
49
56
  Enabled: false
57
+
58
+ Naming/PredicateMethod:
59
+ Enabled: false
60
+
61
+ Naming/InclusiveLanguage:
62
+ Enabled: false
data/Appraisals ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # See https://github.com/thoughtbot/appraisal for more information.
4
+
5
+ appraise "rails_7.1" do
6
+ gem "rails", "~> 7.1.0"
7
+ end
8
+
9
+ appraise "rails_7.2" do
10
+ gem "rails", "~> 7.2.0"
11
+ end
12
+
13
+ appraise "rails_8.0" do
14
+ gem "rails", "~> 8.0.0"
15
+ end
16
+
17
+ appraise "rails_8.1" do
18
+ gem "rails", "~> 8.1.0"
19
+ end
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
- # [2.0.1] - 2025-04-09
3
+ ## [3.0.0] - 2025-11-27
4
+ - Disabled fields name format validation
5
+ - Added scope functionality: _Active Field_ can now be limited to a specific context, allowing different sets of fields per group within the same _Customizable_ model.
6
+ - Added Appraisal to run tests across multiple Rails versions.
7
+
8
+ ## [2.0.1] - 2025-04-09
4
9
  - Fixed search with `nil` operator
5
10
 
6
11
  ## [2.0.0] - 2025-02-22
data/README.md CHANGED
@@ -38,6 +38,12 @@ classDiagram
38
38
  All values are stored in a JSON (jsonb) field, which is a highly flexible column type capable of storing various data types,
39
39
  such as booleans, strings, numbers, arrays, etc.
40
40
 
41
+ ## Requirements
42
+
43
+ - Ruby 3.1+
44
+ - Rails 7.1+
45
+ - Postgres 15+ (17+ for search functionality)
46
+
41
47
  ## Installation
42
48
 
43
49
  1. Install the gem and add it to your application's Gemfile by running:
@@ -849,6 +855,97 @@ IpFinder.new(active_field: ip_active_field).search(op: "eq", value: "127.0.0.1")
849
855
  IpArrayFinder.new(active_field: ip_array_active_field).search(op: "#>=", value: 5)
850
856
  ```
851
857
 
858
+ ### Scoping
859
+
860
+ The scoping feature enables multi-tenancy or context-based field definitions per model.
861
+ It allows you to define different sets of _Active Fields_ for different scopes (e.g., different tenants, organizations, or contexts).
862
+
863
+ **How it works:**
864
+ - Pass a `scope_method` parameter to `has_active_fields` to enable scoping for a _Customizable_ model. The method should return a value that identifies the scope (e.g., `tenant_id`, `organization_id`).
865
+ - The scope method's return value is automatically converted to a string and exposed as `active_fields_scope` on each _Customizable_ record. This value is used to match against _Active Field_ `scope` values.
866
+ - When an _Active Field_ has `scope` = `nil` (_global field_), it is available to all _Customizable_ records, regardless of their scope value.
867
+ - When an _Active Field_ has a `scope` != `nil` (_scope field_), it is only available to _Customizable_ records where `active_fields_scope` matches the `scope`.
868
+
869
+ ```ruby
870
+ class User < ApplicationRecord
871
+ has_active_fields scope_method: :tenant_id
872
+ end
873
+
874
+ # Global active field (available to all users)
875
+ ActiveFields::Field::Text.create!(
876
+ name: "note",
877
+ customizable_type: "User",
878
+ scope: nil,
879
+ )
880
+
881
+ # Scoped active field (only available to users with tenant_id = "tenant_1")
882
+ ActiveFields::Field::Integer.create!(
883
+ name: "age",
884
+ customizable_type: "User",
885
+ scope: "tenant_1",
886
+ )
887
+
888
+ # Scoped active field (only available to users with tenant_id = "tenant_2")
889
+ ActiveFields::Field::Date.create!(
890
+ name: "registered_on",
891
+ customizable_type: "User",
892
+ scope: "tenant_2",
893
+ )
894
+
895
+ # Usage
896
+ user_1 = User.create!(tenant_id: "tenant_1")
897
+ user_1.active_fields # Returns `note` and `age`
898
+
899
+ user_2 = User.create!(tenant_id: "tenant_2")
900
+ user_2.active_fields # Returns `note` and `registered_on`
901
+
902
+ user_3 = User.create!(tenant_id: nil)
903
+ user_3.active_fields # Returns only `note`
904
+
905
+ # Query with scope
906
+ User.active_fields # Returns only `note`
907
+ User.active_fields(scope: "tenant_1") # Returns `note` and `age`
908
+ User.where_active_fields(filters) # Search by global fields only (`note`)
909
+ User.where_active_fields(filters, scope: "tenant_1") # Search by `note` and `registered_on`
910
+ ```
911
+
912
+ **Handling scope changes:**
913
+
914
+ If you change the scope value of a _Customizable_ record (e.g., changing `tenant_id`), you must manually destroy _Active Values_ that are no longer available for that record.
915
+ The gem does not automatically handle this because the `scope_method` implementation is up to you, and therefore its change tracking is your responsibility.
916
+ However, there is a helper method that you could use to clear the _Active Values_ list: `clear_unavailable_active_values`.
917
+
918
+ **Example 1:** `scope_method` is a single database column.
919
+
920
+ ```ruby
921
+ class User < ApplicationRecord
922
+ has_active_fields scope_method: :tenant_id
923
+
924
+ after_update :clear_unavailable_active_values, if: :saved_change_to_tenant_id?
925
+ end
926
+ ```
927
+
928
+ **Example 2:** `scope_method` is a computed value from multiple columns.
929
+
930
+ ```ruby
931
+ class User < ApplicationRecord
932
+ has_active_fields scope_method: :tenant_and_department_scope
933
+
934
+ # The scope method should return a string
935
+ def tenant_and_department_scope
936
+ "#{tenant_id}-#{department_id}"
937
+ end
938
+
939
+ after_update :clear_unavailable_active_values, if: :tenant_and_department_scope_changed?
940
+
941
+ private
942
+
943
+ def tenant_and_department_scope_changed?
944
+ saved_change_to_tenant_id? || saved_change_to_department_id?
945
+ end
946
+ end
947
+ ```
948
+
852
949
  ### Localization (I18n)
853
950
 
854
951
  The built-in _validators_ primarily use _Rails_ default error types.
@@ -861,9 +958,7 @@ For an example, refer to the [locale file](https://github.com/lassoid/active_fie
861
958
 
862
959
  ## Current Restrictions
863
960
 
864
- 1. Only _PostgreSQL_ 17+ is fully supported.
865
-
866
- The gem is tested exclusively with _PostgreSQL_. Support for other databases is not guaranteed.
961
+ 1. This gem requires _PostgreSQL_ and is not designed to support other database systems.
867
962
 
868
963
  2. Updating some _Active Fields_ options may be unsafe.
869
964
 
@@ -901,6 +996,7 @@ active_field.available_customizable_types # Available Customizable types for thi
901
996
 
902
997
  # Scopes:
903
998
  ActiveFields::Field::Boolean.for("Post") # Collection of Active Fields registered for the specified Customizable type
999
+ ActiveFields::Field::Integer.for("User", scope: "main_tenant") # Collection of Active Fields available for the specified Customizable type with given scope
904
1000
  ```
905
1001
 
906
1002
  ### Values API
@@ -929,10 +1025,13 @@ customizable = Post.take
929
1025
  customizable.active_values # `has_many` association with Active Values linked to this Customizable
930
1026
 
931
1027
  # Methods:
932
- customizable.active_fields # Collection of Active Fields registered for this record
933
- Post.active_fields # Collection of Active Fields registered for this model
1028
+ customizable.active_fields # Collection of Active Fields available for this record
1029
+ customizable.active_fields_scope # Scope value for this record
1030
+ Post.active_fields # Collection of Active Fields available for this model
1031
+ User.active_fields(scope: "main_tenant") # Collection of Active Fields available for this model and given scope
934
1032
  Post.allowed_active_fields_type_names # Active Fields type names allowed for this Customizable model
935
1033
  Post.allowed_active_fields_class_names # Active Fields class names allowed for this Customizable model
1034
+ User.active_fields_scope_method # Scope method for this model
936
1035
 
937
1036
  # Create, update or destroy Active Values.
938
1037
  customizable.active_fields_attributes = [
@@ -963,6 +1062,10 @@ customizable.active_values_attributes = attributes
963
1062
  # `form.fields_for :active_fields, customizable.initialize_active_values`.
964
1063
  customizable.initialize_active_values
965
1064
 
1065
+ # Destroys Active Values that are no longer associated with Active Fields available for this record.
1066
+ # Call this method after changing the scope value to ensure all Active Values are valid.
1067
+ customizable.clear_unavailable_active_values
1068
+
966
1069
  # Query Customizables by Active Values.
967
1070
  Post.where_active_fields(
968
1071
  [
@@ -971,6 +1074,11 @@ Post.where_active_fields(
971
1074
  { n: "boolean", op: "!=", v: false }, # compact form (string or symbol keys)
972
1075
  ],
973
1076
  )
1077
+ # Search with given scope.
1078
+ User.where_active_fields(
1079
+ filters,
1080
+ scope: "main_tenant",
1081
+ )
974
1082
  ```
975
1083
 
976
1084
  ### Global Config
@@ -1013,14 +1121,7 @@ and push the `.gem` file to [rubygems.org](https://rubygems.org).
1013
1121
  ## Contributing
1014
1122
 
1015
1123
  Bug reports and pull requests are welcome on GitHub at https://github.com/lassoid/active_fields.
1016
- This project is intended to be a safe, welcoming space for collaboration, and contributors
1017
- are expected to adhere to the [code of conduct](https://github.com/lassoid/active_fields/blob/main/CODE_OF_CONDUCT.md).
1018
1124
 
1019
1125
  ## License
1020
1126
 
1021
1127
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
1022
-
1023
- ## Code of Conduct
1024
-
1025
- Everyone interacting in the ActiveFields project's codebases, issue trackers, chat rooms and mailing lists
1026
- is expected to follow the [code of conduct](https://github.com/lassoid/active_fields/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -5,6 +5,4 @@ require "bundler/setup"
5
5
  APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
6
6
  load "rails/tasks/engine.rake"
7
7
 
8
- load "rails/tasks/statistics.rake"
9
-
10
8
  require "bundler/gem_tasks"
@@ -23,6 +23,10 @@ module ActiveFields
23
23
  # - <tt>:op</tt> or <tt>:operator</tt> key specifying search operation or operator;
24
24
  # - <tt>:v</tt> or <tt>:value</tt> key specifying search value.
25
25
  #
26
+ # Optionally, a <tt>:scope</tt> keyword argument can be provided to expand the search
27
+ # to include both global fields (where scope is nil) and scoped fields for the given scope.
28
+ # When <tt>:scope</tt> is omitted or nil, only global fields are searched.
29
+ #
26
30
  # Example:
27
31
  #
28
32
  # # Array of hashes
@@ -45,7 +49,13 @@ module ActiveFields
45
49
  #
46
50
  # # Params (must be permitted)
47
51
  # CustomizableModel.where_active_fields(permitted_params)
48
- scope :where_active_fields, ->(filters) do
52
+ #
53
+ # # Scoped
54
+ # CustomizableModel.where_active_fields(
55
+ # filters,
56
+ # scope: Current.tenant.id,
57
+ # )
58
+ scope :where_active_fields, ->(filters, scope: nil) do
49
59
  filters = filters.to_h if filters.respond_to?(:permitted?)
50
60
 
51
61
  unless filters.is_a?(Array) || filters.is_a?(Hash)
@@ -55,23 +65,23 @@ module ActiveFields
55
65
  # Handle `fields_for` params
56
66
  filters = filters.values if filters.is_a?(Hash)
57
67
 
58
- active_fields_by_name = active_fields.index_by(&:name)
68
+ active_fields_by_name = active_fields(scope: scope).index_by(&:name)
59
69
 
60
- filters.inject(self) do |scope, filter|
70
+ filters.inject(self) do |query, filter|
61
71
  filter = filter.to_h if filter.respond_to?(:permitted?)
62
72
  filter = filter.with_indifferent_access
63
73
 
64
74
  active_field = active_fields_by_name[filter[:n] || filter[:name]]
65
- next scope if active_field.nil?
66
- next scope if active_field.value_finder.nil?
75
+ next query if active_field.nil?
76
+ next query if active_field.value_finder.nil?
67
77
 
68
78
  active_values = active_field.value_finder.search(
69
79
  op: filter[:op] || filter[:operator],
70
80
  value: filter[:v] || filter[:value],
71
81
  )
72
- next scope if active_values.nil?
82
+ next query if active_values.nil?
73
83
 
74
- scope.where(id: active_values.select(:customizable_id))
84
+ query.where(id: active_values.select(:customizable_id))
75
85
  end
76
86
  end
77
87
 
@@ -79,9 +89,13 @@ module ActiveFields
79
89
  end
80
90
 
81
91
  class_methods do
82
- # Collection of active fields registered for this customizable
83
- def active_fields
84
- ActiveFields.config.field_base_class.for(name)
92
+ # Returns the collection of active fields associated with this customizable model.
93
+ # You can provide an optional <tt>:scope</tt> parameter
94
+ # to retrieve active fields available for a specific scope, not just global fields.
95
+ def active_fields(scope: nil)
96
+ scope = nil if active_fields_scope_method.nil?
97
+
98
+ ActiveFields.config.field_base_class.for(name, scope: scope)
85
99
  end
86
100
 
87
101
  # Returns active fields type names allowed for this customizable model.
@@ -95,7 +109,17 @@ module ActiveFields
95
109
  end
96
110
  end
97
111
 
98
- delegate :active_fields, to: :class
112
+ # Collection of active fields registered for this customizable.
113
+ def active_fields
114
+ self.class.active_fields(scope: active_fields_scope)
115
+ end
116
+
117
+ # Scope value for the active fields collection.
118
+ def active_fields_scope
119
+ return if self.class.active_fields_scope_method.nil?
120
+
121
+ send(self.class.active_fields_scope_method)&.to_s
122
+ end
99
123
 
100
124
  # Assigns the given attributes to the active_values association.
101
125
  #
@@ -177,5 +201,29 @@ module ActiveFields
177
201
 
178
202
  active_values
179
203
  end
204
+
205
+ # Destroys all active_values that are no longer available for this customizable
206
+ # (e.g. after its scope has changed).
207
+ #
208
+ # This method is suitable for customizables with scope functionality enabled
209
+ # (when `has_active_fields` is called with a `scope_method:` parameter).
210
+ # When a customizable's scope value changes,
211
+ # some active_values may reference active_fields that are no longer available for the new scope.
212
+ # This method identifies and destroys those orphaned active_values.
213
+ #
214
+ # Example:
215
+ #
216
+ # class User < ApplicationRecord
217
+ # has_active_fields scope_method: :tenant_id
218
+ #
219
+ # after_update :clear_unavailable_active_values, if: :saved_change_to_tenant_id?
220
+ # end
221
+ def clear_unavailable_active_values
222
+ available_field_ids = active_fields.pluck(:id)
223
+
224
+ active_values.select do |active_value|
225
+ available_field_ids.exclude?(active_value.active_field_id)
226
+ end.each(&:destroy)
227
+ end
180
228
  end
181
229
  end
@@ -12,15 +12,22 @@ module ActiveFields
12
12
  inverse_of: :active_field,
13
13
  dependent: :destroy
14
14
 
15
- scope :for, ->(customizable_type) { where(customizable_type: customizable_type) }
15
+ # Returns active fields for the given customizable type, including both
16
+ # scoped fields (when scope parameter is provided) and global fields (where scope is nil).
17
+ scope :for, ->(customizable_type, scope: nil) do
18
+ # Global fields are available to all records of the given customizable type.
19
+ scopes = [scope, nil].uniq
20
+ where(customizable_type: customizable_type, scope: scopes)
21
+ end
16
22
 
17
23
  validates :type, presence: true
18
- validates :name, presence: true, uniqueness: { scope: :customizable_type }
19
- validates :name, format: { with: /\A[a-z0-9_]+\z/ }, allow_blank: true
24
+ validates :name, presence: true
25
+ validate :validate_name_uniqueness
20
26
  validate :validate_default_value
21
27
  validate :validate_customizable_model_allows_type
22
28
 
23
29
  after_initialize :set_defaults
30
+ before_validation :set_scope
24
31
  end
25
32
 
26
33
  class_methods do
@@ -89,7 +96,7 @@ module ActiveFields
89
96
 
90
97
  # Returns customizable types that allow this field type.
91
98
  #
92
- # Notes:
99
+ # NOTE:
93
100
  # - The customizable model must be loaded to appear in this list.
94
101
  # Relationships between customizable models and field types are established in the `has_active_fields` method,
95
102
  # which is typically called within the customizable model.
@@ -104,6 +111,26 @@ module ActiveFields
104
111
 
105
112
  private
106
113
 
114
+ # NOTE: The uniqueness constraint in the DB does not fully enforce this validation:
115
+ # Records where scope is NULL do not prevent the existence of records with the same
116
+ # [name, customizable_type] but with a non-NULL scope, and vice versa.
117
+ def validate_name_uniqueness
118
+ base_scope =
119
+ ActiveFields.config.field_base_class.excluding(self).where(customizable_type: customizable_type, name: name)
120
+
121
+ if scope.nil?
122
+ if base_scope.exists?
123
+ errors.add(:name, :taken)
124
+ return false
125
+ end
126
+ elsif base_scope.exists?(scope: [scope, nil])
127
+ errors.add(:name, :taken)
128
+ return false
129
+ end
130
+
131
+ true
132
+ end
133
+
107
134
  def validate_default_value
108
135
  validator = value_validator
109
136
  return if validator.validate(default_value)
@@ -128,5 +155,12 @@ module ActiveFields
128
155
  end
129
156
 
130
157
  def set_defaults; end
158
+
159
+ # Forces scope to nil if the customizable model does not have a scope method.
160
+ def set_scope
161
+ return if customizable_model.nil?
162
+
163
+ self.scope = nil if customizable_model.active_fields_scope_method.nil?
164
+ end
131
165
  end
132
166
  end
@@ -59,7 +59,14 @@ module ActiveFields
59
59
 
60
60
  def validate_customizable_allowed
61
61
  return true if active_field.nil?
62
- return true if customizable_type == active_field.customizable_type
62
+
63
+ if customizable_type != active_field.customizable_type
64
+ errors.add(:customizable, :invalid)
65
+ return false
66
+ end
67
+
68
+ return true if active_field.scope.nil?
69
+ return true if customizable&.active_fields_scope == active_field.scope
63
70
 
64
71
  errors.add(:customizable, :invalid)
65
72
  false
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddScopeToActiveFields < ActiveRecord::Migration[7.1]
4
+ def change
5
+ change_table :active_fields do |t|
6
+ t.string :scope
7
+
8
+ t.remove_index %i[name customizable_type], unique: true
9
+ t.remove_index :customizable_type
10
+
11
+ t.index %i[customizable_type scope name], unique: true, nulls_not_distinct: true
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "puma"
6
+ gem "propshaft"
7
+ gem "importmap-rails"
8
+ gem "stimulus-rails"
9
+ gem "rails", "~> 7.1.0"
10
+
11
+ group :development do
12
+ gem "web-console"
13
+ end
14
+
15
+ gemspec path: "../"
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "puma"
6
+ gem "propshaft"
7
+ gem "importmap-rails"
8
+ gem "stimulus-rails"
9
+ gem "rails", "~> 7.2.0"
10
+
11
+ group :development do
12
+ gem "web-console"
13
+ end
14
+
15
+ gemspec path: "../"
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "puma"
6
+ gem "propshaft"
7
+ gem "importmap-rails"
8
+ gem "stimulus-rails"
9
+ gem "rails", "~> 8.0.0"
10
+
11
+ group :development do
12
+ gem "web-console"
13
+ end
14
+
15
+ gemspec path: "../"
@@ -0,0 +1,15 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "puma"
6
+ gem "propshaft"
7
+ gem "importmap-rails"
8
+ gem "stimulus-rails"
9
+ gem "rails", "~> 8.1.0"
10
+
11
+ group :development do
12
+ gem "web-console"
13
+ end
14
+
15
+ gemspec path: "../"
@@ -6,13 +6,13 @@ module ActiveFields
6
6
  def serialize(value)
7
7
  return unless value.is_a?(Array)
8
8
 
9
- value.map { super(_1) }
9
+ value.map { |element| super(element) }
10
10
  end
11
11
 
12
12
  def deserialize(value)
13
13
  return unless value.is_a?(Array)
14
14
 
15
- value.map { super(_1) }
15
+ value.map { |element| super(element) }
16
16
  end
17
17
  end
18
18
  end
@@ -6,13 +6,13 @@ module ActiveFields
6
6
  def serialize(value)
7
7
  return unless value.is_a?(Array)
8
8
 
9
- value.map { super(_1) }
9
+ value.map { |element| super(element) }
10
10
  end
11
11
 
12
12
  def deserialize(value)
13
13
  return unless value.is_a?(Array)
14
14
 
15
- value.map { super(_1) }
15
+ value.map { |element| super(element) }
16
16
  end
17
17
  end
18
18
  end
@@ -6,13 +6,13 @@ module ActiveFields
6
6
  def serialize(value)
7
7
  return unless value.is_a?(Array)
8
8
 
9
- value.map { super(_1) }
9
+ value.map { |element| super(element) }
10
10
  end
11
11
 
12
12
  def deserialize(value)
13
13
  return unless value.is_a?(Array)
14
14
 
15
- value.map { super(_1) }
15
+ value.map { |element| super(element) }
16
16
  end
17
17
  end
18
18
  end
@@ -6,13 +6,13 @@ module ActiveFields
6
6
  def serialize(value)
7
7
  return unless value.is_a?(Array)
8
8
 
9
- value.map { super(_1) }
9
+ value.map { |element| super(element) }
10
10
  end
11
11
 
12
12
  def deserialize(value)
13
13
  return unless value.is_a?(Array)
14
14
 
15
- value.map { super(_1) }
15
+ value.map { |element| super(element) }
16
16
  end
17
17
  end
18
18
  end
@@ -6,13 +6,13 @@ module ActiveFields
6
6
  def serialize(value)
7
7
  return unless value.is_a?(Array)
8
8
 
9
- value.map { super(_1) }
9
+ value.map { |element| super(element) }
10
10
  end
11
11
 
12
12
  def deserialize(value)
13
13
  return unless value.is_a?(Array)
14
14
 
15
- value.map { super(_1) }
15
+ value.map { |element| super(element) }
16
16
  end
17
17
  end
18
18
  end
@@ -6,13 +6,13 @@ module ActiveFields
6
6
  def serialize(value)
7
7
  return unless value.is_a?(Array)
8
8
 
9
- value.map { super(_1) }
9
+ value.map { |element| super(element) }
10
10
  end
11
11
 
12
12
  def deserialize(value)
13
13
  return unless value.is_a?(Array)
14
14
 
15
- value.map { super(_1) }
15
+ value.map { |element| super(element) }
16
16
  end
17
17
  end
18
18
  end