Skip to content

Commit

Permalink
Merge pull request #1 from AuroraWebSoftware/flexy-field-boolean
Browse files Browse the repository at this point in the history
Flexy field boolean
  • Loading branch information
emreakay authored Sep 14, 2024
2 parents 0f0b7d1 + 865afa4 commit ee062e8
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 10 deletions.
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ It enables on-the-fly definition of fields, field types, validation, and value a
## Key Features

- Dynamically add fields to any Eloquent model.
- Supports field types: String, Integer, Decimal, Date, and DateTime.
- Supports field types: String, Integer, Decimal, Date, Boolean and DateTime.
- Field-level validation using Laravel’s validation rules.
- Query models by flexible field values using Elequent's native methods.
- Supports multiple field types and data storage through a pivot view.
Expand Down Expand Up @@ -69,16 +69,19 @@ $product = Product::create(['name' => 'Training Shoes']);
$product->flexy->color = 'blue'; // string
$product->flexy->price = 49.90; // decimal value
$product->flexy->size = 42; // integer
$product->flexy->gender = 'man'; // integer
$product->flexy->gender = 'man'; // string
$product->flexy->in_stock = true; // boolean
$product->save();

// Retrieve the flexy fields using flexy attribute
echo $product->flexy->color; // Outputs 'blue'
echo $product->flexy->size; // Outputs 42`
echo $product->flexy->in_stock; // Outputs true`

// or retrieve the flexy fields using default models' attribute with flexy_ prefix
echo $product->flexy_color; // Outputs 'blue'
echo $product->flexy_size; // Outputs 42`
echo $product->flexy_in_stock; // Outputs true`

```

Expand All @@ -92,7 +95,8 @@ You can define shapes dynamically with `setFlexyShape()` for fields to apply val
use AuroraWebSoftware\FlexyField\Enums\FlexyFieldType;

Product::setFlexyShape('color', FlexyFieldType::STRING, 1, 'required');
Product::setFlexyShape('size', FlexyFieldType::INTEGER, 2, 'numeric|min:20');`
Product::setFlexyShape('size', FlexyFieldType::INTEGER, 2, 'numeric|min:20');
Product::setFlexyShape('in_stock', FlexyFieldType::BOOLEAN, 3, 'required|bool');
```

This ensures that when saving the `color` field, it must be a required, and the `size` field must be a number greater than or equal to 20.
Expand Down Expand Up @@ -140,6 +144,7 @@ $products = Product::whereFlexyColor('blue')->get();
// Find models with multiple conditions on flexy fields
$models = Product::where('flexy_color', 'blue 1')
->where('flexy_price', '<', 100)
->where('flexy_in_stock', true)
->get();
```

Expand All @@ -151,7 +156,8 @@ Flexy fields can be validated using the shapes you define. For example, you can
use AuroraWebSoftware\FlexyField\Enums\FlexyFieldType;

User::setFlexyShape('username', FlexyFieldType::STRING, 1, 'required|max:20');
User::setFlexyShape('score', FlexyFieldType::INTEGER, 2, 'numeric|min:0|max:100');`
User::setFlexyShape('score', FlexyFieldType::INTEGER, 2, 'numeric|min:0|max:100');
User::setFlexyShape('banned', FlexyFieldType::BOOLEAN, 3, 'bool');
```


Expand All @@ -160,6 +166,7 @@ If a user tries to save invalid data, Laravel's native validation system kicks i
```php
$user->flexy->username = 'too_long_username_exceeding_the_limit';
$user->flexy->score = 120;
$user->flexy->banned = false;
$user->save(); // ValidationException thrown due to invalid data`
```

Expand Down Expand Up @@ -214,6 +221,18 @@ You can run the tests provided using [Pest](https://pestphp.com/) to ensure ev
./vendor/bin/pest
```

#### Code Style

```shell
./vendor/bin/pint
```

#### Static Analyze

```shell
./vendor/bin/phpstan analyse
```

## License

The FlexyField package is open-sourced software licensed under the MIT License.
Expand Down
1 change: 1 addition & 0 deletions database/migrations/create_flexyfield_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function up(): void
$table->decimal('value_decimal')->nullable();
$table->bigInteger('value_int')->nullable();
$table->string('value_string')->nullable();
$table->boolean('value_boolean')->nullable();
$table->timestamps();

$table->unique(['model_type', 'model_id', 'field_name']);
Expand Down
1 change: 1 addition & 0 deletions src/Enums/FlexyFieldType.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ enum FlexyFieldType: string
case DECIMAL = 'decimal';
case INTEGER = 'integer';
case STRING = 'string';
case BOOLEAN = 'boolean';
}
16 changes: 11 additions & 5 deletions src/FlexyField.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static function dropAndCreatePivotViewForMysql(): void
CONCAT(
'MAX(CASE WHEN field_name = ''',
field_name,
''' THEN COALESCE(value_date, value_datetime, value_decimal, value_int, value_string, NULL) END) AS `flexy_',
''' THEN COALESCE(value_date, value_datetime, value_decimal, value_int, value_string, value_boolean, NULL) END) AS `flexy_',
field_name,
'`'
)
Expand Down Expand Up @@ -64,6 +64,7 @@ public static function dropAndCreatePivotViewForMysql(): void

}

//TODO BOOLEAN values will be checked
/**
* @throws Exception
*/
Expand All @@ -75,11 +76,16 @@ public static function dropAndCreatePivotViewForPostgres(): void
sql TEXT;
BEGIN
-- Concatenate column names using STRING_AGG for dynamic pivot column generation
SELECT STRING_AGG(
SELECT STRING_AGG(
'MAX(CASE WHEN field_name = ''' || field_name || ''' THEN ' ||
'COALESCE(CAST(value_date AS TEXT), CAST(value_datetime AS TEXT), CAST(value_decimal AS TEXT), CAST(value_int AS TEXT), value_string) ' ||
'END) AS flexy_' || field_name,
', ')
'CASE ' ||
'WHEN value_date IS NOT NULL THEN value_date::TEXT ' ||
'WHEN value_datetime IS NOT NULL THEN value_datetime::TEXT ' ||
'WHEN value_decimal IS NOT NULL THEN value_decimal::TEXT ' ||
'WHEN value_int IS NOT NULL THEN value_int::TEXT ' ||
'WHEN value_boolean IS NOT NULL THEN CASE WHEN value_boolean THEN ''true'' ELSE ''false'' END ' ||
'ELSE value_string END ' ||
'END) AS \"flexy_' || field_name || '\"', ', ')
INTO sql
FROM (SELECT DISTINCT field_name FROM ff_values) AS distinct_fields;
Expand Down
5 changes: 4 additions & 1 deletion src/Traits/Flexy.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ public static function bootFlexy(): void
$addition['value_decimal'] = $value;
} elseif ($value instanceof DateTime) {
$addition['value_datetime'] = $value;
} elseif (is_bool($value)) {
$addition['value_boolean'] = $value;
} else {
throw new FlexyFieldTypeNotAllowedException;
}
Expand Down Expand Up @@ -158,7 +160,8 @@ public function flexy(): Attribute
$value->value_datetime ??
$value->value_decimal ??
$value->value_int ??
$value->value_string ?? null;
$value->value_string ??
$value->value_boolean ?? null;
});
}

Expand Down
73 changes: 73 additions & 0 deletions tests/PackageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,76 @@

expect(ExampleShapelyFlexyModel::where('flexy_a', 5)->get())->toHaveCount(1);
});

it('can test set, get and delete a shape for a flexy model bool', function () {
$flexyModel = ExampleFlexyModel::setFlexyShape(
'test_boolean',
FlexyFieldType::BOOLEAN,
1,
fieldMetadata: ['a' => 3, 'b' => true],
);
expect($flexyModel)->toBeInstanceOf(Shape::class)
->and(ExampleFlexyModel::getFlexyShape('test_boolean')->count())->toBe(1);

ExampleFlexyModel::deleteFlexyShape('test_boolean');
expect(ExampleFlexyModel::getFlexyShape('test_boolean'))->toBeNull();

});

it('can create shape for a model and validate and save bool', function () {
// $flexyModel1 = ExampleShapelyFlexyModel::create(['name' => 'ExampleFlexyModel 1']);
// ExampleShapelyFlexyModel::$hasShape = true;
//
// ExampleShapelyFlexyModel::setFlexyShape('a', FlexyFieldType::BOOLEAN, 1, 'required|bool');
//
// $flexyModel1->flexy->a = false;
// $flexyModel1->save();
//
// expect(ExampleShapelyFlexyModel::where('flexy_a', false)->get())->toHaveCount(1);
// expect(ExampleShapelyFlexyModel::where('flexy_a', true)->get())->toHaveCount(0);
//
// $flexyModel2 = ExampleShapelyFlexyModel::create(['name' => 'ExampleFlexyModel 2']);
// ExampleShapelyFlexyModel::$hasShape = true;
//
// ExampleShapelyFlexyModel::setFlexyShape('b', FlexyFieldType::BOOLEAN, 1, 'required|bool');
//
// $flexyModel2->flexy->b = true;
// $flexyModel2->save();
//
// expect(ExampleShapelyFlexyModel::where('flexy_b', true)->get())->toHaveCount(1);
// expect(ExampleShapelyFlexyModel::where('flexy_b', false)->get())->toHaveCount(0);

$flexyModel3 = ExampleShapelyFlexyModel::create(['name' => 'ExampleFlexyModel 3']);
ExampleShapelyFlexyModel::$hasShape = true;

ExampleShapelyFlexyModel::setFlexyShape('c', FlexyFieldType::BOOLEAN, 1, 'required|bool');

$flexyModel3->flexy->c = 1;
$flexyModel3->save();

expect(ExampleShapelyFlexyModel::where('flexy_c', 1)->get())->toHaveCount(1);
expect(ExampleShapelyFlexyModel::where('flexy_c', 0)->get())->toHaveCount(0);

$flexyModel4 = ExampleShapelyFlexyModel::create(['name' => 'ExampleFlexyModel 4']);
ExampleShapelyFlexyModel::$hasShape = true;

ExampleShapelyFlexyModel::setFlexyShape('d', FlexyFieldType::BOOLEAN, 1, 'required|bool');

$flexyModel4->flexy->d = 0;
$flexyModel4->save();

expect(ExampleShapelyFlexyModel::where('flexy_d', 0)->get())->toHaveCount(1);
expect(ExampleShapelyFlexyModel::where('flexy_d', 1)->get())->toHaveCount(0);
});

it('can create shape for a model and save bool', function () {
$flexyModel1 = ExampleShapelyFlexyModel::create(['name' => 'ExampleFlexyModel 1']);
ExampleShapelyFlexyModel::$hasShape = true;

ExampleShapelyFlexyModel::setFlexyShape('a', FlexyFieldType::BOOLEAN, 1);

$flexyModel1->flexy->a = false;
$flexyModel1->save();

expect(ExampleShapelyFlexyModel::getFlexyShape('a'))->toBeInstanceOf(Shape::class);
});

0 comments on commit ee062e8

Please sign in to comment.