{"id":17177355,"url":"https://github.com/floere/view_models","last_synced_at":"2025-07-03T17:36:07.438Z","repository":{"id":1068889,"uuid":"240059","full_name":"floere/view_models","owner":"floere","description":"Rails 2.2+ MVC with an added and very helpful presentation layer","archived":false,"fork":false,"pushed_at":"2023-01-19T00:52:06.000Z","size":720,"stargazers_count":34,"open_issues_count":10,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-13T16:54:51.080Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://floere.github.com/view_models/","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/floere.png","metadata":{"files":{"readme":"README.textile","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2009-06-30T19:59:21.000Z","updated_at":"2022-05-31T09:28:48.000Z","dependencies_parsed_at":"2023-02-10T19:15:15.433Z","dependency_job_id":null,"html_url":"https://github.com/floere/view_models","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/floere/view_models","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floere%2Fview_models","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floere%2Fview_models/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floere%2Fview_models/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floere%2Fview_models/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/floere","download_url":"https://codeload.github.com/floere/view_models/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/floere%2Fview_models/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":263370171,"owners_count":23456425,"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-10-15T00:03:48.637Z","updated_at":"2025-07-03T17:36:07.170Z","avatar_url":"https://github.com/floere.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"h1. View Models !https://secure.travis-ci.org/beatrichartz/view_models.png(Build Status)!:http://travis-ci.org/beatrichartz/view_models \"!https://codeclimate.com/badge.png!\":https://codeclimate.com/github/beatrichartz/view_models\n\nA representer solution for Rails 3. For older versions of Rails please use versions up to 3. You may find view models useful (or not). Fact: They will keep code out of your views. Because view models do not like code in your view.\n\nh2. Installing View Models\n\nAdd this to your gemfile\n\n\u003cpre\u003e\u003ccode\u003egem \"view_models\",   \"\u003e= 3.0.0\"\n\u003c/code\u003e\u003c/pre\u003e\n\nAnd this to your application.rb: (It adds the app folder to the autoload paths, which is necessary for the view models to load)\n\u003cpre\u003e\u003ccode\u003econfig.autoload_paths += %W(#{config.root}/app)\n\u003c/code\u003e\u003c/pre\u003e\n\nCreate a directory \"view_models\" in your app folder, and you're good to go.\n\u003cpre\u003e\u003ccode\u003emkdir ./app/view_models\n\u003c/code\u003e\u003c/pre\u003e\n\nh2. TODOs\n\nWhat you can look forward to in future versions of this gem:\n- Bootstrapping for easier installation\n- JSON generation out of view models\n\nh2. Basic Usage\n\nLet's say you have a model User (with a first and a last name and an address in the database):\n\n\u003cpre\u003e\u003ccode\u003eclass User \u003c ActiveRecord::Base\n  \n  has_many :posts\n  \nend\n\u003c/code\u003e\u003c/pre\u003e\n\nWrite a corresponding view model user.rb in your view_models folder\n\n\u003cpre\u003e\u003ccode\u003eclass ViewModels::User \u003c ViewModels::Base\n  \n  # model readers make model methods accessible on the view model object\n  #\n  model_reader :first_name, :last_name, :street, :street_number, :zip_code, :city\n  \n  # Write helper methods which benefit from model readers\n  #\n  def name\n    [first_name, last_name].join ' '\n  end\n  \n  # Access the model by using «model»\n  #\n  def creation_time\n    time_ago_in_words model.created_at\n  end\n  \nend\n\u003c/code\u003e\u003c/pre\u003e\n\nIn your view, call the view_model for a model by using «view_model_for»\n\n\u003cpre\u003e\u003ccode\u003e- view_model = view_model_for(@user)\n%h2= view_model.name\n%h2= view_model.creation_time\n\u003c/code\u003e\u003c/pre\u003e\n\nAll beautiful, you may think, but...\n\nh2. Why View Models? (aka «The Problem»)\n\nEver felt like putting something like this in your views (example in haml)?\n\n\u003cpre\u003e\u003ccode\u003e#user\n  .name= [@user.first_name, @user.last_name].join ' '\n  .address= [@user.street, @user.street_number, @user.zip_code, @user.city].join ' '\n\u003c/code\u003e\u003c/pre\u003e\n\nWell, that may feel good if you're in a hurry. Soon there comes the time when you use this code in more than one place. Time to build a helper:\n\n\u003cpre\u003e\u003ccode\u003emodule UserHelper\n  def user_name user\n    [user.first_name, user.last_name].join ' '\n  end\n\n  def user_address user\n    [user.street, user.street_number, user.zip_code, user.city].join ' '\n  end\nend\n\n#user\n  .name= user_name @user\n  .address= user_address @user\n\u003c/code\u003e\u003c/pre\u003e\n\nIt may be a lot cleaner, but something just does not feel right. Right, but what? Well, for instance, you have to namespace all your methods with «user» so your methods don't get messy. Second, you have to include the helper either in every context you use it, or even in the whole app. If only you had a polymorphic object to represent your models in the views...\n\nh3. Meet View Models (aka «The Solution»)\n\nView models are pretty, because they represent your models in your views. They look good in every context, and therefore make you look good, too. Take our example from before:\n\n\u003cpre\u003e\u003ccode\u003eclass ViewModels::User \u003c ViewModels::Base\n\n  model_reader :first_name, :last_name, :street, :street_number, :zip_code, :city\n  \n  def name\n    [first_name, last_name].join ' '\n  end\n\n  def address\n    [street, street_number, zip_code, city].join ' '\n  end\n  \nend\n\u003c/code\u003e\u003c/pre\u003e\n\nAnd your view will look like this:\n\n\u003cpre\u003e\u003ccode\u003e- user_view_model = view_model_for(@user)\n#user\n  .name= user_view_model.name\n  .address= user_view_model.address\n\u003c/code\u003e\u003c/pre\u003e\n\nNo helper inclusion needed, no further noise involved. In fact, you can make it even prettier: What if you needed that partial with the user name and address somewhere else in your code?\n\nh3. Meet View Models render_as method\n\nView models feature template rendering: You can render any partial in your views, following your views directory tree structure: A view_model variable will automatically be included as a local variable in your partial if you render it with view models:\n\nLet's say you have the user model from before, and the following partial written for the view model to render named \"info\":\n\n\u003cpre\u003e\u003ccode\u003e#user\n  .name= view_model.name\n  .address= view_model.address\n\u003c/code\u003e\u003c/pre\u003e\n\nNow everything you have to do to render that partial is:\n\n\u003cpre\u003e\u003ccode\u003e= view_model_for(@user).render_as :info\n\u003c/code\u003e\u003c/pre\u003e\n\nView models feature hierarchical template rendering. That means, if you have a parent view model which has an identical partial already installed, you do not need to copy identical code just to render the same template. The view model will lookup its inheritance chain and Rails template paths to find the suitable partial. Which brings us to another great feature:\n\nh3. View Models can haz inheritance\n\nEver tried to do inheritance in Rails helpers? Right. View Models, on the other side, love inheritance. They do not need arguments to display a formatted creation time for all your models. Consider the following:\n\nh4. The View Model Way\n\nYou can generate a view model tree structure with a view model for your whole app for your other view models to inherit from. The polymorphic class matching of the view models ignores the missing class YourApp candidly.\n\n\u003cpre\u003e\u003ccode\u003eclass ViewModels::YourApp \u003c ViewModels::Base\n\n  def creation_time\n    time_ago_in_words model.created_at\n  end\n\nend\n\nclass ViewModels::User \u003c ViewModels::YourApp\nend\n\u003c/code\u003e\u003c/pre\u003e\n\nIn your view:\n\n\u003cpre\u003e\u003ccode\u003e= view_model_for(@user).creation_time\n\u003c/code\u003e\u003c/pre\u003e\n\nh4. The Helper Way\n\nImagine how to do this in a helper ? Well, better go ahead and use time_ago_in_words everywhere.\n\nh2. Where is it used?\n\nThere are known some places in the big place called internet to use this gem:\n\n\"rightclearing.com [Music Licensing Provider]\": http://rightclearing.com\n\"restorm.com [Music Platform]\": http://restorm.com\n\nh2. Testing View Models\n\nTesting View Models is easy. You can use the view models initializer instead of the view_model_for mapping (example in rspec with factory girl):\n\n\u003cpre\u003e\u003ccode\u003edescribe \"ViewModels::User\" do\n  let(:user) { build_stubbed(:user) }\n  subject { ViewModels::User.new(user, @controller) } # @controller may be nil or the controller\n  \n  describe \"model_readers\"\n    .. there you go\n  end\nend\n\u003c/code\u003e\u003c/pre\u003e\n\nh2. Further reads\n\n\"Mailing List\":http://groups.google.com/group/view_models/topics\n\"Rubygems\":http://rubygems.org/gems/view_models\n\"Bug Tracker\":http://github.com/floere/view_models/issues\n\"Source [Github]\":http://github.com/floere/view_models","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloere%2Fview_models","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffloere%2Fview_models","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffloere%2Fview_models/lists"}