leafy-ruby 0.0.2 → 0.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +87 -0
- data/.gitignore +5 -1
- data/.travis.yml +21 -5
- data/GITHUB_ACTIONS_MIGRATION.md +81 -0
- data/Gemfile +24 -0
- data/README.md +328 -79
- data/docker-compose.yml +23 -0
- data/leafy.gemspec +34 -10
- data/lib/leafy/coder/default.rb +7 -0
- data/lib/leafy/coder/mock.rb +15 -0
- data/lib/leafy/configuration.rb +21 -0
- data/lib/leafy/converter/bool_converter.rb +9 -1
- data/lib/leafy/converter/date_converter.rb +2 -2
- data/lib/leafy/converter/datetime_converter.rb +1 -1
- data/lib/leafy/field_value.rb +1 -1
- data/lib/leafy/field_value_collection.rb +27 -18
- data/lib/leafy/mixin/active_record/fields.rb +4 -2
- data/lib/leafy/mixin/data_accessor.rb +2 -0
- data/lib/leafy/mixin/poro/fields.rb +4 -2
- data/lib/leafy/schema.rb +3 -1
- data/lib/leafy/utils.rb +2 -0
- data/lib/leafy/version.rb +2 -2
- data/lib/leafy.rb +24 -2
- metadata +30 -40
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60d25d9e5f13760ff97626b8baf31cb98fd941cfc250c8585cfd06a758b3c121
|
|
4
|
+
data.tar.gz: 572e02908880e0d8a72f0bd5c15752127b56732196b5ea829d49faf7d3c85744
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aea5d1a9ebf0ebf91e89058ee4749692635fdb7971e8bb6da881d896747776dabb0a66a10515e0addb35a464b62f262a11593d18236d1ce63c2e8bf8334dcc18
|
|
7
|
+
data.tar.gz: fc1538fb57c85f902986c2615656361f0281e1e84ed029f13b7eae11058951790275ce77f39000d5625950cc2fd32b7b41374991f78f72922a26dede91b855ea
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ master, main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ master, main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
ruby:
|
|
17
|
+
- '2.4'
|
|
18
|
+
- '2.5'
|
|
19
|
+
- '2.6'
|
|
20
|
+
- '2.7'
|
|
21
|
+
- '3.0'
|
|
22
|
+
- '3.1'
|
|
23
|
+
- '3.2'
|
|
24
|
+
- '3.3'
|
|
25
|
+
include:
|
|
26
|
+
- ruby: 'head'
|
|
27
|
+
continue-on-error: true
|
|
28
|
+
- ruby: 'jruby-9.4'
|
|
29
|
+
continue-on-error: true
|
|
30
|
+
# Ruby 2.2-2.3 are very old and may have stability issues
|
|
31
|
+
- ruby: '2.2'
|
|
32
|
+
continue-on-error: true
|
|
33
|
+
- ruby: '2.3'
|
|
34
|
+
continue-on-error: true
|
|
35
|
+
|
|
36
|
+
continue-on-error: ${{ matrix.continue-on-error || false }}
|
|
37
|
+
|
|
38
|
+
services:
|
|
39
|
+
postgres:
|
|
40
|
+
image: postgres:13
|
|
41
|
+
env:
|
|
42
|
+
POSTGRES_USER: root
|
|
43
|
+
POSTGRES_PASSWORD: 111
|
|
44
|
+
POSTGRES_DB: postgres
|
|
45
|
+
ports:
|
|
46
|
+
- 5432:5432
|
|
47
|
+
options: >-
|
|
48
|
+
--health-cmd pg_isready
|
|
49
|
+
--health-interval 10s
|
|
50
|
+
--health-timeout 5s
|
|
51
|
+
--health-retries 5
|
|
52
|
+
|
|
53
|
+
mysql:
|
|
54
|
+
image: mysql:8.0
|
|
55
|
+
env:
|
|
56
|
+
MYSQL_ROOT_PASSWORD: 111
|
|
57
|
+
MYSQL_DATABASE: leafy_test
|
|
58
|
+
ports:
|
|
59
|
+
- 3306:3306
|
|
60
|
+
options: >-
|
|
61
|
+
--health-cmd "mysqladmin ping"
|
|
62
|
+
--health-interval 10s
|
|
63
|
+
--health-timeout 5s
|
|
64
|
+
--health-retries 5
|
|
65
|
+
|
|
66
|
+
env:
|
|
67
|
+
COVERAGE: 1
|
|
68
|
+
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
|
|
72
|
+
- name: Set up Ruby
|
|
73
|
+
uses: ruby/setup-ruby@v1
|
|
74
|
+
with:
|
|
75
|
+
ruby-version: ${{ matrix.ruby }}
|
|
76
|
+
bundler-cache: true
|
|
77
|
+
|
|
78
|
+
- name: Run tests
|
|
79
|
+
run: bundle exec rspec
|
|
80
|
+
|
|
81
|
+
- name: Upload coverage to Codecov
|
|
82
|
+
if: matrix.ruby == '3.3'
|
|
83
|
+
uses: codecov/codecov-action@v4
|
|
84
|
+
with:
|
|
85
|
+
files: ./coverage/.resultset.json
|
|
86
|
+
flags: ruby-${{ matrix.ruby }}
|
|
87
|
+
fail_ci_if_error: false
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
|
@@ -2,21 +2,35 @@ sudo: false
|
|
|
2
2
|
|
|
3
3
|
language: ruby
|
|
4
4
|
|
|
5
|
+
services:
|
|
6
|
+
- postgresql
|
|
7
|
+
|
|
8
|
+
addons:
|
|
9
|
+
postgresql: "10"
|
|
10
|
+
apt:
|
|
11
|
+
packages:
|
|
12
|
+
- postgresql-10
|
|
13
|
+
- postgresql-client-10
|
|
14
|
+
|
|
5
15
|
rvm:
|
|
6
|
-
- 2.2
|
|
7
16
|
- 2.3
|
|
8
17
|
- 2.4
|
|
9
18
|
- 2.5
|
|
19
|
+
- 2.6
|
|
20
|
+
- 2.7
|
|
10
21
|
- ruby-head
|
|
11
|
-
- jruby-9.2.0.0
|
|
12
22
|
- jruby-head
|
|
13
23
|
|
|
14
24
|
matrix:
|
|
15
25
|
allow_failures:
|
|
26
|
+
- rvm: jruby-9.2.0.0
|
|
27
|
+
- rvm: 2.2
|
|
16
28
|
- rvm: ruby-head
|
|
17
29
|
- rvm: jruby-head
|
|
18
30
|
|
|
19
|
-
before_install:
|
|
31
|
+
before_install:
|
|
32
|
+
- yes | gem update --system --force
|
|
33
|
+
- gem install bundler
|
|
20
34
|
|
|
21
35
|
before_script:
|
|
22
36
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
|
@@ -32,7 +46,9 @@ cache: bundler
|
|
|
32
46
|
|
|
33
47
|
env:
|
|
34
48
|
global:
|
|
35
|
-
CC_TEST_REPORTER_ID=5c94ea74238649cee4b51abbe647de62a3d63971eaeca9aa58c82b62d9a0e6a8
|
|
36
|
-
COVERAGE=1
|
|
49
|
+
- CC_TEST_REPORTER_ID=5c94ea74238649cee4b51abbe647de62a3d63971eaeca9aa58c82b62d9a0e6a8
|
|
50
|
+
- COVERAGE=1
|
|
51
|
+
- PGPORT=5433
|
|
52
|
+
|
|
37
53
|
|
|
38
54
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Migration from Travis CI to GitHub Actions
|
|
2
|
+
|
|
3
|
+
## Changes Made
|
|
4
|
+
|
|
5
|
+
### 1. GitHub Actions Workflow (`.github/workflows/ci.yml`)
|
|
6
|
+
- Created a new CI workflow that tests against multiple Ruby versions
|
|
7
|
+
- **Ruby versions tested**: 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, head, jruby-9.4
|
|
8
|
+
- Set up PostgreSQL and MySQL services (matching your docker-compose setup)
|
|
9
|
+
- Integrated Code Climate test reporter for coverage (only on Ruby 3.3)
|
|
10
|
+
- Allow failures for Ruby head and JRuby (won't block CI)
|
|
11
|
+
|
|
12
|
+
### 2. Updated `leafy.gemspec`
|
|
13
|
+
- Improved ActiveRecord version selection based on Ruby version:
|
|
14
|
+
- Ruby 2.2-2.4: ActiveRecord ~> 5.2
|
|
15
|
+
- Ruby 2.5-2.6: ActiveRecord ~> 6.0
|
|
16
|
+
- Ruby 2.7+: ActiveRecord ~> 6.1
|
|
17
|
+
- Added pg version constraint `< 2.0` for better compatibility
|
|
18
|
+
|
|
19
|
+
### 3. Updated `Gemfile`
|
|
20
|
+
- Made Ruby 3.4+ bundled gems conditional (only loaded when RUBY_VERSION >= 3.4.0)
|
|
21
|
+
- This ensures older Ruby versions don't have issues with gems they don't need
|
|
22
|
+
|
|
23
|
+
### 4. Updated `spec/spec_helper.rb`
|
|
24
|
+
- Made bundled gem requires conditional for Ruby 3.4+
|
|
25
|
+
- Ensures compatibility across all Ruby versions
|
|
26
|
+
|
|
27
|
+
### 5. Updated `README.md`
|
|
28
|
+
- Replaced Travis CI badge with GitHub Actions badge
|
|
29
|
+
|
|
30
|
+
## Testing the Setup
|
|
31
|
+
|
|
32
|
+
### Local Testing
|
|
33
|
+
Ensure tests still pass locally:
|
|
34
|
+
```bash
|
|
35
|
+
bundle install
|
|
36
|
+
bundle exec rspec
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Testing with Different Ruby Versions (using Docker)
|
|
40
|
+
```bash
|
|
41
|
+
# Ruby 2.7
|
|
42
|
+
docker run -it --rm -v $(pwd):/app -w /app ruby:2.7 bash -c "bundle install && bundle exec rspec"
|
|
43
|
+
|
|
44
|
+
# Ruby 3.0
|
|
45
|
+
docker run -it --rm -v $(pwd):/app -w /app ruby:3.0 bash -c "bundle install && bundle exec rspec"
|
|
46
|
+
|
|
47
|
+
# Ruby 3.1
|
|
48
|
+
docker run -it --rm -v $(pwd):/app -w /app ruby:3.1 bash -c "bundle install && bundle exec rspec"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## What to Do Next
|
|
52
|
+
|
|
53
|
+
1. **Commit the changes**:
|
|
54
|
+
```bash
|
|
55
|
+
git add .github/workflows/ci.yml
|
|
56
|
+
git add Gemfile leafy.gemspec spec/spec_helper.rb README.md
|
|
57
|
+
git rm .travis.yml
|
|
58
|
+
git commit -m "Migrate from Travis CI to GitHub Actions"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. **Push to GitHub**:
|
|
62
|
+
```bash
|
|
63
|
+
git push origin master
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
3. **Verify the workflow**:
|
|
67
|
+
- Go to your repository on GitHub
|
|
68
|
+
- Click on the "Actions" tab
|
|
69
|
+
- You should see the CI workflow running
|
|
70
|
+
- Check that tests pass for all Ruby versions
|
|
71
|
+
|
|
72
|
+
## Notes
|
|
73
|
+
|
|
74
|
+
- The workflow runs on every push to `master`/`main` branches and on pull requests
|
|
75
|
+
- Ruby 2.2-2.4 may have limitations with newer gems, so ActiveRecord 5.2 is used
|
|
76
|
+
- Ruby 3.4+ requires explicit bundled gem declarations (erb, logger, mutex_m, etc.)
|
|
77
|
+
- Code Climate coverage is only uploaded from Ruby 3.3 builds to avoid duplicates
|
|
78
|
+
|
|
79
|
+
## Cleanup
|
|
80
|
+
|
|
81
|
+
You can safely delete `.travis.yml` after confirming GitHub Actions is working.
|
data/Gemfile
CHANGED
|
@@ -4,3 +4,27 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
|
4
4
|
|
|
5
5
|
# Specify your gem's dependencies in leafy.gemspec
|
|
6
6
|
gemspec
|
|
7
|
+
|
|
8
|
+
# Ruby 2.5+ bundled gems needed by ActiveRecord 6.0+
|
|
9
|
+
if RUBY_VERSION >= "2.5.0"
|
|
10
|
+
gem 'logger'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Ruby 3.0+ additional bundled gems
|
|
14
|
+
if RUBY_VERSION >= "3.0.0"
|
|
15
|
+
gem 'mutex_m'
|
|
16
|
+
gem 'base64'
|
|
17
|
+
gem 'benchmark' # Needed by ActiveSupport in Ruby 3.3+/4.x
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Ruby 3.1+ additional bundled gems
|
|
21
|
+
if RUBY_VERSION >= "3.1.0"
|
|
22
|
+
gem 'csv'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Ruby 3.4+ additional bundled gems
|
|
26
|
+
if RUBY_VERSION >= "3.4.0"
|
|
27
|
+
gem 'erb'
|
|
28
|
+
gem 'bigdecimal'
|
|
29
|
+
gem 'drb'
|
|
30
|
+
end
|
data/README.md
CHANGED
|
@@ -1,78 +1,146 @@
|
|
|
1
|
-
# Leafy [](https://github.com/estepnv/leafy/actions/workflows/ci.yml) [](https://codecov.io/gh/estepnv/leafy) [](https://codeclimate.com/github/estepnv/leafy/maintainability)
|
|
2
2
|
|
|
3
3
|
A toolkit for dynamic custom attributes for Ruby applications.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Features](#features)
|
|
8
|
+
- [Supported Data Types](#supported-data-types)
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Requirements](#requirements)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Plain Ruby Objects (PORO)](#plain-ruby-objects-poro)
|
|
13
|
+
- [ActiveRecord Integration](#activerecord-integration)
|
|
14
|
+
- [Configuration](#configuration)
|
|
15
|
+
- [Custom Field Types](#custom-field-types)
|
|
16
|
+
- [Best Practices](#best-practices)
|
|
17
|
+
- [API Reference](#api-reference)
|
|
18
|
+
- [Troubleshooting](#troubleshooting)
|
|
19
|
+
- [Contributing](#contributing)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
* **Simple modular design** - Load only what you need
|
|
25
|
+
* **JSON-backed storage** - Store custom fields as JSON with your models, avoiding expensive JOIN queries
|
|
26
|
+
* **PostgreSQL support** - Native support for `json` and `jsonb` column types
|
|
27
|
+
* **Type safety** - Automatic type inference and validation for custom field data
|
|
28
|
+
* **Extensible** - Add your own custom field types with converters
|
|
29
|
+
* **Thread-safe** - Safe for concurrent access
|
|
30
|
+
|
|
31
|
+
## Supported Data Types
|
|
32
|
+
|
|
33
|
+
- `string` - String values
|
|
34
|
+
- `integer` - Integer numbers
|
|
35
|
+
- `double` - Floating point numbers
|
|
36
|
+
- `datetime` - `Time` instances (stored as ISO8601)
|
|
37
|
+
- `date` - `Date` instances (stored as ISO8601)
|
|
38
|
+
- `bool` - Boolean values (`true`/`false`)
|
|
39
|
+
- `dummy` - Pass-through type (no conversion)
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
Add Leafy to your Gemfile:
|
|
21
44
|
|
|
22
45
|
```ruby
|
|
23
46
|
gem 'leafy-ruby'
|
|
24
47
|
```
|
|
25
48
|
|
|
26
|
-
|
|
49
|
+
Then run:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bundle install
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Requirements
|
|
27
56
|
|
|
28
|
-
|
|
57
|
+
- Ruby 2.7 or higher
|
|
58
|
+
- ActiveRecord 6.0+ (optional, only if using ActiveRecord integration)
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
## Quick Start
|
|
63
|
+
|
|
64
|
+
### Plain Ruby Objects (PORO)
|
|
65
|
+
|
|
66
|
+
For plain Ruby objects, include the `:poro` mixin and provide a `leafy_data` accessor:
|
|
29
67
|
|
|
30
68
|
```ruby
|
|
31
|
-
class SchemaHost
|
|
69
|
+
class SchemaHost
|
|
32
70
|
include Leafy::Mixin::Schema[:poro]
|
|
33
|
-
|
|
71
|
+
|
|
34
72
|
attr_accessor :leafy_data
|
|
35
73
|
end
|
|
36
74
|
|
|
37
|
-
class FieldsHost
|
|
75
|
+
class FieldsHost
|
|
38
76
|
include Leafy::Mixin::Fields[:poro]
|
|
39
|
-
|
|
77
|
+
|
|
40
78
|
attr_accessor :leafy_data
|
|
41
79
|
attr_accessor :leafy_fields
|
|
42
80
|
end
|
|
43
81
|
```
|
|
44
82
|
|
|
45
|
-
Schema mixin
|
|
46
|
-
|
|
47
|
-
- `#leafy_fields (Schema)` returns Schema instance allowing you to iterate through custom attribute definitions.
|
|
48
|
-
- `#leafy_fields=` schema setter method
|
|
49
|
-
- `#leafy_fields_attributes=` nested attributes setter method
|
|
83
|
+
**Schema mixin provides:**
|
|
50
84
|
|
|
51
|
-
|
|
85
|
+
- `#leafy_fields` - Returns a `Leafy::Schema` instance for iterating through custom field definitions
|
|
86
|
+
- `#leafy_fields=` - Schema setter method
|
|
87
|
+
- `#leafy_fields_attributes=` - Nested attributes setter method
|
|
52
88
|
|
|
53
|
-
|
|
54
|
-
- `#leafy_values=` allows you to assign custom attributes data
|
|
55
|
-
- `#leafy_fields_values (Leafy::FieldValueCollection)` returns a collection of `Field::Value` instances which provide more control over values data
|
|
89
|
+
**Fields mixin provides:**
|
|
56
90
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
91
|
+
- `#leafy_values` - Returns a hash of field values
|
|
92
|
+
- `#leafy_values=` - Assigns custom field values
|
|
93
|
+
- `#leafy_field_values` - Returns a `Leafy::FieldValueCollection` for fine-grained control
|
|
60
94
|
|
|
95
|
+
**Important:** Leafy is stateless. Changing a Schema instance won't automatically update your model. You must explicitly assign the schema or attributes to persist changes.
|
|
61
96
|
|
|
97
|
+
#### Example Usage
|
|
62
98
|
|
|
63
99
|
```ruby
|
|
100
|
+
# Create a schema host
|
|
64
101
|
host = SchemaHost.new
|
|
102
|
+
|
|
103
|
+
# Define custom fields using attributes
|
|
65
104
|
host.leafy_fields_attributes = [
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
105
|
+
{
|
|
106
|
+
name: "Field 1",
|
|
107
|
+
type: :integer,
|
|
108
|
+
id: "id_1",
|
|
109
|
+
metadata: { default: 1, placeholder: "Enter an integer", required: true }
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "Field 2",
|
|
113
|
+
type: :string,
|
|
114
|
+
id: "id_2",
|
|
115
|
+
metadata: { default: "", placeholder: "Enter value" }
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "Field 3",
|
|
119
|
+
type: :datetime,
|
|
120
|
+
id: "id_3",
|
|
121
|
+
metadata: { order: 10000 }
|
|
122
|
+
}
|
|
69
123
|
]
|
|
70
124
|
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
125
|
+
# Or build the schema manually
|
|
126
|
+
field_1 = Leafy::Field.new(
|
|
127
|
+
name: "Field 1",
|
|
128
|
+
type: :integer,
|
|
129
|
+
id: "id_1",
|
|
130
|
+
metadata: { default: 1, placeholder: "Enter an integer", required: true }
|
|
131
|
+
)
|
|
132
|
+
field_2 = Leafy::Field.new(
|
|
133
|
+
name: "Field 2",
|
|
134
|
+
type: :string,
|
|
135
|
+
id: "id_2",
|
|
136
|
+
metadata: { default: "", placeholder: "Enter value" }
|
|
137
|
+
)
|
|
138
|
+
field_3 = Leafy::Field.new(
|
|
139
|
+
name: "Field 3",
|
|
140
|
+
type: :datetime,
|
|
141
|
+
id: "id_3",
|
|
142
|
+
metadata: { order: 10000 }
|
|
143
|
+
)
|
|
76
144
|
|
|
77
145
|
schema = Leafy::Schema.new
|
|
78
146
|
schema << field_1
|
|
@@ -81,31 +149,47 @@ schema << field_3
|
|
|
81
149
|
|
|
82
150
|
host.leafy_fields = schema
|
|
83
151
|
|
|
84
|
-
#
|
|
85
|
-
|
|
152
|
+
# Use the schema with a fields host
|
|
86
153
|
target = FieldsHost.new
|
|
87
154
|
target.leafy_fields = host.leafy_fields
|
|
88
|
-
target.leafy_values
|
|
89
155
|
|
|
156
|
+
# Initial values are nil
|
|
157
|
+
target.leafy_values
|
|
90
158
|
# => { "id_1" => nil, "id_2" => nil, "id_3" => nil }
|
|
91
159
|
|
|
92
|
-
|
|
93
|
-
target.leafy_values
|
|
160
|
+
# Set values (unknown fields are ignored)
|
|
161
|
+
target.leafy_values = {
|
|
162
|
+
"id_1" => 123,
|
|
163
|
+
"id_2" => "test",
|
|
164
|
+
"id_3" => Time.new(2018, 10, 10, 10, 10, 10, "+03:00"),
|
|
165
|
+
"junk" => "ignored"
|
|
166
|
+
}
|
|
94
167
|
|
|
95
|
-
|
|
168
|
+
target.leafy_values
|
|
169
|
+
# => { "id_1" => 123, "id_2" => "test", "id_3" => 2018-10-10 07:10:10 UTC }
|
|
96
170
|
```
|
|
97
171
|
|
|
98
|
-
|
|
172
|
+
### ActiveRecord Integration
|
|
173
|
+
|
|
174
|
+
#### 1. Create a migration
|
|
99
175
|
|
|
100
|
-
Add migration
|
|
101
176
|
```ruby
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
#
|
|
105
|
-
|
|
177
|
+
class AddLeafyData < ActiveRecord::Migration[6.1]
|
|
178
|
+
def change
|
|
179
|
+
# For text/string storage (all databases)
|
|
180
|
+
add_column :schema_hosts, :leafy_data, :text, null: false, default: "{}"
|
|
181
|
+
add_column :fields_hosts, :leafy_data, :text, null: false, default: "{}"
|
|
182
|
+
|
|
183
|
+
# For PostgreSQL with native JSON support (recommended)
|
|
184
|
+
# add_column :schema_hosts, :leafy_data, :jsonb, null: false, default: {}
|
|
185
|
+
# add_column :fields_hosts, :leafy_data, :jsonb, null: false, default: {}
|
|
186
|
+
# add_index :schema_hosts, :leafy_data, using: :gin
|
|
187
|
+
# add_index :fields_hosts, :leafy_data, using: :gin
|
|
188
|
+
end
|
|
189
|
+
end
|
|
106
190
|
```
|
|
107
191
|
|
|
108
|
-
Update your models
|
|
192
|
+
#### 2. Update your models
|
|
109
193
|
|
|
110
194
|
```ruby
|
|
111
195
|
class SchemaHost < ActiveRecord::Base
|
|
@@ -114,59 +198,224 @@ end
|
|
|
114
198
|
|
|
115
199
|
class FieldsHost < ActiveRecord::Base
|
|
116
200
|
include Leafy::Mixin::Fields[:active_record]
|
|
117
|
-
|
|
201
|
+
|
|
118
202
|
belongs_to :schema_host, required: true
|
|
119
|
-
delegate :leafy_fields, to: :schema_host
|
|
203
|
+
delegate :leafy_fields, to: :schema_host
|
|
120
204
|
end
|
|
121
205
|
```
|
|
122
206
|
|
|
207
|
+
#### 3. Usage
|
|
208
|
+
|
|
123
209
|
```ruby
|
|
210
|
+
# Create a schema host with custom fields
|
|
124
211
|
host = SchemaHost.create(
|
|
125
212
|
leafy_fields_attributes: [
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
|
|
213
|
+
{
|
|
214
|
+
name: "Field 1",
|
|
215
|
+
type: :integer,
|
|
216
|
+
id: "id_1",
|
|
217
|
+
metadata: { default: 1, placeholder: "Enter an integer", required: true }
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: "Field 2",
|
|
221
|
+
type: :string,
|
|
222
|
+
id: "id_2",
|
|
223
|
+
metadata: { default: "", placeholder: "Enter value" }
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
name: "Field 3",
|
|
227
|
+
type: :datetime,
|
|
228
|
+
id: "id_3",
|
|
229
|
+
metadata: { order: 10000 }
|
|
230
|
+
}
|
|
129
231
|
]
|
|
130
232
|
)
|
|
131
233
|
|
|
234
|
+
# Create a fields host and set values
|
|
132
235
|
target = FieldsHost.create(schema_host: host)
|
|
133
|
-
target.leafy_values
|
|
134
236
|
|
|
237
|
+
target.leafy_values
|
|
135
238
|
# => { "id_1" => nil, "id_2" => nil, "id_3" => nil }
|
|
136
239
|
|
|
137
|
-
target.leafy_values = {
|
|
240
|
+
target.leafy_values = {
|
|
241
|
+
"id_1" => 123,
|
|
242
|
+
"id_2" => "test",
|
|
243
|
+
"id_3" => Time.new(2018, 10, 10, 10, 10, 10, "+03:00"),
|
|
244
|
+
"junk" => "ignored"
|
|
245
|
+
}
|
|
138
246
|
target.save!
|
|
139
247
|
target.reload
|
|
140
248
|
|
|
141
249
|
target.leafy_values
|
|
250
|
+
# => { "id_1" => 123, "id_2" => "test", "id_3" => 2018-10-10 07:10:10 UTC }
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Configuration
|
|
142
254
|
|
|
143
|
-
|
|
255
|
+
### Rails Setup
|
|
256
|
+
|
|
257
|
+
If you get a `NameError: uninitialized constant` error in Rails, create an initializer:
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
# config/initializers/leafy.rb
|
|
261
|
+
require 'leafy'
|
|
144
262
|
```
|
|
145
263
|
|
|
146
|
-
|
|
264
|
+
### Custom Coder
|
|
147
265
|
|
|
148
|
-
Leafy
|
|
149
|
-
To allow leafy process your own data type you need to describe how to store it. For that purpose leafy utilizes converter classes associated for each type.
|
|
266
|
+
By default, Leafy uses the JSON module for serialization. You can configure a custom coder (e.g., Oj for better performance):
|
|
150
267
|
|
|
151
|
-
|
|
268
|
+
```ruby
|
|
269
|
+
# config/initializers/leafy.rb
|
|
270
|
+
require 'leafy'
|
|
271
|
+
require 'oj'
|
|
272
|
+
|
|
273
|
+
class OjCoder
|
|
274
|
+
def dump(data)
|
|
275
|
+
Oj.dump(data)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def load(data)
|
|
279
|
+
Oj.load(data)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
Leafy.configure do |config|
|
|
284
|
+
config.coder = OjCoder.new
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Note:** Your coder must implement both `#dump` and `#load` instance methods.
|
|
289
|
+
|
|
290
|
+
## Custom Field Types
|
|
291
|
+
|
|
292
|
+
Leafy allows you to add your own custom data types by registering converters.
|
|
293
|
+
|
|
294
|
+
### Creating a Converter
|
|
295
|
+
|
|
296
|
+
A converter is responsible for serializing (dump) and deserializing (load) your custom type. It must implement both `#dump` and `#load` instance methods:
|
|
152
297
|
|
|
153
298
|
```ruby
|
|
154
|
-
class
|
|
155
|
-
def
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
return
|
|
299
|
+
class MoneyConverter
|
|
300
|
+
def dump(value)
|
|
301
|
+
return nil if value.nil?
|
|
302
|
+
# Convert Money object to cents for storage
|
|
303
|
+
value.cents.to_s
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def load(value)
|
|
307
|
+
return nil if value.nil?
|
|
308
|
+
# Convert cents back to Money object
|
|
309
|
+
Money.new(value.to_i)
|
|
163
310
|
end
|
|
164
311
|
end
|
|
165
312
|
|
|
166
|
-
|
|
313
|
+
# Register the converter
|
|
314
|
+
Leafy.register_converter(:money, MoneyConverter.new)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Using Custom Types
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
schema = Leafy::Schema.new
|
|
321
|
+
schema << Leafy::Field.new(
|
|
322
|
+
name: "Price",
|
|
323
|
+
type: :money, # Your custom type
|
|
324
|
+
id: "price_field"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
host.leafy_fields = schema
|
|
328
|
+
target.leafy_fields = schema
|
|
329
|
+
|
|
330
|
+
target.leafy_values = { "price_field" => Money.new(1999) }
|
|
331
|
+
target.leafy_values["price_field"]
|
|
332
|
+
# => #<Money cents=1999>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Best Practices
|
|
336
|
+
|
|
337
|
+
### Field IDs
|
|
338
|
+
|
|
339
|
+
- Use stable, unique IDs for fields (UUIDs are generated automatically if not provided)
|
|
340
|
+
- Don't change field IDs after data has been stored
|
|
341
|
+
- Field IDs are the key for storing values - changing them will lose existing data
|
|
342
|
+
|
|
343
|
+
### Metadata
|
|
344
|
+
|
|
345
|
+
The `metadata` hash is completely flexible - store any additional information you need:
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
metadata: {
|
|
349
|
+
default: "some default",
|
|
350
|
+
placeholder: "Help text",
|
|
351
|
+
required: true,
|
|
352
|
+
order: 100,
|
|
353
|
+
validation_rules: { min: 0, max: 100 },
|
|
354
|
+
custom_property: "anything you want"
|
|
355
|
+
}
|
|
167
356
|
```
|
|
168
357
|
|
|
169
|
-
|
|
358
|
+
### Performance Tips
|
|
359
|
+
|
|
360
|
+
- Use PostgreSQL `jsonb` columns for better query performance and indexing
|
|
361
|
+
- Keep the number of custom fields reasonable (< 100 per model)
|
|
362
|
+
- Use GIN indexes on jsonb columns for field queries
|
|
363
|
+
- Consider using Oj or other fast JSON libraries as your coder
|
|
364
|
+
|
|
365
|
+
### Thread Safety
|
|
366
|
+
|
|
367
|
+
Leafy's class-level configuration and converter registry are thread-safe. You can safely register converters and configure Leafy from multiple threads or in multi-threaded web servers (Puma, Sidekiq, etc.).
|
|
368
|
+
|
|
369
|
+
## API Reference
|
|
370
|
+
|
|
371
|
+
### Schema Methods
|
|
372
|
+
|
|
373
|
+
- `Leafy::Schema.new(fields_array)` - Create a new schema
|
|
374
|
+
- `#push(field)` / `#<<(field)` - Add a field to the schema
|
|
375
|
+
- `#[](identifier)` - Find a field by ID
|
|
376
|
+
- `#ids` - Get array of all field IDs
|
|
377
|
+
- `#each` - Iterate through fields (Enumerable)
|
|
378
|
+
- `#serializable_hash` - Convert to hash representation
|
|
379
|
+
- `Leafy::Schema.dump(schema)` - Serialize to JSON string
|
|
380
|
+
- `Leafy::Schema.load(json_string)` - Deserialize from JSON string
|
|
381
|
+
|
|
382
|
+
### Field Methods
|
|
383
|
+
|
|
384
|
+
- `Leafy::Field.new(name:, type:, id:, metadata:)` - Create a new field
|
|
385
|
+
- `#name` - Field display name
|
|
386
|
+
- `#type` - Field type symbol
|
|
387
|
+
- `#id` - Unique field identifier
|
|
388
|
+
- `#metadata` - Custom metadata hash
|
|
389
|
+
- `#serializable_hash` - Convert to hash representation
|
|
390
|
+
|
|
391
|
+
### FieldValueCollection Methods
|
|
392
|
+
|
|
393
|
+
- `#values` - Get hash of all field values
|
|
394
|
+
- `#values=` - Set field values from hash
|
|
395
|
+
- `#each` - Iterate through field values (Enumerable)
|
|
396
|
+
- `#[](index)` - Access by array index
|
|
397
|
+
- `#size` / `#count` - Number of fields
|
|
398
|
+
|
|
399
|
+
## Troubleshooting
|
|
400
|
+
|
|
401
|
+
### NameError: uninitialized constant Leafy
|
|
402
|
+
|
|
403
|
+
**Solution:** Add `require 'leafy'` to your initializer file.
|
|
404
|
+
|
|
405
|
+
### Values not persisting in ActiveRecord
|
|
406
|
+
|
|
407
|
+
**Solution:** Make sure you call `save` or `save!` after setting `leafy_values`. Leafy setters update the model but don't automatically save.
|
|
408
|
+
|
|
409
|
+
### Custom converter not working
|
|
410
|
+
|
|
411
|
+
**Solution:** Ensure your converter:
|
|
412
|
+
1. Implements both `#dump` and `#load` as **instance methods** (not class methods)
|
|
413
|
+
2. Is registered before use: `Leafy.register_converter(:my_type, MyConverter.new)`
|
|
414
|
+
3. Handles `nil` values appropriately
|
|
415
|
+
|
|
416
|
+
### Type mismatch errors
|
|
417
|
+
|
|
418
|
+
**Solution:** Converters will attempt to coerce values. For strict validation, implement it in your converter's `#dump` or `#load` methods.
|
|
170
419
|
|
|
171
420
|
## Contributing
|
|
172
421
|
|
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: "2.4"
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
mysql:
|
|
5
|
+
image: mysql:8.0.20
|
|
6
|
+
environment:
|
|
7
|
+
- MYSQL_ROOT_PASSWORD=111
|
|
8
|
+
- MYSQL_PORT=3306
|
|
9
|
+
- MYSQL_LOWER_CASE_TABLE_NAMES=0
|
|
10
|
+
ports:
|
|
11
|
+
- 3306:3306
|
|
12
|
+
volumes:
|
|
13
|
+
- ./.docker/mysql/master/data:/var/lib/mysql:delegated
|
|
14
|
+
postgresql:
|
|
15
|
+
image: postgres:latest
|
|
16
|
+
environment:
|
|
17
|
+
- POSTGRES_PASSWORD=111
|
|
18
|
+
- POSTGRES_USER=root
|
|
19
|
+
- PGDATA=/var/lib/postgresql/data
|
|
20
|
+
ports:
|
|
21
|
+
- 5432:5432
|
|
22
|
+
volumes:
|
|
23
|
+
- ./.docker/postgres/data:/var/lib/postgresql/data/:delegated
|
data/leafy.gemspec
CHANGED
|
@@ -23,25 +23,49 @@ Gem::Specification.new do |spec|
|
|
|
23
23
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
24
24
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
25
25
|
end
|
|
26
|
-
spec.bindir = "
|
|
27
|
-
spec.executables = spec.files.grep(%r{^
|
|
26
|
+
spec.bindir = "bin"
|
|
27
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
28
28
|
spec.require_paths = ["lib"]
|
|
29
29
|
spec.required_ruby_version = '>= 2.2'
|
|
30
|
+
spec.platform = Gem::Platform::RUBY
|
|
30
31
|
|
|
31
|
-
spec.add_development_dependency "bundler", "~> 1.16"
|
|
32
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
|
33
32
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
34
|
-
spec.add_development_dependency "simplecov"
|
|
33
|
+
spec.add_development_dependency "simplecov", '~> 0.17.1'
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
# ActiveRecord version based on Ruby version
|
|
36
|
+
if RUBY_VERSION >= "2.7.0"
|
|
37
|
+
spec.add_development_dependency "activerecord", "~> 6.1.0"
|
|
38
|
+
elsif RUBY_VERSION >= "2.5.0"
|
|
39
|
+
spec.add_development_dependency "activerecord", "~> 6.0.0"
|
|
38
40
|
else
|
|
39
|
-
spec.add_development_dependency "activerecord", "~>
|
|
41
|
+
spec.add_development_dependency "activerecord", "~> 5.2.0"
|
|
40
42
|
end
|
|
41
43
|
|
|
42
44
|
if RUBY_ENGINE == "jruby"
|
|
43
|
-
|
|
45
|
+
# JDBC adapter version must match ActiveRecord version
|
|
46
|
+
if RUBY_VERSION >= "2.7.0"
|
|
47
|
+
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", "~> 61.0"
|
|
48
|
+
elsif RUBY_VERSION >= "2.5.0"
|
|
49
|
+
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", "~> 60.0"
|
|
50
|
+
else
|
|
51
|
+
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", "~> 52.0"
|
|
52
|
+
end
|
|
53
|
+
spec.add_development_dependency "jdbc-postgres"
|
|
44
54
|
else
|
|
45
|
-
|
|
55
|
+
# sqlite3 version based on Ruby version (1.4+ requires Ruby 2.5+)
|
|
56
|
+
if RUBY_VERSION >= "2.5.0"
|
|
57
|
+
spec.add_development_dependency "sqlite3", "~> 1.4"
|
|
58
|
+
else
|
|
59
|
+
spec.add_development_dependency "sqlite3", "~> 1.3.0"
|
|
60
|
+
end
|
|
61
|
+
# pg version constraint for older Ruby versions
|
|
62
|
+
if RUBY_VERSION >= "2.5.0"
|
|
63
|
+
spec.add_development_dependency "pg", "< 2.0"
|
|
64
|
+
elsif RUBY_VERSION >= "2.4.0"
|
|
65
|
+
spec.add_development_dependency "pg", "~> 1.0"
|
|
66
|
+
else
|
|
67
|
+
# Ruby 2.2-2.3 need older pg version to avoid segfaults
|
|
68
|
+
spec.add_development_dependency "pg", "~> 0.21.0"
|
|
69
|
+
end
|
|
46
70
|
end
|
|
47
71
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Leafy
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :coder
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@coder = Leafy::Coder::Default
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def coder=(value)
|
|
14
|
+
if value.respond_to?(:dump) && value.respond_to?(:load)
|
|
15
|
+
@coder = value
|
|
16
|
+
else
|
|
17
|
+
raise ArgumentError, "coder must implement #dump and #load"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -13,7 +13,15 @@ module Leafy
|
|
|
13
13
|
def load(value)
|
|
14
14
|
return if value.nil?
|
|
15
15
|
|
|
16
|
-
target = value.respond_to?(:downcase)
|
|
16
|
+
target = if value.respond_to?(:downcase)
|
|
17
|
+
begin
|
|
18
|
+
value.downcase
|
|
19
|
+
rescue StandardError
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
value
|
|
24
|
+
end
|
|
17
25
|
return true if ["1", "true", "t", 1, "yes", "y", true].include?(target)
|
|
18
26
|
return false if ["0", "false", "f", 0, "no", "n", false].include?(target)
|
|
19
27
|
|
|
@@ -9,9 +9,9 @@ module Leafy
|
|
|
9
9
|
def dump(value)
|
|
10
10
|
return if value.nil?
|
|
11
11
|
|
|
12
|
-
target = value
|
|
12
|
+
target = value
|
|
13
13
|
target = load(target) if target.is_a?(String)
|
|
14
|
-
target = target.
|
|
14
|
+
target = target.to_date if target.is_a?(Time)
|
|
15
15
|
|
|
16
16
|
unless target.is_a?(Date)
|
|
17
17
|
raise(ArgumentError, "is not a Date object")
|
data/lib/leafy/field_value.rb
CHANGED
|
@@ -5,7 +5,7 @@ module Leafy
|
|
|
5
5
|
attr_accessor :id, :name, :type, :raw, :converter
|
|
6
6
|
|
|
7
7
|
def initialize(attributes)
|
|
8
|
-
attributes = attributes.
|
|
8
|
+
attributes = attributes.transform_keys(&:to_sym)
|
|
9
9
|
|
|
10
10
|
self.id = attributes.fetch(:id)
|
|
11
11
|
self.name = attributes.fetch(:name)
|
|
@@ -4,6 +4,23 @@ module Leafy
|
|
|
4
4
|
class FieldValueCollection
|
|
5
5
|
include ::Enumerable
|
|
6
6
|
|
|
7
|
+
def initialize(leafy_fields, field_values: {}, ar_json: false)
|
|
8
|
+
@leafy_fields = leafy_fields
|
|
9
|
+
@coder = ar_json ? Leafy::Coder::Mock.new : Leafy.configuration.coder
|
|
10
|
+
self.leafy_field_values = field_values
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def leafy_field_values=(data)
|
|
14
|
+
@leafy_field_values = @leafy_fields.map do |custom_field|
|
|
15
|
+
Leafy::FieldValue.new(
|
|
16
|
+
id: custom_field.id,
|
|
17
|
+
name: custom_field.name,
|
|
18
|
+
raw: data[custom_field.id],
|
|
19
|
+
type: custom_field.type
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
7
24
|
def each
|
|
8
25
|
if block_given?
|
|
9
26
|
@leafy_field_values.each { |i| yield i }
|
|
@@ -20,18 +37,6 @@ module Leafy
|
|
|
20
37
|
count
|
|
21
38
|
end
|
|
22
39
|
|
|
23
|
-
def initialize(leafy_fields, values = {})
|
|
24
|
-
@leafy_fields = leafy_fields
|
|
25
|
-
@leafy_field_values = leafy_fields.map do |custom_field|
|
|
26
|
-
Leafy::FieldValue.new(
|
|
27
|
-
id: custom_field.id,
|
|
28
|
-
name: custom_field.name,
|
|
29
|
-
raw: values[custom_field.id],
|
|
30
|
-
type: custom_field.type
|
|
31
|
-
)
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
40
|
def values
|
|
36
41
|
inject({}) do |acc, field_value|
|
|
37
42
|
acc[field_value.id] = field_value.value
|
|
@@ -40,19 +45,23 @@ module Leafy
|
|
|
40
45
|
end
|
|
41
46
|
|
|
42
47
|
def values=(attributes = {})
|
|
43
|
-
|
|
48
|
+
_attributes = {}
|
|
49
|
+
|
|
50
|
+
attributes.each { |key, value| _attributes[key.to_s] = value }
|
|
44
51
|
|
|
45
52
|
@leafy_field_values.each do |field_value|
|
|
46
|
-
field_value.value =
|
|
53
|
+
field_value.value = _attributes[field_value.id]
|
|
47
54
|
end
|
|
48
55
|
end
|
|
49
56
|
|
|
50
|
-
def
|
|
51
|
-
|
|
57
|
+
def dump
|
|
58
|
+
data = {}
|
|
59
|
+
each { |field_value| data[field_value.id] = field_value.raw }
|
|
60
|
+
@coder.dump(data)
|
|
52
61
|
end
|
|
53
62
|
|
|
54
|
-
def
|
|
55
|
-
|
|
63
|
+
def load(data)
|
|
64
|
+
self.leafy_field_values = @coder.load(data)
|
|
56
65
|
end
|
|
57
66
|
|
|
58
67
|
end
|
|
@@ -24,11 +24,13 @@ module Leafy
|
|
|
24
24
|
field_value_list = leafy_field_values
|
|
25
25
|
field_value_list.values = attributes
|
|
26
26
|
|
|
27
|
-
self.
|
|
27
|
+
self._leafy_data = field_value_list.dump
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def leafy_field_values
|
|
31
|
-
::Leafy::FieldValueCollection.
|
|
31
|
+
field_value_collection = ::Leafy::FieldValueCollection.new(leafy_fields, ar_json: activerecord_json_column?)
|
|
32
|
+
field_value_collection.load(_leafy_data || '{}')
|
|
33
|
+
field_value_collection
|
|
32
34
|
end
|
|
33
35
|
end
|
|
34
36
|
end
|
|
@@ -22,11 +22,13 @@ module Leafy
|
|
|
22
22
|
field_value_list = leafy_field_values
|
|
23
23
|
field_value_list.values = attributes
|
|
24
24
|
|
|
25
|
-
self._leafy_data =
|
|
25
|
+
self._leafy_data = field_value_list.dump
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def leafy_field_values
|
|
29
|
-
::Leafy::FieldValueCollection.
|
|
29
|
+
field_value_collection = ::Leafy::FieldValueCollection.new(leafy_fields)
|
|
30
|
+
field_value_collection.load(_leafy_data || '{}')
|
|
31
|
+
field_value_collection
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
end
|
data/lib/leafy/schema.rb
CHANGED
data/lib/leafy/utils.rb
CHANGED
data/lib/leafy/version.rb
CHANGED
data/lib/leafy.rb
CHANGED
|
@@ -6,13 +6,31 @@ require "leafy/field"
|
|
|
6
6
|
require "leafy/schema"
|
|
7
7
|
require "leafy/field_value"
|
|
8
8
|
require "leafy/field_value_collection"
|
|
9
|
-
Dir[File.
|
|
9
|
+
Dir[File.join(__dir__, "leafy/converter/**/*.rb")].each { |f| require f }
|
|
10
10
|
require "leafy/mixin/schema"
|
|
11
11
|
require "leafy/mixin/fields"
|
|
12
|
+
require "leafy/coder/default"
|
|
13
|
+
require "leafy/coder/mock"
|
|
14
|
+
require "leafy/configuration"
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
# module definition
|
|
15
18
|
module Leafy
|
|
19
|
+
@config_mutex = Mutex.new
|
|
20
|
+
@converters_mutex = Mutex.new
|
|
21
|
+
|
|
22
|
+
def self.configure
|
|
23
|
+
yield configuration if block_given?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.configuration
|
|
27
|
+
return @config if defined?(@config) && @config
|
|
28
|
+
|
|
29
|
+
@config_mutex.synchronize do
|
|
30
|
+
@config ||= Leafy::Configuration.new
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
16
34
|
def self.register_converter(name, converter)
|
|
17
35
|
raise(ArgumentError, "converter is not provided") if converter.nil?
|
|
18
36
|
|
|
@@ -24,7 +42,11 @@ module Leafy
|
|
|
24
42
|
end
|
|
25
43
|
|
|
26
44
|
def self.converters
|
|
27
|
-
@converters
|
|
45
|
+
return @converters if defined?(@converters) && @converters
|
|
46
|
+
|
|
47
|
+
@converters_mutex.synchronize do
|
|
48
|
+
@converters ||= {}
|
|
49
|
+
end
|
|
28
50
|
end
|
|
29
51
|
end
|
|
30
52
|
|
metadata
CHANGED
|
@@ -1,99 +1,84 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leafy-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgeny Stepanov
|
|
8
|
-
|
|
9
|
-
bindir: exe
|
|
8
|
+
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: rspec
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
18
|
+
version: '3.0'
|
|
20
19
|
type: :development
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
25
|
+
version: '3.0'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: simplecov
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - "~>"
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
32
|
+
version: 0.17.1
|
|
34
33
|
type: :development
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - "~>"
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
39
|
+
version: 0.17.1
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
41
|
+
name: activerecord
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
44
43
|
requirements:
|
|
45
44
|
- - "~>"
|
|
46
45
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
46
|
+
version: 6.1.0
|
|
48
47
|
type: :development
|
|
49
48
|
prerelease: false
|
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
50
|
requirements:
|
|
52
51
|
- - "~>"
|
|
53
52
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
53
|
+
version: 6.1.0
|
|
55
54
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: activerecord
|
|
55
|
+
name: sqlite3
|
|
71
56
|
requirement: !ruby/object:Gem::Requirement
|
|
72
57
|
requirements:
|
|
73
58
|
- - "~>"
|
|
74
59
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
60
|
+
version: '1.4'
|
|
76
61
|
type: :development
|
|
77
62
|
prerelease: false
|
|
78
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
64
|
requirements:
|
|
80
65
|
- - "~>"
|
|
81
66
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
67
|
+
version: '1.4'
|
|
83
68
|
- !ruby/object:Gem::Dependency
|
|
84
|
-
name:
|
|
69
|
+
name: pg
|
|
85
70
|
requirement: !ruby/object:Gem::Requirement
|
|
86
71
|
requirements:
|
|
87
|
-
- - "
|
|
72
|
+
- - "<"
|
|
88
73
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '0'
|
|
74
|
+
version: '2.0'
|
|
90
75
|
type: :development
|
|
91
76
|
prerelease: false
|
|
92
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
78
|
requirements:
|
|
94
|
-
- - "
|
|
79
|
+
- - "<"
|
|
95
80
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '0'
|
|
81
|
+
version: '2.0'
|
|
97
82
|
description: |2
|
|
98
83
|
Leafy is toolkit that allows you to integrate dynamic custom attributes functionality into your ruby application.
|
|
99
84
|
|
|
@@ -101,21 +86,29 @@ description: |2
|
|
|
101
86
|
It ships with several basic data types and allows you to add your own data type converters.
|
|
102
87
|
email:
|
|
103
88
|
- estepnv@icloud.com
|
|
104
|
-
executables:
|
|
89
|
+
executables:
|
|
90
|
+
- console
|
|
91
|
+
- setup
|
|
105
92
|
extensions: []
|
|
106
93
|
extra_rdoc_files: []
|
|
107
94
|
files:
|
|
95
|
+
- ".github/workflows/ci.yml"
|
|
108
96
|
- ".gitignore"
|
|
109
97
|
- ".rspec"
|
|
110
98
|
- ".travis.yml"
|
|
99
|
+
- GITHUB_ACTIONS_MIGRATION.md
|
|
111
100
|
- Gemfile
|
|
112
101
|
- LICENSE.txt
|
|
113
102
|
- README.md
|
|
114
103
|
- Rakefile
|
|
115
104
|
- bin/console
|
|
116
105
|
- bin/setup
|
|
106
|
+
- docker-compose.yml
|
|
117
107
|
- leafy.gemspec
|
|
118
108
|
- lib/leafy.rb
|
|
109
|
+
- lib/leafy/coder/default.rb
|
|
110
|
+
- lib/leafy/coder/mock.rb
|
|
111
|
+
- lib/leafy/configuration.rb
|
|
119
112
|
- lib/leafy/converter/bool_converter.rb
|
|
120
113
|
- lib/leafy/converter/date_converter.rb
|
|
121
114
|
- lib/leafy/converter/datetime_converter.rb
|
|
@@ -141,7 +134,6 @@ homepage: https://github.com/estepnv/leafy
|
|
|
141
134
|
licenses:
|
|
142
135
|
- MIT
|
|
143
136
|
metadata: {}
|
|
144
|
-
post_install_message:
|
|
145
137
|
rdoc_options: []
|
|
146
138
|
require_paths:
|
|
147
139
|
- lib
|
|
@@ -156,9 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
148
|
- !ruby/object:Gem::Version
|
|
157
149
|
version: '0'
|
|
158
150
|
requirements: []
|
|
159
|
-
|
|
160
|
-
rubygems_version: 2.7.7
|
|
161
|
-
signing_key:
|
|
151
|
+
rubygems_version: 3.6.9
|
|
162
152
|
specification_version: 4
|
|
163
153
|
summary: Toolkit for custom attributes in Ruby apps
|
|
164
154
|
test_files: []
|