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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e9b8ab7fa5fce879b69856041e24097cc574e41343f3c954c6256d45b8746c0
4
- data.tar.gz: de0763a761626b180748303a01bd0cf9d403d012ac13c61af212051e1a65b35a
3
+ metadata.gz: 60d25d9e5f13760ff97626b8baf31cb98fd941cfc250c8585cfd06a758b3c121
4
+ data.tar.gz: 572e02908880e0d8a72f0bd5c15752127b56732196b5ea829d49faf7d3c85744
5
5
  SHA512:
6
- metadata.gz: e5390a02d111263752b418777d2cdcee9e224a9f3c52db573362da4d63f1af9f66f3cf17865d978c95d5317b18d87b4a379161f5f40efbc09bae1335ffca710e
7
- data.tar.gz: d3e94469b3c0a7d22de8415a5f22550ac90dbf40f72c3986181914c75127c631c53fa1cc97cda8b40199cae5d7bdf4401e7e48071b4fd4dfa208d428565826c3
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
@@ -9,6 +9,10 @@
9
9
  /.idea/*
10
10
  /coverage/*
11
11
  .rbenv-gemsets
12
- Gemfile.lock
13
12
  # rspec failure tracking
14
13
  .rspec_status
14
+ /.history/*
15
+ Gemfile.lock
16
+ .ruby-version
17
+ .docker
18
+ vendor/
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: gem install bundler
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 [![Build Status](https://travis-ci.org/estepnv/leafy.svg?branch=master)](https://travis-ci.org/estepnv/leafy) [![Maintainability](https://api.codeclimate.com/v1/badges/5108d8a1ac5e2915f30f/maintainability)](https://codeclimate.com/github/estepnv/leafy/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/5108d8a1ac5e2915f30f/test_coverage)](https://codeclimate.com/github/estepnv/leafy/test_coverage)
1
+ # Leafy [![CI](https://github.com/estepnv/leafy/actions/workflows/ci.yml/badge.svg)](https://github.com/estepnv/leafy/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/estepnv/leafy/branch/master/graph/badge.svg)](https://codecov.io/gh/estepnv/leafy) [![Maintainability](https://api.codeclimate.com/v1/badges/5108d8a1ac5e2915f30f/maintainability)](https://codeclimate.com/github/estepnv/leafy/maintainability)
2
2
 
3
3
  A toolkit for dynamic custom attributes for Ruby applications.
4
4
 
5
- * Simple modular design - load only things you need
6
- * Stored as JSON with your models - allows you to avoid expensive JOIN queries (supports postgresql json/jsonb data types)
7
- * Type inference - Infers type from custom field data
8
- * Add your own custom field types
9
-
10
- Supported data types:
11
- - `string` - strings
12
- - `integer` - integer numbers
13
- - `double` - floating point numbers
14
- - `datetime` - `Time` instances
15
- - `date` - `Date` instances
16
- - `bool` - `TrueClass` and `FalseClass` instances
17
-
18
- ## Quick start
19
-
20
- Add Leafy to Gemfile
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
- **Plain Ruby app**
49
+ Then run:
50
+
51
+ ```bash
52
+ bundle install
53
+ ```
54
+
55
+ ## Requirements
27
56
 
28
- Include "Plain old ruby object" mixin into your class definition to start using leafy
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 < ActiveRecord::Base
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 < ActiveRecord::Base
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 introduces next methods:
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
- Fields mixin:
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
- - `#leafy_values (Hash)` returns a hash representation of your fields data
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
- **Please note**:
58
- Leafy is stateless and changing Schema instance won't reflect on your active record model instance.
59
- For changes to take place you have to explicitly assign schema or attributes data to the model.
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
- { name: "Field 1", type: :integer, id: "id_1", metadata: { default: 1, placeholder: "enter an integer", required: true } },
67
- { name: "Field 2", type: :string, id: "id_2", metadata: { default: "", placeholder: "enter value" } },
68
- { name: "Field 3", type: :datetime, id: "id_3", metadata: { order: 10000 } }
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
- # or build schema yourself
72
-
73
- field_1 = Leafy::Field.new(name: "Field 1", type: :integer, id: "id_1", metadata: { default: 1, placeholder: "enter an integer", required: true })
74
- field_2 = Leafy::Field.new(name: "Field 2", type: :string, id: "id_2", metadata: { default: "", placeholder: "enter value" })
75
- field_3 = Leafy::Field.new(name: "Field 3", type: :datetime, id: "id_3", metadata: { order: 10000 })
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
- # after that reference schema for fields target instance
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
- target.leafy_values = { "id_1": 123, "id_2": "test", "id_3": Time.new(2018,10,10, 10,10,10, "+03:00"), "junk": "some junk data" }
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
- # => { "id_1": 123, "id_2": "test", "id_3": Time.new(2018,10,10, 10,10,10, "+03:00") }
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
- **ActiveRecord**
172
+ ### ActiveRecord Integration
173
+
174
+ #### 1. Create a migration
99
175
 
100
- Add migration
101
176
  ```ruby
102
- add_column :schema_hosts, :leafy_data, :text, null: false, default: "{}"
103
- add_column :fields_hosts, :leafy_data, :text, null: false, default: "{}"
104
- # for postgresql
105
- # add_column :leafy_data, :jsonb, null: false, default: {}
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
- { name: "Field 1", type: :integer, id: "id_1", metadata: { default: 1, placeholder: "enter an integer", required: true } },
127
- { name: "Field 2", type: :string, id: "id_2", metadata: { default: "", placeholder: "enter value" } },
128
- { name: "Field 3", type: :datetime, id: "id_3", metadata: { order: 10000 } }
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 = { "id_1": 123, "id_2": "test", "id_3": Time.new(2018,10,10, 10,10,10, "+03:00"), "junk": "some junk data" }
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
- # => { "id_1": 123, "id_2": "test", "id_3": Time.new(2018,10,10, 10,10,10, "+03:00") }
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
- ## Adding your own types
264
+ ### Custom Coder
147
265
 
148
- Leafy allows adding your own data types
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
- Converter instance has to implement `#dump` and `#load` methods
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 MyComplexTypeConverter
155
- def self.load(json_string)
156
- # parsing logic
157
- return MyComplexType.new(parsed_data)
158
- end
159
-
160
- def self.dump(my_complex_type_instance)
161
- # serializing logic
162
- return json
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
- Leafy.register_converter(:complex_type, MyComplexTypeConverter)
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
 
@@ -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 = "exe"
27
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
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
- if RUBY_VERSION >= "2.2.2"
37
- spec.add_development_dependency "activerecord", "~> 5.0"
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", "~> 4.2"
41
+ spec.add_development_dependency "activerecord", "~> 5.2.0"
40
42
  end
41
43
 
42
44
  if RUBY_ENGINE == "jruby"
43
- spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", "51"
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
- spec.add_development_dependency "sqlite3"
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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leafy
4
+ module Coder
5
+ Default = JSON
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Leafy
4
+ module Coder
5
+ class Mock
6
+ def dump(data)
7
+ data
8
+ end
9
+
10
+ def load(data)
11
+ data
12
+ end
13
+ end
14
+ end
15
+ 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) ? (value.downcase rescue nil) : value
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.dup
12
+ target = value
13
13
  target = load(target) if target.is_a?(String)
14
- target = target.dup.to_date if target.is_a?(Time)
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")
@@ -9,7 +9,7 @@ module Leafy
9
9
  def dump(value)
10
10
  return if value.nil?
11
11
 
12
- target = value.dup
12
+ target = value
13
13
  target = load(target) if target.is_a?(String)
14
14
 
15
15
  raise(ArgumentError, "is not a Time object") unless target.is_a?(Time)
@@ -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.dup.to_a.map { |pair| [pair[0].to_sym, pair[1]]}.to_h
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
- attributes = attributes.dup.to_a.map { |pair| [pair[0].to_s, pair[1]]}.to_h
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 = attributes[field_value.id]
53
+ field_value.value = _attributes[field_value.id]
47
54
  end
48
55
  end
49
56
 
50
- def self.dump(field_values_collection)
51
- JSON.dump(field_values_collection.map { |field_value| [field_value.id, field_value.raw] }.to_h)
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 self.load(leafy_fields, data)
55
- Leafy::FieldValueCollection.new(leafy_fields, JSON.load(data))
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.leafy_data = ::Leafy::FieldValueCollection.dump(field_value_list)
27
+ self._leafy_data = field_value_list.dump
28
28
  end
29
29
 
30
30
  def leafy_field_values
31
- ::Leafy::FieldValueCollection.load(leafy_fields, leafy_data || "{}")
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Leafy
2
4
  module Mixin
3
5
  module DataAccessor
@@ -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 = ::Leafy::FieldValueCollection.dump(field_value_list)
25
+ self._leafy_data = field_value_list.dump
26
26
  end
27
27
 
28
28
  def leafy_field_values
29
- ::Leafy::FieldValueCollection.load(leafy_fields, _leafy_data || "{}")
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
@@ -39,7 +39,9 @@ module Leafy
39
39
  end
40
40
 
41
41
  def self.load(data)
42
- Schema.new(JSON.parse(data))
42
+ parsed = JSON.parse(data)
43
+ raise(ArgumentError, "Schema data must be an array") unless parsed.is_a?(Array)
44
+ Schema.new(parsed)
43
45
  end
44
46
 
45
47
  alias :<< :push
data/lib/leafy/utils.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Leafy
2
4
  module Utils
3
5
  def self.symbolize_keys(hash)
data/lib/leafy/version.rb CHANGED
@@ -7,8 +7,8 @@ module Leafy
7
7
 
8
8
  module VERSION
9
9
  MAJOR = 0
10
- MINOR = 0
11
- TINY = 2
10
+ MINOR = 2
11
+ TINY = 0
12
12
  PRE = nil
13
13
 
14
14
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
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.expand_path("../leafy/converter/**/*.rb", __FILE__)].each { |f| require f }
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.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeny Stepanov
8
- autorequire:
9
- bindir: exe
8
+ bindir: bin
10
9
  cert_chain: []
11
- date: 2018-11-04 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: bundler
13
+ name: rspec
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '1.16'
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: '1.16'
25
+ version: '3.0'
27
26
  - !ruby/object:Gem::Dependency
28
- name: rake
27
+ name: simplecov
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: '10.0'
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: '10.0'
39
+ version: 0.17.1
41
40
  - !ruby/object:Gem::Dependency
42
- name: rspec
41
+ name: activerecord
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '3.0'
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: '3.0'
53
+ version: 6.1.0
55
54
  - !ruby/object:Gem::Dependency
56
- name: simplecov
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: '5.0'
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: '5.0'
67
+ version: '1.4'
83
68
  - !ruby/object:Gem::Dependency
84
- name: sqlite3
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
- rubyforge_project:
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: []