{"id":13400227,"url":"https://github.com/trailblazer/cells","last_synced_at":"2025-05-06T02:52:08.768Z","repository":{"id":420569,"uuid":"40407","full_name":"trailblazer/cells","owner":"trailblazer","description":"View components for Ruby and Rails.","archived":false,"fork":false,"pushed_at":"2024-12-02T09:00:38.000Z","size":1743,"stargazers_count":3074,"open_issues_count":38,"forks_count":234,"subscribers_count":59,"default_branch":"master","last_synced_at":"2025-04-25T14:50:30.862Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://trailblazer.to/2.1/docs/cells.html","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/trailblazer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":null,"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}},"created_at":"2008-08-06T10:31:06.000Z","updated_at":"2025-04-24T23:52:00.000Z","dependencies_parsed_at":"2023-07-06T07:27:16.927Z","dependency_job_id":"afd95742-53ef-4000-a08d-82141291e313","html_url":"https://github.com/trailblazer/cells","commit_stats":{"total_commits":1098,"total_committers":74,"mean_commits":"14.837837837837839","dds":0.1912568306010929,"last_synced_commit":"26ddc436e828cb2527be7cd2d0d1bb5e8b10b868"},"previous_names":["apotonick/cells"],"tags_count":90,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Fcells","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Fcells/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Fcells/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/trailblazer%2Fcells/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/trailblazer","download_url":"https://codeload.github.com/trailblazer/cells/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252446123,"owners_count":21749165,"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":[],"created_at":"2024-07-30T19:00:49.733Z","updated_at":"2025-05-06T02:52:08.723Z","avatar_url":"https://github.com/trailblazer.png","language":"Ruby","readme":"# Cells\n\n*View Components for Ruby and Rails.*\n\n[![Zulip Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://trailblazer.zulipchat.com/login/)\n[![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](https://trailblazer.to/2.1/#callout-section)\n![Build\nStatus](https://github.com/trailblazer/cells/actions/workflows/ci.yml/badge.svg\n)\n[![Gem Version](https://badge.fury.io/rb/cells.svg)](http://badge.fury.io/rb/cells)\n\n## Overview\n\nCells allow you to encapsulate parts of your UI into components into _view models_. View models, or cells, are simple ruby classes that can render templates.\n\nNevertheless, a cell gives you more than just a template renderer. They allow proper OOP, polymorphic builders, [nesting](#nested-cells), view inheritance, using Rails helpers, [asset packaging](https://trailblazer.to/2.1/docs/cells.html#cells-rails-asset-pipeline) to bundle JS, CSS or images, simple distribution via gems or Rails engines, encapsulated testing, [caching](#caching), and [integrate with Trailblazer](https://github.com/trailblazer/trailblazer-cells).\n\n## Full Documentation\n\nCells is part of the Trailblazer framework. [Full documentation](https://trailblazer.to/2.1/docs/cells/) is available on the project site.\n\nCells is completely decoupled from Rails. However, Rails-specific functionality is to be found [here](https://trailblazer.to/2.1/docs/cells/#cells-4-rails).\n\n## Rendering Cells\n\nYou can render cells anywhere and as many as you want, in views, controllers, composites, mailers, etc.\n\nRendering a cell in Rails ironically happens via a helper.\n\n```ruby\n\u003c%= cell(:comment, @comment) %\u003e\n```\n\nThis boils down to the following invocation, that can be used to render cells in *any other Ruby* environment.\n\n```ruby\nCommentCell.(@comment).()\n```\n\nYou can also pass the cell class in explicitly:\n\n```ruby\n\u003c%= cell(CommentCell, @comment) %\u003e\n```\n\nIn Rails you have the same helper API for views and controllers.\n\n```ruby\nclass DashboardController \u003c ApplicationController\n  def dashboard\n    @comments = cell(:comment, collection: Comment.recent)\n    @traffic  = cell(:report, TrafficReport.find(1)).()\n  end\n```\n\nUsually, you'd pass in one or more objects you want the cell to present. That can be an ActiveRecord model, a ROM instance or any kind of PORO you fancy.\n\n## Cell Class\n\nA cell is a light-weight class with one or multiple methods that render views.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  property :body\n  property :author\n\n  def show\n    render\n  end\n\nprivate\n  def author_link\n    link_to \"#{author.email}\", author\n  end\nend\n```\n\nHere, `show` is the only public method. By calling `render` it will invoke rendering for the `show` view.\n\n\n## Logicless Views\n\nViews come packaged with the cell and can be ERB, Haml, or Slim.\n\n```erb\n\u003ch3\u003eNew Comment\u003c/h3\u003e\n  \u003c%= body %\u003e\n\nBy \u003c%= author_link %\u003e\n```\n\nThe concept of \"helpers\" that get strangely copied from modules to the view does not exist in Cells anymore.\n\nMethods called in the view are directly called _on the cell instance_. You're free to use loops and deciders in views, even instance variables are allowed, but Cells tries to push you gently towards method invocations to access data in the view.\n\n## File Structure\n\nIn Rails, cells are placed in `app/cells` or `app/concepts/`. Every cell has their own directory where it keeps views, assets and code.\n\n```\napp\n├── cells\n│   ├── comment_cell.rb\n│   ├── comment\n│   │   ├── show.haml\n│   │   ├── list.haml\n```\n\nThe discussed `show` view would reside in `app/cells/comment/show.haml`. However, you can set [any set of view paths](#view-paths) you want.\n\n\n## Invocation Styles\n\nIn order to make a cell render, you have to call the rendering methods. While you could call the method directly, the preferred way is the _call style_.\n\n```ruby\ncell(:comment, @song).()       # calls CommentCell#show.\ncell(:comment, @song).(:index) # calls CommentCell#index.\n```\n\nThe call style respects caching.\n\nKeep in mind that `cell(..)` really gives you the cell object. In case you want to reuse the cell, need setup logic, etc. that's completely up to you.\n\n## Parameters\n\nYou can pass in as many parameters as you need. Per convention, this is a hash.\n\n```ruby\ncell(:comment, @song, volume: 99, genre: \"Jazz Fusion\")\n```\n\nOptions can be accessed via the `@options` instance variable.\n\nNaturally, you may also pass arbitrary options into the call itself. Those will be simple method arguments.\n\n```ruby\ncell(:comment, @song).(:show, volume: 99)\n```\n\nThen, the `show` method signature changes to `def show(options)`.\n\n\n## Testing\n\nA huge benefit from \"all this encapsulation\" is that you can easily write tests for your components. The API does not change and everything is exactly as it would be in production.\n\n```ruby\nhtml = CommentCell.(@comment).()\nCapybara.string(html).must_have_css \"h3\"\n```\n\nIt is completely up to you how you test, whether it's RSpec, MiniTest or whatever. All the cell does is return HTML.\n\n[In Rails, there's support](https://trailblazer.to/2.1/docs/cells/#cells-4-overview-testing) for TestUnit, MiniTest and RSpec available, along with Capybara integration.\n\n## Properties\n\nThe cell's model is available via the `model` reader. You can have automatic readers to the model's fields by using `::property`.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  property :author # delegates to model.author\n\n  def author_link\n    link_to author.name, author\n  end\nend\n```\n\n## HTML Escaping\n\nCells per default does no HTML escaping, anywhere. Include `Escaped` to make property readers return escaped strings.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  include Escaped\n\n  property :title\nend\n\nsong.title                 #=\u003e \"\u003cscript\u003eDangerous\u003c/script\u003e\"\nComment::Cell.(song).title #=\u003e \u0026lt;script\u0026gt;Dangerous\u0026lt;/script\u0026gt;\n```\n\nProperties and escaping are [documented here](https://trailblazer.to/2.1/docs/cells/#cells-4-api-html-escaping).\n\n## Installation\n\nCells runs with any framework.\n\n```ruby\ngem \"cells\"\n```\n\nFor Rails, please use the [cells-rails](https://github.com/trailblazer/cells-rails) gem. It supports Rails \u003e= 4.0.\n\n```ruby\ngem \"cells-rails\"\n```\n\nLower versions of Rails will still run with Cells, but you will get in trouble with the helpers. (Note: we use Cells in production with Rails 3.2 and Haml and it works great.)\n\nVarious template engines are supported but need to be added to your Gemfile.\n\n* [cells-erb](https://github.com/trailblazer/cells-erb)\n* [cells-hamlit](https://github.com/trailblazer/cells-hamlit) We strongly recommend using [Hamlit](https://github.com/k0kubun/hamlit) as a Haml replacement.\n* [cells-haml](https://github.com/trailblazer/cells-haml) Make sure to bundle Haml 4.1: `gem \"haml\", github: \"haml/haml\", ref: \"7c7c169\"`. Use `cells-hamlit` instead.\n* [cells-slim](https://github.com/trailblazer/cells-slim)\n\n```ruby\ngem \"cells-erb\"\n```\n\nIn Rails, this is all you need to do. In other environments, you need to include the respective module into your cells.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  include ::Cell::Erb # or Cell::Hamlit, or Cell::Haml, or Cell::Slim\nend\n```\n\n## Namespaces\n\nCells can be namespaced as well.\n\n```ruby\nmodule Admin\n  class CommentCell \u003c Cell::ViewModel\n```\n\nInvocation in Rails would happen as follows.\n\n```ruby\ncell(\"admin/comment\", @comment).()\n```\n\nViews will be searched in `app/cells/admin/comment` per default.\n\n\n## Rails Helper API\n\nEven in a non-Rails environment, Cells provides the Rails view API and allows using all Rails helpers.\n\nYou have to include all helper modules into your cell class. You can then use `link_to`, `simple_form_for` or whatever you feel like.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  include ActionView::Helpers::UrlHelper\n  include ActionView::Helpers::CaptureHelper\n\n  def author_link\n    content_tag :div, link_to(author.name, author)\n  end\n```\n\nAs always, you can use helpers in cells and in views.\n\nYou might run into problems with wrong escaping or missing URL helpers. This is not Cells' fault but Rails suboptimal way of implementing and interfacing their helpers. Please open the actionview gem helper code and try figuring out the problem yourself before bombarding us with issues because helper `xyz` doesn't work.\n\n\n## View Paths\n\nIn Rails, the view path is automatically set to `app/cells/` or `app/concepts/`. You can append or set view paths by using `::view_paths`. Of course, this works in any Ruby environment.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  self.view_paths = \"lib/views\"\nend\n```\n\n## Asset Packaging\n\nCells can easily ship with their own JavaScript, CSS and more and be part of Rails' asset pipeline. Bundling assets into a cell allows you to implement super encapsulated widgets that are stand-alone. Asset pipeline is [documented here](https://trailblazer.to/2.1/docs/cells/#cells-4-rails-asset-pipeline).\n\n## Render API\n\nUnlike Rails, the `#render` method only provides a handful of options you gotta learn.\n\n```ruby\ndef show\n  render\nend\n```\n\nWithout options, this will render the state name, e.g. `show.erb`.\n\nYou can provide a view name manually. The following calls are identical.\n\n```ruby\nrender :index\nrender view: :index\n```\n\nIf you need locals, pass them to `#render`.\n\n```ruby\nrender locals: {style: \"border: solid;\"}\n```\n\n## Layouts\n\nEvery view can be wrapped by a layout. Either pass it when rendering.\n\n```ruby\nrender layout: :default\n```\n\nOr configure it on the class-level.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  layout :default\n```\n\nThe layout is treated as a view and will be searched in the same directories.\n\n\n## Nested Cells\n\nCells love to render. You can render as many views as you need in a cell state or view.\n\n```ruby\n\u003c%= render :index %\u003e\n```\n\nThe `#render` method really just returns the rendered template string, allowing you all kind of modification.\n\n```ruby\ndef show\n  render + render(:additional)\nend\n```\n\nYou can even render other cells _within_ a cell using the exact same API.\n\n```ruby\ndef about\n  cell(:profile, model.author).()\nend\n```\n\nThis works both in cell views and on the instance, in states.\n\n\n## View Inheritance\n\nYou can not only inherit code across cell classes, but also views. This is extremely helpful if you want to override parts of your UI, only. It's [documented here](https://trailblazer.to/2.1/docs/cells/#cells-4-api-view-inheritance).\n\n## Collections\n\nIn order to render collections, Cells comes with a shortcut.\n\n```ruby\ncomments = Comment.all #=\u003e three comments.\ncell(:comment, collection: comments).()\n```\n\nThis will invoke `cell(:comment, comment).()` three times and concatenate the rendered output automatically.\n\nLearn more [about collections here](https://trailblazer.to/2.1/docs/cells/#cells-4-api-collection).\n\n\n## Builder\n\nBuilders allow instantiating different cell classes for different models and options. They introduce polymorphism into cells.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  include ::Cell::Builder\n\n  builds do |model, options|\n    case model\n    when Post; PostCell\n    when Comment; CommentCell\n    end\n  end\n```\n\nThe `#cell` helper takes care of instantiating the right cell class for you.\n\n```ruby\ncell(:comment, Post.find(1)) #=\u003e creates a PostCell.\n```\n\nLearn more [about builders here](https://trailblazer.to/2.1/docs/cells/#cells-4-api-builder).\n\n## Caching\n\nFor every cell class you can define caching per state. Without any configuration the cell will run and render the state once. In following invocations, the cached fragment is returned.\n\n```ruby\nclass CommentCell \u003c Cell::ViewModel\n  cache :show\n  # ..\nend\n```\n\nThe `::cache` method will forward options to the caching engine.\n\n```ruby\ncache :show, expires_in: 10.minutes\n```\n\nYou can also compute your own cache key, use dynamic keys, cache tags, and conditionals using `:if`. Caching is documented [here](https://trailblazer.to/2.1/docs/cells/#cells-4-api-caching) and in chapter 8 of the [Trailblazer book](http://leanpub.com/trailblazer).\n\n\n## The Book\n\nCells is part of the [Trailblazer project](https://github.com/apotonick/trailblazer). Please [buy my book](https://leanpub.com/trailblazer) to support the development and to learn all the cool stuff about Cells. The book discusses many use cases of Cells.\n\n[![trb](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)](https://leanpub.com/trailblazer)\n\n* Basic view models, replacing helpers, and how to structure your view into cell components (chapter 2 and 4).\n* Advanced Cells API (chapter 4 and 6).\n* Testing Cells (chapter 4 and 6).\n* Cells Pagination with AJAX (chapter 6).\n* View Caching and Expiring (chapter 8).\n\nThe book picks up where the README leaves off. Go grab a copy and support us - it talks about object- and view design and covers all aspects of the API.\n\n## This is not Cells 3.x!\n\nTemporary note: This is the README and API for Cells 4. Many things have improved. If you want to upgrade, [follow this guide](https://github.com/apotonick/cells/wiki/From-Cells-3-to-Cells-4---Upgrading-Guide). When in trouble, join the [Zulip channel](https://trailblazer.zulipchat.com/login/).\n\n## LICENSE\n\nCopyright (c) 2007-2024, Nick Sutterer\n\nCopyright (c) 2007-2008, Solide ICT by Peter Bex and Bob Leers\n\nReleased under the MIT License.\n","funding_links":[],"categories":["Ruby","View components","Abstraction"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrailblazer%2Fcells","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrailblazer%2Fcells","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrailblazer%2Fcells/lists"}