Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
cdf08e5
feat: add new polymorphic relation schema
nicolasalexandre9 Jun 11, 2024
a343b03
feat: introspect polymorphic associations
nicolasalexandre9 Jun 11, 2024
06dacc6
feat(schema): generate schema for polymorphic relation
nicolasalexandre9 Jun 11, 2024
33d5208
feat: update projection for ignore polymorphic relations
nicolasalexandre9 Jun 11, 2024
3216d2b
feat: update decorators for work with polymorphic relations
nicolasalexandre9 Jun 11, 2024
34d00e8
feat: update api serializer to work with polymorphic relations
nicolasalexandre9 Jun 11, 2024
acdfe23
feat: add reverse polymorphic relations support
nicolasalexandre9 Jun 11, 2024
37293ac
feat: add polymorphic relations support on show api
nicolasalexandre9 Jun 12, 2024
92a84e8
feat: add update polymorphic relation
nicolasalexandre9 Jun 13, 2024
38a22af
fix: list_relation
matthv Jun 13, 2024
a8baad7
feat: update binary decorator for work with polymorphic relation
nicolasalexandre9 Jun 13, 2024
0de59ec
fix(rename): sort projection comparisons on list
matthv Jun 13, 2024
dd960a6
fix(publication): refine_schema
matthv Jun 13, 2024
17fef74
feat: update parse projection
nicolasalexandre9 Jun 20, 2024
bb746b2
feat(schema): update generator field
nicolasalexandre9 Jun 20, 2024
7368d61
feat: associate related polymorphic relation
nicolasalexandre9 Jul 8, 2024
cdc8a0d
fix: query
nicolasalexandre9 Jul 9, 2024
53af6a5
feat: update the update related route
nicolasalexandre9 Jul 9, 2024
6595ee4
fix: remove useless attribute
nicolasalexandre9 Jul 15, 2024
09c65ad
fix: update related
nicolasalexandre9 Jul 15, 2024
aa3a2ec
feat: update dissociate related for polymorphic association
nicolasalexandre9 Jul 15, 2024
5420b86
feat: add support polymorphic type with namespace
nicolasalexandre9 Jul 16, 2024
3fa27c3
fix: associate related and update related
nicolasalexandre9 Jul 17, 2024
d4b7d84
feat(computed): forbidden users to use polymorphic dependencies
nicolasalexandre9 Jul 19, 2024
12c652e
test(computed): add test on computed decorator
nicolasalexandre9 Jul 19, 2024
bca9a97
feat(computed): add debug log when computed field over polymotphic re…
nicolasalexandre9 Jul 22, 2024
29b3178
feat: prevent users from renaming a collection that have polymorphic …
nicolasalexandre9 Jul 22, 2024
eb1fd68
feat(search): add log we don't search through polymorphic relationships
nicolasalexandre9 Jul 22, 2024
aa0cd8c
chore: update gemspecs conf
nicolasalexandre9 Jul 23, 2024
7f9dc0d
test: add test on parse projection with polymorphic relation
nicolasalexandre9 Jul 23, 2024
8f830ff
chore: update gemspecs conf
nicolasalexandre9 Jul 23, 2024
1e0f82f
test: add test on generator field with polymorphic field case
nicolasalexandre9 Jul 23, 2024
99af238
fix: tests
nicolasalexandre9 Jul 23, 2024
f443707
test: add tests on filter factory
nicolasalexandre9 Jul 25, 2024
29bd3c8
test: add tests on projection and projection factory
nicolasalexandre9 Jul 25, 2024
cadfe91
test: add tests on new relations schemas
nicolasalexandre9 Jul 29, 2024
45e8742
test: add test on collection utils
nicolasalexandre9 Jul 29, 2024
56e30b2
test: add test on schema utils
nicolasalexandre9 Jul 29, 2024
8b2be95
fix(introspection): formate collection name
nicolasalexandre9 Jul 29, 2024
afe2606
fix(introspection): use klass.name instead of class_name
nicolasalexandre9 Jul 29, 2024
1562c61
fix(introspection): association_primary_key? return true if associati…
nicolasalexandre9 Jul 29, 2024
7ab07bb
fix: allow to create an empty record
nicolasalexandre9 Jul 29, 2024
f06183e
test: add test on binary decorator
nicolasalexandre9 Jul 30, 2024
823b5d9
feat: raise error if user rename polymorphic field
nicolasalexandre9 Jul 30, 2024
ed0845a
feat: block remove polymorphic relations fields
nicolasalexandre9 Jul 30, 2024
563f287
test: add test on search decorator
nicolasalexandre9 Jul 31, 2024
8ccbbce
fix: set fields of polymorphic relation as read only
nicolasalexandre9 Jul 31, 2024
793d05f
test: add test on associated route
nicolasalexandre9 Jul 31, 2024
a04158a
test: add test on dissociate route
nicolasalexandre9 Aug 1, 2024
01b046e
test: add test on update related route
nicolasalexandre9 Aug 1, 2024
3b8b34e
fix: collection utils
nicolasalexandre9 Aug 1, 2024
bb70a52
chore: update github action config
nicolasalexandre9 Aug 1, 2024
5df1887
chore: github workflow
nicolasalexandre9 Aug 2, 2024
556adfa
chore: github workflow
nicolasalexandre9 Aug 2, 2024
5f1fa7a
chore: github workflow
nicolasalexandre9 Aug 2, 2024
5d26b2a
chore: fix releaserc config
nicolasalexandre9 Aug 2, 2024
cbb8b14
fix: refine schema on operator equivalence
nicolasalexandre9 Aug 2, 2024
55370d8
test: update test on datasource active record
nicolasalexandre9 Aug 2, 2024
d42ffb0
fix: store with OneToOne relation
nicolasalexandre9 Aug 5, 2024
d1be747
fix: delete polymorphic target reset the polymorphic record
nicolasalexandre9 Aug 5, 2024
37777de
feat: add option to support polymorphic relations and tests
nicolasalexandre9 Aug 6, 2024
9146439
test: fix tests on store route
nicolasalexandre9 Aug 6, 2024
043e22b
feat: add check on remove collection
nicolasalexandre9 Aug 6, 2024
254f7d7
fix: test
nicolasalexandre9 Aug 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -69,7 +69,7 @@ jobs:
bundle install

- name: Test
run: cd packages/${{ matrix.package }} && bundle install && bundle exec rspec --color --format doc && cd -
run: cd packages/${{ matrix.package }} && BUNDLE_GEMFILE=Gemfile-test bundle install && BUNDLE_GEMFILE=Gemfile-test bundle exec rspec --color --format doc && cd -

- name: Upload coverage
uses: actions/upload-artifact@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,5 +16,6 @@ vendor

# GEM
Gemfile.lock
Gemfile-test.lock
*.gem
pkg/
2 changes: 1 addition & 1 deletion .releaserc.js
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,7 +42,7 @@ module.exports = {
'packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/schema_emitter.rb',
'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/version.rb',
'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/version.rb',
'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_customizer/version.rb',
'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/version.rb',
'packages/forest_admin_rails/lib/forest_admin_rails/version.rb',
'package.json'
],
Expand Down
4 changes: 4 additions & 0 deletions .rubocop.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -226,6 +226,7 @@ Metrics/MethodLength:
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/field_validator.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/actions/action_field_factory.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/action/base_action.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb'

Metrics/BlockLength:
Exclude:
Expand All@@ -239,11 +240,13 @@ Metrics/ClassLength:
- 'packages/forest_admin_agent/lib/forest_admin_agent/services/permissions.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/routes/charts/charts.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/routes/action/actions.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/routes/resources/related/update_related.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/frontend_validation_utils.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/utils/query.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/collection_customizer.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/binary/binary_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/search/search_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/relation/relation_collection_decorator.rb'
- 'packages/forest_admin_datasource_customizer/lib/forest_admin_datasource_customizer/decorators/rename_field/rename_field_collection_decorator.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/utils/collection.rb'
Expand All@@ -253,6 +256,7 @@ Metrics/ClassLength:
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/components/query/condition_tree/transforms/comparisons.rb'
- 'packages/forest_admin_datasource_toolkit/lib/forest_admin_datasource_toolkit/validations/rules.rb'
- 'packages/forest_admin_agent/lib/forest_admin_agent/utils/schema/generator_action_field_widget.rb'
- 'packages/forest_admin_datasource_active_record/lib/forest_admin_datasource_active_record/collection.rb'

Style/OpenStructUse:
Exclude:
Expand Down
5 changes: 2 additions & 3 deletions packages/forest_admin_agent/Gemfile
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,10 +2,9 @@ source "https://rubygems.org"

gemspec

gem 'forest_admin_datasource_customizer'
gem 'forest_admin_datasource_toolkit'

group :development, :test do
gem 'forest_admin_datasource_customizer'
gem 'forest_admin_datasource_toolkit'
gem 'rspec', '~> 3.0'
gem 'simplecov', '~> 0.22', require: false
gem 'simplecov-html', '~> 0.12.3'
Expand Down
12 changes: 12 additions & 0 deletions packages/forest_admin_agent/Gemfile-test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
source "https://rubygems.org"

gemspec

group :development, :test do
gem 'forest_admin_datasource_customizer', path: '../forest_admin_datasource_customizer'
gem 'forest_admin_datasource_toolkit', path: '../forest_admin_datasource_toolkit'
gem 'rspec', '~> 3.0'
gem 'simplecov', '~> 0.22', require: false
gem 'simplecov-html', '~> 0.12.3'
gem 'simplecov_json_formatter', '~> 0.1.4'
end
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ def format_attributes(args)
record[schema.foreign_key] = value['data'][schema.foreign_key_target] if schema.type == 'ManyToOne'
end

record
record || {}
end
end
end
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -5,7 +5,11 @@ def build(args = {})
super

relation = @collection.schema[:fields][args[:params]['relation_name']]
@child_collection = @datasource.get_collection(relation.foreign_collection)
@child_collection = if relation.type == 'PolymorphicManyToOne'
@datasource.get_collection(args[:params]['data']['type'])
else
@datasource.get_collection(relation.foreign_collection)
end
end
end
end
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,25 @@ def handle_request_bulk(args = {})
def delete_records(args, selection_ids)
condition_tree_ids = ConditionTree::ConditionTreeFactory.match_records(@collection, selection_ids[:ids])
condition_tree_ids = condition_tree_ids.inverse if selection_ids[:are_excluded]

@collection.schema[:fields].each_value do |field_schema|
next unless field_schema.type == 'PolymorphicOneToOne' || field_schema.type == 'PolymorphicOneToMany'

condition_tree = Nodes::ConditionTreeBranch.new(
'And',
[
Nodes::ConditionTreeLeaf.new(field_schema.origin_key, Operators::IN,
selection_ids[:ids].map { |value| value['id'] }),
Nodes::ConditionTreeLeaf.new(field_schema.origin_type_field, Operators::EQUAL,
@collection.name.gsub('__', '::'))
]
)
filter = Filter.new(condition_tree: condition_tree)
@datasource.get_collection(field_schema.foreign_collection)
.update(@caller, filter, { field_schema.origin_key => nil,
field_schema.origin_type_field => nil })
end

filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
[
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -38,7 +38,7 @@ def handle_request(args = {})
class_name: @collection.name,
is_collection: true,
serializer: Serializer::ForestSerializer,
include: projection.relations.keys,
include: projection.relations(only_keys: true),
meta: handle_search_decorator(args[:params]['search'], records)
)
}
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,10 +28,13 @@ def handle_request(args = {})
target_relation_id = Utils::Id.unpack_id(@child_collection, args[:params]['data'][0]['id'], with_key: true)
relation = Schema.get_to_many_relation(@collection, args[:params]['relation_name'])

if relation.type == 'OneToMany'
case relation.type
when 'OneToMany'
associate_one_to_many(relation, parent_id, target_relation_id)
else
when 'ManyToMany'
associate_many_to_many(relation, parent_id, target_relation_id)
when 'PolymorphicOneToMany'
associate_polymorphic_one_to_many(relation, parent_id, target_relation_id)
end

{ content: nil, status: 204 }
Expand All@@ -55,6 +58,27 @@ def associate_one_to_many(relation, parent_id, target_relation_id)
@child_collection.update(@caller, filter, { relation.origin_key => value })
end

def associate_polymorphic_one_to_many(relation, parent_id, target_relation_id)
id = Schema.primary_keys(@child_collection)[0]
value = Collection.get_value(@child_collection, @caller, target_relation_id, id)
filter = Filter.new(
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
[
ConditionTree::Nodes::ConditionTreeLeaf.new(id, 'Equal', value),
@permissions.get_scope(@collection)
]
)
)

value = Collection.get_value(@collection, @caller, parent_id, relation.origin_key_target)

@child_collection.update(
@caller,
filter,
{ relation.origin_key => value, relation.origin_type_field => @collection.name.gsub('__', '::') }
)
end

def associate_many_to_many(relation, parent_id, target_relation_id)
id = Schema.primary_keys(@child_collection)[0]
foreign_value = Collection.get_value(@child_collection, @caller, target_relation_id, id)
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,7 +28,7 @@ def handle_request(args = {})
filter = get_base_foreign_filter(args)
relation = Schema.get_to_many_relation(@collection, args[:params]['relation_name'])

if relation.type == 'OneToMany'
if relation.type == 'OneToMany' || relation.type == 'PolymorphicOneToMany'
dissociate_or_delete_one_to_many(relation, args[:params]['relation_name'], parent_id, is_delete_mode,
filter)
else
Expand All@@ -47,7 +47,12 @@ def dissociate_or_delete_one_to_many(relation, relation_name, parent_id, is_dele
if is_delete_mode
@child_collection.delete(@caller, foreign_filter)
else
@child_collection.update(@caller, foreign_filter, { relation.origin_key => nil })
patch = if relation.type == 'PolymorphicOneToMany'
{ relation.origin_key => nil, relation.origin_type_field => nil }
else
{ relation.origin_key => nil }
end
@child_collection.update(@caller, foreign_filter, patch)
end
end

Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,6 +8,7 @@ class UpdateRelated < AbstractRelatedRoute
include ForestAdminAgent::Builder
include ForestAdminDatasourceToolkit::Utils
include ForestAdminDatasourceToolkit::Components::Query

def setup_routes
add_route(
'forest_related_update',
Expand All@@ -26,14 +27,19 @@ def handle_request(args = {})
relation = @collection.schema[:fields][args[:params]['relation_name']]
parent_id = Utils::Id.unpack_id(@collection, args[:params]['id'])

linked_id = if (id = args.dig(:params, :data, :id))
linked_id = if (id = args.dig(:params, 'data', 'id'))
Utils::Id.unpack_id(@child_collection, id)
end

if relation.type == 'ManyToOne'
case relation.type
when 'ManyToOne'
update_many_to_one(relation, parent_id, linked_id)
elsif relation.type == 'OneToOne'
when 'PolymorphicManyToOne'
update_polymorphic_many_to_one(relation, parent_id, linked_id)
when 'OneToOne'
update_one_to_one(relation, parent_id, linked_id)
when 'PolymorphicOneToOne'
update_polymorphic_one_to_one(relation, parent_id, linked_id)
end

{ content: nil, status: 204 }
Expand All@@ -49,16 +55,104 @@ def update_many_to_one(relation, parent_id, linked_id)
@collection.update(@caller, Filter.new(condition_tree: fk_owner), { relation.foreign_key => foreign_value })
end

def update_polymorphic_many_to_one(relation, parent_id, linked_id)
foreign_value = if linked_id
Collection.get_value(
@child_collection,
@caller,
linked_id,
relation.foreign_key_targets[@child_collection.name]
)
end

polymorphic_type = @child_collection.name.gsub('__', '::')
fk_owner = ConditionTree::ConditionTreeFactory.match_ids(@collection, [parent_id])
@collection.update(
@caller,
Filter.new(condition_tree: fk_owner),
{
relation.foreign_key => foreign_value,
relation.foreign_key_type_field => polymorphic_type
}
)
end

def update_polymorphic_one_to_one(relation, parent_id, linked_id)
origin_value = Collection.get_value(@collection, @caller, parent_id, relation.origin_key_target)

break_old_polymorphic_one_to_one_relationship(relation, origin_value, linked_id)
create_new_polymorphic_one_to_one_relationship(relation, origin_value, linked_id)
end

def update_one_to_one(relation, parent_id, linked_id)
origin_value = Collection.get_value(@collection, @caller, parent_id, relation.origin_key_target)

break_old_one_to_one_relationship(nil, relation, origin_value, linked_id)
create_new_one_to_one_relationship(nil, relation, origin_value, linked_id)
break_old_one_to_one_relationship(relation, origin_value, linked_id)
create_new_one_to_one_relationship(relation, origin_value, linked_id)
end

def break_old_one_to_one_relationship(_scope, relation, origin_value, linked_id)
def break_old_polymorphic_one_to_one_relationship(relation, origin_value, linked_id)
linked_id ||= []

old_fk_owner_filter = Filter.new(
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
[
@permissions.get_scope(@collection),
ConditionTree::Nodes::ConditionTreeBranch.new(
'And',
[
ConditionTree::Nodes::ConditionTreeLeaf.new(
relation.origin_key,
ConditionTree::Operators::EQUAL,
origin_value
),
ConditionTree::Nodes::ConditionTreeLeaf.new(
relation.origin_type_field,
ConditionTree::Operators::EQUAL,
@collection.name.gsub('__', '::')
)
]
),
# Don't set the new record's field to null
# if it's already initialized with the right value
ConditionTree::ConditionTreeFactory.match_ids(@child_collection, [linked_id]).inverse
]
)
)

result = @child_collection.aggregate(@caller, old_fk_owner_filter, Aggregation.new(operation: 'Count'), 1)
return unless !(result[0]['value']).nil? && (result[0]['value']).positive?

# Avoids updating records to null if it's not authorized by the ORM
# and if there is no record to update (the filter returns no record)

@child_collection.update(
@caller,
old_fk_owner_filter,
{ relation.origin_key => nil, relation.origin_type_field => nil }
)
end

def create_new_polymorphic_one_to_one_relationship(relation, origin_value, linked_id)
return unless linked_id

new_fk_owner = ConditionTree::ConditionTreeFactory.match_ids(@child_collection, [linked_id])

@child_collection.update(
@caller,
Filter.new(
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
[
@permissions.get_scope(@collection), new_fk_owner
]
)
),
{ relation.origin_key => origin_value, relation.origin_type_field => @collection.name.gsub('__', '::') }
)
end

def break_old_one_to_one_relationship(relation, origin_value, linked_id)
linked_id ||= []
old_fk_owner_filter = Filter.new(
condition_tree: ConditionTree::ConditionTreeFactory.intersect(
[
Expand All@@ -67,26 +161,24 @@ def break_old_one_to_one_relationship(_scope, relation, origin_value, linked_id)
relation.origin_key,
ConditionTree::Operators::EQUAL,
origin_value
)
].push(
),
# Don't set the new record's field to null
# if it's already initialized with the right value
ConditionTree::ConditionTreeFactory.match_ids(@child_collection, [linked_id]).inverse
)
]
)
)

result = @child_collection.aggregate(@caller, old_fk_owner_filter, Aggregation.new(operation: 'Count'), 1)

return unless (result[0][:value]).positive?
return unless !(result[0]['value']).nil? && (result[0]['value']).positive?

# Avoids updating records to null if it's not authorized by the ORM
# and if there is no record to update (the filter returns no record)

@child_collection.update(@caller, old_fk_owner_filter, { relation.origin_key => nil })
end

def create_new_one_to_one_relationship(_scope, relation, origin_value, linked_id)
def create_new_one_to_one_relationship(relation, origin_value, linked_id)
return unless linked_id

new_fk_owner = ConditionTree::ConditionTreeFactory.match_ids(@child_collection, [linked_id])
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ def handle_request(args = {})
class_name: @collection.name,
is_collection: false,
serializer: Serializer::ForestSerializer,
include: projection.relations.keys
include: projection.relations(only_keys: true)
)
}
end
Expand Down
Loading