{"id":15683478,"url":"https://github.com/estepnv/leafy","last_synced_at":"2025-05-07T13:05:21.336Z","repository":{"id":56880903,"uuid":"155096549","full_name":"estepnv/leafy","owner":"estepnv","description":"Toolkit for custom attributes in Ruby apps","archived":false,"fork":false,"pushed_at":"2020-09-28T18:51:08.000Z","size":43,"stargazers_count":4,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-07T13:05:15.203Z","etag":null,"topics":["attributes","custom","custom-attributes","custom-fields","json","mixin","rails","rails-engine","ruby"],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/estepnv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-10-28T17:25:32.000Z","updated_at":"2020-09-28T18:51:10.000Z","dependencies_parsed_at":"2022-08-20T13:00:43.339Z","dependency_job_id":null,"html_url":"https://github.com/estepnv/leafy","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estepnv%2Fleafy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estepnv%2Fleafy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estepnv%2Fleafy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/estepnv%2Fleafy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/estepnv","download_url":"https://codeload.github.com/estepnv/leafy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252883215,"owners_count":21819160,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["attributes","custom","custom-attributes","custom-fields","json","mixin","rails","rails-engine","ruby"],"created_at":"2024-10-03T17:06:03.876Z","updated_at":"2025-05-07T13:05:21.309Z","avatar_url":"https://github.com/estepnv.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 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)\n\nA toolkit for dynamic custom attributes for Ruby applications.\n\n* Simple modular design - load only things you need\n* Stored as JSON with your models - allows you to avoid expensive JOIN queries (supports postgresql json/jsonb data types)\n* Type inference - Infers type from custom field data\n* Add your own custom field types\n\nSupported data types:\n- `string` - strings\n- `integer` - integer numbers\n- `double` - floating point numbers\n- `datetime` - `Time` instances\n- `date` - `Date` instances\n- `bool` - `TrueClass` and `FalseClass` instances\n\n## Quick start\n\nAdd Leafy to Gemfile\n\n```ruby\ngem 'leafy-ruby'\n```\n\n**Plain Ruby app**\n\nInclude \"Plain old ruby object\" mixin into your class definition to start using leafy\n\n```ruby\nclass SchemaHost \u003c ActiveRecord::Base\n  include Leafy::Mixin::Schema[:poro]\n\n  attr_accessor :leafy_data\nend\n\nclass FieldsHost \u003c ActiveRecord::Base\n  include Leafy::Mixin::Fields[:poro]\n\n  attr_accessor :leafy_data\n  attr_accessor :leafy_fields\nend\n```\n\nSchema mixin introduces next methods:\n\n- `#leafy_fields (Schema)` returns Schema instance allowing you to iterate through custom attribute definitions.\n- `#leafy_fields=` schema setter method\n- `#leafy_fields_attributes=` nested attributes setter method\n\nFields mixin:\n\n- `#leafy_values (Hash)` returns a hash representation of your fields data\n- `#leafy_values=` allows you to assign custom attributes data\n- `#leafy_fields_values (Leafy::FieldValueCollection)`  returns a collection of `Field::Value` instances which provide more control over values data\n\n**Please note**:\nLeafy is stateless and changing Schema instance won't reflect on your active record model instance.\nFor changes to take place you have to explicitly assign schema or attributes data to the model.\n\n\n\n```ruby\nhost = SchemaHost.new\nhost.leafy_fields_attributes = [\n  { name: \"Field 1\", type: :integer, id: \"id_1\", metadata: { default: 1, placeholder: \"enter an integer\", required: true } },\n  { name: \"Field 2\", type: :string, id: \"id_2\", metadata: { default: \"\", placeholder: \"enter value\" } },\n  { name: \"Field 3\", type: :datetime, id: \"id_3\", metadata: { order: 10000 } }\n]\n\n# or build schema yourself\n\nfield_1 = Leafy::Field.new(name: \"Field 1\", type: :integer, id: \"id_1\", metadata: { default: 1, placeholder: \"enter an integer\", required: true })\nfield_2 = Leafy::Field.new(name: \"Field 2\", type: :string, id: \"id_2\", metadata: { default: \"\", placeholder: \"enter value\" })\nfield_3 = Leafy::Field.new(name: \"Field 3\", type: :datetime, id: \"id_3\", metadata: { order: 10000 })\n\nschema = Leafy::Schema.new\nschema \u003c\u003c field_1\nschema \u003c\u003c field_2\nschema \u003c\u003c field_3\n\nhost.leafy_fields = schema\n\n# after that reference schema for fields target instance\n\ntarget = FieldsHost.new\ntarget.leafy_fields = host.leafy_fields\ntarget.leafy_values\n\n# =\u003e { \"id_1\" =\u003e nil, \"id_2\" =\u003e nil, \"id_3\" =\u003e nil }\n\ntarget.leafy_values = { \"id_1\": 123, \"id_2\": \"test\", \"id_3\": Time.new(2018,10,10, 10,10,10, \"+03:00\"), \"junk\": \"some junk data\" }\ntarget.leafy_values\n\n# =\u003e { \"id_1\": 123, \"id_2\": \"test\", \"id_3\": Time.new(2018,10,10, 10,10,10, \"+03:00\") }\n```\n\n**ActiveRecord**\n\nAdd migration\n```ruby\nadd_column :schema_hosts, :leafy_data, :text, null: false, default: \"{}\"\nadd_column :fields_hosts, :leafy_data, :text, null: false, default: \"{}\"\n# for postgresql\n# add_column :leafy_data, :jsonb, null: false, default: {}\n```\n\nUpdate your models\n\n```ruby\nclass SchemaHost \u003c ActiveRecord::Base\n  include Leafy::Mixin::Schema[:active_record]\nend\n\nclass FieldsHost \u003c ActiveRecord::Base\n  include Leafy::Mixin::Fields[:active_record]\n\n  belongs_to :schema_host, required: true\n  delegate :leafy_fields, to: :schema_host\nend\n```\n\n```ruby\nhost = SchemaHost.create(\n  leafy_fields_attributes: [\n    { name: \"Field 1\", type: :integer, id: \"id_1\", metadata: { default: 1, placeholder: \"enter an integer\", required: true } },\n    { name: \"Field 2\", type: :string, id: \"id_2\", metadata: { default: \"\", placeholder: \"enter value\" } },\n    { name: \"Field 3\", type: :datetime, id: \"id_3\", metadata: { order: 10000 } }\n  ]\n)\n\ntarget = FieldsHost.create(schema_host: host)\ntarget.leafy_values\n\n# =\u003e { \"id_1\" =\u003e nil, \"id_2\" =\u003e nil, \"id_3\" =\u003e nil }\n\ntarget.leafy_values = { \"id_1\": 123, \"id_2\": \"test\", \"id_3\": Time.new(2018,10,10, 10,10,10, \"+03:00\"), \"junk\": \"some junk data\" }\ntarget.save!\ntarget.reload\n\ntarget.leafy_values\n\n# =\u003e { \"id_1\": 123, \"id_2\": \"test\", \"id_3\": Time.new(2018,10,10, 10,10,10, \"+03:00\") }\n```\n\n## Configuration\n\nIn you initialization code\n\nIf you get a `NameError: uninitialized constant` in Rails, please ensure you have required leafy in an initializer.\n\nin `app/config/initializers/leafy.rb` simply add `require 'leafy'`\n\n```ruby\nclass MyLovelyCoder\n  def dump(data)\n    \"lovely_#{data}\"\n  end\n\n  def load(data)\n    data.split(\"_\")[1]\n  end\nend\n\nLeafy.configure do |config|\n  # you may wonna use oj instead\n  config.coder = MyLovelyCoder.new\nend\n```\n\n## Adding your own types\n\nLeafy allows adding your own data types\nTo 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.\n\nConverter instance has to implement `#dump` and `#load` methods\n\n```ruby\nclass MyComplexTypeConverter\n  def self.load(json_string)\n    #  parsing logic\n    return MyComplexType.new(parsed_data)\n  end\n\n  def self.dump(my_complex_type_instance)\n    # serializing logic\n    return json\n  end\nend\n\nLeafy.register_converter(:complex_type, MyComplexTypeConverter)\n```\n\n\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/estepnv/leafy.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Festepnv%2Fleafy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Festepnv%2Fleafy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Festepnv%2Fleafy/lists"}