leafy-ruby 0.1.1 → 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 +5 -5
- data/.github/workflows/ci.yml +87 -0
- data/.gitignore +2 -0
- data/GITHUB_ACTIONS_MIGRATION.md +81 -0
- data/Gemfile +24 -0
- data/README.md +307 -79
- data/docker-compose.yml +23 -0
- data/leafy.gemspec +30 -7
- data/lib/leafy/coder/default.rb +2 -0
- data/lib/leafy/coder/mock.rb +2 -0
- data/lib/leafy/configuration.rb +4 -2
- 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/mixin/active_record/fields.rb +2 -2
- data/lib/leafy/mixin/data_accessor.rb +2 -0
- 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 +13 -3
- metadata +16 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
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
|
@@ -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,40 +1,78 @@
|
|
|
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
|
|
56
|
+
|
|
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)
|
|
27
65
|
|
|
28
|
-
|
|
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
|
|
@@ -42,37 +80,67 @@ class FieldsHost < ActiveRecord::Base
|
|
|
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
|
|
@@ -120,74 +204,218 @@ class FieldsHost < ActiveRecord::Base
|
|
|
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
|
|
142
|
-
|
|
143
|
-
# => { "id_1": 123, "id_2": "test", "id_3": Time.new(2018,10,10, 10,10,10, "+03:00") }
|
|
250
|
+
# => { "id_1" => 123, "id_2" => "test", "id_3" => 2018-10-10 07:10:10 UTC }
|
|
144
251
|
```
|
|
145
252
|
|
|
146
253
|
## Configuration
|
|
147
254
|
|
|
148
|
-
|
|
255
|
+
### Rails Setup
|
|
256
|
+
|
|
257
|
+
If you get a `NameError: uninitialized constant` error in Rails, create an initializer:
|
|
149
258
|
|
|
150
259
|
```ruby
|
|
151
|
-
|
|
260
|
+
# config/initializers/leafy.rb
|
|
261
|
+
require 'leafy'
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Custom Coder
|
|
265
|
+
|
|
266
|
+
By default, Leafy uses the JSON module for serialization. You can configure a custom coder (e.g., Oj for better performance):
|
|
267
|
+
|
|
268
|
+
```ruby
|
|
269
|
+
# config/initializers/leafy.rb
|
|
270
|
+
require 'leafy'
|
|
271
|
+
require 'oj'
|
|
272
|
+
|
|
273
|
+
class OjCoder
|
|
152
274
|
def dump(data)
|
|
153
|
-
|
|
275
|
+
Oj.dump(data)
|
|
154
276
|
end
|
|
155
277
|
|
|
156
278
|
def load(data)
|
|
157
|
-
|
|
279
|
+
Oj.load(data)
|
|
158
280
|
end
|
|
159
281
|
end
|
|
160
282
|
|
|
161
283
|
Leafy.configure do |config|
|
|
162
|
-
|
|
163
|
-
config.coder = MyLovelyCoder.new
|
|
284
|
+
config.coder = OjCoder.new
|
|
164
285
|
end
|
|
165
286
|
```
|
|
166
287
|
|
|
167
|
-
|
|
288
|
+
**Note:** Your coder must implement both `#dump` and `#load` instance methods.
|
|
168
289
|
|
|
169
|
-
|
|
170
|
-
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.
|
|
290
|
+
## Custom Field Types
|
|
171
291
|
|
|
172
|
-
|
|
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:
|
|
173
297
|
|
|
174
298
|
```ruby
|
|
175
|
-
class
|
|
176
|
-
def
|
|
177
|
-
|
|
178
|
-
|
|
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
|
|
179
304
|
end
|
|
180
305
|
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
306
|
+
def load(value)
|
|
307
|
+
return nil if value.nil?
|
|
308
|
+
# Convert cents back to Money object
|
|
309
|
+
Money.new(value.to_i)
|
|
184
310
|
end
|
|
185
311
|
end
|
|
186
312
|
|
|
187
|
-
|
|
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
|
+
}
|
|
188
356
|
```
|
|
189
357
|
|
|
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
|
|
190
417
|
|
|
418
|
+
**Solution:** Converters will attempt to coerce values. For strict validation, implement it in your converter's `#dump` or `#load` methods.
|
|
191
419
|
|
|
192
420
|
## Contributing
|
|
193
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
|
@@ -32,17 +32,40 @@ Gem::Specification.new do |spec|
|
|
|
32
32
|
spec.add_development_dependency "rspec", "~> 3.0"
|
|
33
33
|
spec.add_development_dependency "simplecov", '~> 0.17.1'
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
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"
|
|
37
40
|
else
|
|
38
|
-
spec.add_development_dependency "activerecord", "~> 5.2"
|
|
41
|
+
spec.add_development_dependency "activerecord", "~> 5.2.0"
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
if RUBY_ENGINE == "jruby"
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
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
|
|
47
70
|
end
|
|
48
71
|
end
|
data/lib/leafy/coder/default.rb
CHANGED
data/lib/leafy/coder/mock.rb
CHANGED
data/lib/leafy/configuration.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
|
|
3
5
|
module Leafy
|
|
@@ -11,9 +13,9 @@ module Leafy
|
|
|
11
13
|
def coder=(value)
|
|
12
14
|
if value.respond_to?(:dump) && value.respond_to?(:load)
|
|
13
15
|
@coder = value
|
|
16
|
+
else
|
|
17
|
+
raise ArgumentError, "coder must implement #dump and #load"
|
|
14
18
|
end
|
|
15
|
-
|
|
16
|
-
raise ArgumentError, "coder must implement #dump and #load"
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
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)
|
|
@@ -24,12 +24,12 @@ 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
31
|
field_value_collection = ::Leafy::FieldValueCollection.new(leafy_fields, ar_json: activerecord_json_column?)
|
|
32
|
-
field_value_collection.load(
|
|
32
|
+
field_value_collection.load(_leafy_data || '{}')
|
|
33
33
|
field_value_collection
|
|
34
34
|
end
|
|
35
35
|
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,7 +6,7 @@ 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
12
|
require "leafy/coder/default"
|
|
@@ -16,13 +16,19 @@ require "leafy/configuration"
|
|
|
16
16
|
|
|
17
17
|
# module definition
|
|
18
18
|
module Leafy
|
|
19
|
+
@config_mutex = Mutex.new
|
|
20
|
+
@converters_mutex = Mutex.new
|
|
19
21
|
|
|
20
22
|
def self.configure
|
|
21
23
|
yield configuration if block_given?
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def self.configuration
|
|
25
|
-
@config
|
|
27
|
+
return @config if defined?(@config) && @config
|
|
28
|
+
|
|
29
|
+
@config_mutex.synchronize do
|
|
30
|
+
@config ||= Leafy::Configuration.new
|
|
31
|
+
end
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def self.register_converter(name, converter)
|
|
@@ -36,7 +42,11 @@ module Leafy
|
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def self.converters
|
|
39
|
-
@converters
|
|
45
|
+
return @converters if defined?(@converters) && @converters
|
|
46
|
+
|
|
47
|
+
@converters_mutex.synchronize do
|
|
48
|
+
@converters ||= {}
|
|
49
|
+
end
|
|
40
50
|
end
|
|
41
51
|
end
|
|
42
52
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leafy-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgeny Stepanov
|
|
8
|
-
autorequire:
|
|
9
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
13
|
name: rspec
|
|
@@ -44,42 +43,42 @@ dependencies:
|
|
|
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
55
|
name: sqlite3
|
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
|
58
57
|
requirements:
|
|
59
|
-
- - "
|
|
58
|
+
- - "~>"
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
60
|
+
version: '1.4'
|
|
62
61
|
type: :development
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
|
-
- - "
|
|
65
|
+
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
67
|
+
version: '1.4'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: pg
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
72
71
|
requirements:
|
|
73
|
-
- - "
|
|
72
|
+
- - "<"
|
|
74
73
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '0'
|
|
74
|
+
version: '2.0'
|
|
76
75
|
type: :development
|
|
77
76
|
prerelease: false
|
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
78
|
requirements:
|
|
80
|
-
- - "
|
|
79
|
+
- - "<"
|
|
81
80
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '0'
|
|
81
|
+
version: '2.0'
|
|
83
82
|
description: |2
|
|
84
83
|
Leafy is toolkit that allows you to integrate dynamic custom attributes functionality into your ruby application.
|
|
85
84
|
|
|
@@ -93,15 +92,18 @@ executables:
|
|
|
93
92
|
extensions: []
|
|
94
93
|
extra_rdoc_files: []
|
|
95
94
|
files:
|
|
95
|
+
- ".github/workflows/ci.yml"
|
|
96
96
|
- ".gitignore"
|
|
97
97
|
- ".rspec"
|
|
98
98
|
- ".travis.yml"
|
|
99
|
+
- GITHUB_ACTIONS_MIGRATION.md
|
|
99
100
|
- Gemfile
|
|
100
101
|
- LICENSE.txt
|
|
101
102
|
- README.md
|
|
102
103
|
- Rakefile
|
|
103
104
|
- bin/console
|
|
104
105
|
- bin/setup
|
|
106
|
+
- docker-compose.yml
|
|
105
107
|
- leafy.gemspec
|
|
106
108
|
- lib/leafy.rb
|
|
107
109
|
- lib/leafy/coder/default.rb
|
|
@@ -132,7 +134,6 @@ homepage: https://github.com/estepnv/leafy
|
|
|
132
134
|
licenses:
|
|
133
135
|
- MIT
|
|
134
136
|
metadata: {}
|
|
135
|
-
post_install_message:
|
|
136
137
|
rdoc_options: []
|
|
137
138
|
require_paths:
|
|
138
139
|
- lib
|
|
@@ -147,9 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
147
148
|
- !ruby/object:Gem::Version
|
|
148
149
|
version: '0'
|
|
149
150
|
requirements: []
|
|
150
|
-
|
|
151
|
-
rubygems_version: 2.5.2.3
|
|
152
|
-
signing_key:
|
|
151
|
+
rubygems_version: 3.6.9
|
|
153
152
|
specification_version: 4
|
|
154
153
|
summary: Toolkit for custom attributes in Ruby apps
|
|
155
154
|
test_files: []
|