{"id":19070315,"url":"https://github.com/inhouse-work/protos-protoform","last_synced_at":"2026-03-02T15:41:23.357Z","repository":{"id":231171975,"uuid":"780850608","full_name":"inhouse-work/protos-protoform","owner":"inhouse-work","description":"Superform for Protos components","archived":false,"fork":false,"pushed_at":"2025-11-11T00:16:14.000Z","size":129,"stargazers_count":3,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-04T17:46:02.859Z","etag":null,"topics":["phlex","rails","ruby"],"latest_commit_sha":null,"homepage":"","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/inhouse-work.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-04-02T09:25:26.000Z","updated_at":"2025-11-11T00:16:18.000Z","dependencies_parsed_at":"2025-09-15T07:13:21.184Z","dependency_job_id":"91183d4b-d7bb-46c3-b2f8-e67af4fc8580","html_url":"https://github.com/inhouse-work/protos-protoform","commit_stats":null,"previous_names":["nolantait/protos-protoform","inhouse-work/protos-protoform"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/inhouse-work/protos-protoform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inhouse-work%2Fprotos-protoform","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inhouse-work%2Fprotos-protoform/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inhouse-work%2Fprotos-protoform/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inhouse-work%2Fprotos-protoform/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inhouse-work","download_url":"https://codeload.github.com/inhouse-work/protos-protoform/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inhouse-work%2Fprotos-protoform/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30008455,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T15:15:59.058Z","status":"ssl_error","status_checked_at":"2026-03-02T15:15:58.758Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["phlex","rails","ruby"],"created_at":"2024-11-09T01:18:03.396Z","updated_at":"2026-03-02T15:41:23.346Z","avatar_url":"https://github.com/inhouse-work.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Protoform\n\nThis is a more opinionated fork of [Superform](https://github.com/rubymonolith/superform).\nUses [protos](https://github.com/inhouse-work/protos) as base components.\n\nOriginally this library was created to experiment with Superform and has become\nits own distinct flavor.\n\n## Usage\n\n```\ngem \"protos-protoform\", require: \"protoform\"\n```\n\nOnce the gem is installed you can run the generators:\n\n```\nbin/rails g protoform:install\n```\n\nThis will:\n\n- Add `phlex-rails` to your gemfile if it does not exist\n- Add `layouts`, `components` and other folders to be autoloaded from `app/views`\n- Add an `ApplicationForm` as the base form class to your `app/views`\n\nThis gem follows the same conventions as Superform with some key differences:\n\n- All components inherit from `Protos::Component`\n\n## Forms\n\nIn classic Rails you use a form builder object. Protoform is a type of form\nbuilder built specifically for Phlex based components. Otherwise, it completely\nfollows the behavior of Rails forms, just with a nicer API.\n\n- Every form takes a model that at least includes `ActiveModel::Naming` and\n  `ActiveModel::Conversion`\n- URLs for the form are defaulted to the current controller's `create` or\n  `update` methods but can be overwritten\n- Naming for IDs and names follows Rails conventions\n- Collections and namespaces have a different API but work the same way\n\n## Defining forms\n\nAt minimum your form needs to inherit from `Protoform::Rails::Form` and have a\n`Field` constant inside that defines the field's API.\n\n```ruby\nclass ApplicationForm \u003c Protoform::Rails::Form\n  class Field \u003c Protoform::Field\n  end\nend\n```\n\nThen you can use these to subclass your own forms:\n\n```ruby\nmodule Posts\n  class Form \u003c ApplicationForm\n    def view_template\n      render field(:title).label(\"Title\")\n      render field(:title).input(required: true)\n\n      render field(:body).label(\"Body\")\n      render field(:body).textarea(rows: 10)\n\n      button \"Submit\"\n    end\n  end\nend\n\nrender Posts::Form.new(@post)\n```\n\nAssuming our model is a `Post`, then the params would be returned under that\nkey.\n\n```\n{\n  post: {\n    title: \"The title\",\n    body: \"The body\"\n  }\n}\n```\n\nWe can change that by overriding the `#key` within our form:\n\n```ruby\nmodule Posts\nclass Form \u003c ApplicationForm\n  def key = \"article\"\n\n  # ...\nend\n```\n\nNow you would get:\n\n```\n{\n  article: {\n    title: \"The title\",\n    body: \"The body\"\n  }\n}\n```\n\nIf we wanted to customize the `#input` method or anything else we could override\nit inside the `Field` constant:\n\n```ruby\nclass ApplicationForm \u003c Protoform::Rails::Form\n  class Field \u003c Protoform::Field\n    # We can override the default fields with our own\n    def input(...)\n      Forms::Input.new(self, ...)\n    end\n  end\n\n  # Here we make a simple helper to make our syntax shorter. Given a field it\n  # will also render its label.\n  def labeled(component)\n    div class: \"form-row\" do\n      render component.field.label\n      render component\n    end\n  end\n\n  def submit(text)\n    button(type: :submit) { text }\n  end\nend\n```\n\nWith the helper we created can now write our form more succinctly:\n\n```ruby\nmodule Posts\n  class Form \u003c ApplicationForm\n    def view_template\n      labeled field(:title).input(required: true)\n      labeled field(:body).textarea(rows: 10)\n\n      submit\n    end\n  end\nend\n```\n\nIn this way you can design your own form DSL that fits your application's needs.\n\n## Namespacing and collections\n\nThe way collections work is not quite the same as Rails:\n\n```ruby\nclass AccountForm \u003c Superform::Rails::Form\n  def view_template\n    # Account#owner returns a single object\n    namespace :owner do |owner|\n      # Renders input with the name `account[owner][name]`\n      owner.field(:name).text\n      # Renders input with the name `account[owner][email]`\n      owner.field(:email).email\n    end\n\n    # Account#members returns a collection of objects\n    collection(:members).each do |member|\n      # Renders input with the name `account[members][0][name]`,\n      # `account[members][1][name]`, ...\n      member.field(:name).input\n      # Renders input with the name `account[members][0][email]`,\n      # `account[members][1][email]`, ...\n      member.field(:email).input(type: :email)\n\n      # Member#permissions returns an array of values like\n      # [\"read\", \"write\", \"delete\"].\n      member.field(:permissions).collection do |permission|\n        # Renders input with the name `account[members][0][permissions][]`,\n        # `account[members][1][permissions][]`, ...\n        render permission.label do\n          plain permisson.value.humanize\n          render permission.checkbox\n        end\n      end\n    end\n  end\nend\n```\n\n`#collection` methods require the use of the each method to enumerate over each\nitem in the collection.\n\nThere's three different types of namespaces and collections to consider:\n\n1. **Namespace** - `namespace(:field_name)` is used to map form fields to a single\n   object that's a child of another object. In ActiveRecord, this could be a\n   `has_one` or `belongs_to` relationship.\n2. **Collection** - `collection(:field_name).each` is used to map a collection of\n   objects to a form. In this case, the members of the account. In ActiveRecord,\n   this could be a `has_many` relationship.\n3. **Field Collection** - `field(:field_name).collection.each` is used when the\n   value of a field is enumerable, like an array of values. In ActiveRecord,\n   this could be an attribute that's an Array type.\n\n## Architecture\n\nForms are a nested hierarchy of `Protoform::Namespace` and `Protoform::Field` objects.\n\n```ruby\nform = Protoform::Namespace.new(:post, parent: nil, object: nil) do |f|\n  f.field(:title, value: \"Hello World\")\n\n  f.collection(:comments) do |comments|\n    comments.namespace(:comment, object: nil) do |comment|\n      comment.field(:body, value: \"Great post!\")\n    end\n  end\nend\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run\n`rake spec` to run the tests. You can also run `bin/console` for an interactive\nprompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To\nrelease a new version, update the version number in `version.rb`, and then run\n`bundle exec rake release`, which will create a git tag for the version, push\ngit commits and the created tag, and push the `.gem` file to\n[rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at\nhttps://github.com/rubymonolith/superform. This project is intended to be\na safe, welcoming space for collaboration, and contributors are expected to\nadhere to the [code of\nconduct](https://github.com/rubymonolith/superform/blob/main/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the\n[MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Superform project's codebases, issue trackers, chat\nrooms and mailing lists is expected to follow the\n[code of conduct](https://github.com/rubymonolith/superform/blob/main/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finhouse-work%2Fprotos-protoform","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finhouse-work%2Fprotos-protoform","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finhouse-work%2Fprotos-protoform/lists"}