Coder Social home page Coder Social logo

roar-rails's Introduction

roar-rails

Makes using Roar's representers in your Rails app fun.

Build Status Gem Version

roar-rails development will be discontinued in the future and we will encourage users to begin migrating to Trailblazer (and trailblazer-rails).


Roar is a framework for parsing and rendering REST documents. For a better overview about representers please check the roar repository.

Roar-rails gives you conventions and convenient access to a lot of Roar's functionality within your Rails app.

Features

  • Rendering with responders
  • Parsing incoming documents
  • URL helpers in representers
  • Better tests
  • Autoloading
  • Generators

This gem works with all Rails >= 3.x.

Prerequisites

Add it to your app's Gemfile.

gem "roar-rails"

Note: For Rails >= 4.2, you need to add the responders gem, too, if you use respond_with. This has to be before the roar-rails entry in the Gemfile.

gem "responders"
gem "roar-rails"

Generators

The generator will create the representer modules in app/representers for you.

Here's an example.

rails g representer Band id name

This will create the file app/representers/band_representer.rb with the following content,

  module BandRepresenter
    include Roar::JSON

    property :id
    property :name
  end

You can change the format (e.g. XML), and pass arbitrary options to customize the generated representer. For all available options, just run

rails g representer

Rendering with #respond_with

roar-rails provides a number of baked-in rendering methods.

Conventional Rendering

Easily render resources using representers with the built-in responder.

class SingersController < ApplicationController
  include Roar::Rails::ControllerAdditions
  respond_to :json

  def show
    singer = Singer.find_by_id(params[:id])
    respond_with singer
  end
end

The representer name will be infered from the passed model class (e.g. a Singer instance gets the SingerRepresenter). If the passed model is a collection it will be extended using a representer. The representer name will be computed from the controller name (e.g. a SingersController uses the SingersRepresenter).

Need to use a representer with a different name than your model? You may always pass it in using the :represent_with option:

respond_with singers, :represent_with => MusicianCollectionRepresenter
end

Represents Configuration

If you don't want to use conventions or pass representers you can configure them on the class level using ::represents. This will also call respond_to for you.

class SingersController < ApplicationController
  represents :json, Musician

This will use the MusicianRepresenter for models and MusiciansRepresenter for representing collections.

Note that ::represents also allows fine-tuning.

class SingersController < ApplicationController
  represents :json, :entity => MusicianRepresenter, :collection => MusicianCollectionRepresenter

You might pass strings as representer names to ::represents, they will be constantized at run-time when needed.

Rendering with #render

In place of #respond_with, you can also use #render to serialize objects using representers.

class SingersController < ApplicationController
  include Roar::Rails::ControllerAdditions
  include Roar::Rails::ControllerAdditions::Render

  def show
    singer = Singer.find_by_id(params[:id])
    render json: singer
  end
end

Old API Support

If you don't want to write a dedicated representer for a collection of items (highly recommended, thou) but rather use a representer for each item, use the :represent_items_with option.

class SingersController < ApplicationController

  def index
    singers = Musician.find(:all)
    respond_with singers, :represent_items_with => SingerRepresenter
  end
end

Parsing incoming documents

In #create and #update actions it is often necessary to parse the incoming representation and map it to a model instance. Use the #consume! method for this. The client must provide a Content-Type request header with proper MIME type to let #consume! know which representer to use.

class SingersController < ApplicationController
  respond_to :json

  def create
    singer = Singer.new
    consume!(singer)

    respond_with singer
  end
end

For instance, if content type is set to application/xml the consume! call will roughly do the following.

singer.
  extend(SingerRepresenter)
  from_xml(request.body)

So, #consume! helps you figuring out the representer module and reading the incoming document. Just like Rails, depending on the registered MIME type for Content-type it picks the deserialize method (e.g. from_json vs. from_xml)

It is important to provide a known content type in the request. If it is missing or not supported by the responder #consume! will raise an exception Roar::Rails::ControllerAdditions::UnsupportedMediaType. Unless you rescue the exception the action will stop and respond with HTTP status 406 Unsupported Media Type.

Note that #consume! respects settings from #represents. It uses the same mechanics known from #respond_with to choose a representer.

consume!(singer, :represent_with => MusicianRepresenter)

Using Decorators

If you prefer roar's decorator approach over extend, just go for it. roar-rails will figure out automatically which represent strategy to use. Be sure to use roar >= 0.11.17.

class SingerRepresenter < Roar::Decorator
  include Roar::JSON
  include Roar::Hypermedia

  property :name

  link :self do
    singer_url(represented)
  end
end

In decorators' link blocks you currently have to use represented to get the actual represented model (this is self in module representers).

Passing Options

Both rendering and consuming support passing user options to the representer.

With #respond_with, any additional options will be passed to to_json (or whatever format you're using).

respond_with @singer, :current_user => current_user

Same goes with #consume!, passing options to from_json.

consume! Singer.new, :current_user => current_user

Note: If you pass in options to a representer, you must process them youself. For rendering, use :getter in the representer.

property :username, getter: lambda { |args| args[:current_user].name }

That'll render the current_user's name as the username property.

More docs about passing and processing option can be found here.

URL Helpers

Any URL helpers from the Rails app are automatically available in representers.

module FruitRepresenter
  include Roar::JSON
  include Roar::Hypermedia

  link :self do
    fruit_url self
  end
end

To get the hyperlinks up and running, please make sure to set the right host name in your environment files (config/environments):

config.representer.default_url_options = {:host => "127.0.0.1:3000"}

Attention: If you are using representers from a gem your Rails URL helpers might not work in these modules. This is due to a loading order problem in Rails. As a workaround, don't require the representers in the gem but load them as late as possible, usually it works when you require in the controller. We are working on fixing that problem.

Representing Formats Exclusively

By default, roar-rails will extend/decorate any model passed to respond_with for any request format. When adding roar-rails to a legacy project, you might want to restrict roar-rails' representing and fall back to the old behavior for certain formats. This can be configured both globally and on a per action basis.

To restrict representing globally to a particular format you can set the config.representer.represented_formats in your environment's configuration to an array of formats. For example the following will only represent hal and json requests.

config.representer.represented_formats = [:hal, :json]

The global configuration (or lack thereof) can be overridden by supplying the :represented_formats array when calling respond_with. The following will only represent @resource for the hal format in the show action. For any other format, it will expose the resource using Rails' old behavior.

class MyController < ApplicationController
  def show
    ...
    respond_with @resource, :represented_formats => [:hal]
  end
end

You can entirely suppress roar-rails in respond_with by passing in an empty array.

class MyController < ApplicationController
  def show
    ...
    respond_with @resource, :represented_formats => []
  end
end

Testing

Autoloading

Put your representers in app/representers and they will be autoloaded by Rails. Also, frequently used modules as media representers and features don't need to be required manually.

JSON-API

In a JSON-API environment, only one representer is written for both singular and collection resources. However, you have to configure represents accordingly so it knows what representer to use for collections.

class SongsController < ApplicationController
  represents :json_api, entity: SongRepresenter, collection: SongRepresenter.for_collection

Rails 4.1+ and HAL/JSON-API

Note: this is a temporary work-around, we're trying to fix that in Rails/roar-rails itself [May 2014].

Rails 4.1 and up expects you to manually register a global HAL or JSON-API renderer, or respond_with will throw an ActionController::MissingRenderer exception.

One fix is to add this to config/initializers/mime_types.rb right below Mime::Type.register 'application/hal+json', :hal:

ActionController::Renderers.add :hal do |obj, options|
  self.content_type ||= Mime[:hal]
  obj
end

Similarly, for JSON-API, below Mime::Type.register 'application/vnd.api+json', :json_api add:

ActionController::Renderers.add :json_api do |obj, options|
  self.content_type ||= Mime[:json_api]
  obj
end

Contributors

  • Railslove and especially Michael Bumann [bumi] have heavily supported development of roar-rails ("resource :singers").

License

Roar-rails is released under the MIT License.

roar-rails's People

Contributors

apotonick avatar aquateen avatar audionerd avatar bengreenberg avatar c0va23 avatar cbeer avatar chadwtaylor avatar chrisupb avatar cowboyd avatar ekampp avatar fltiago avatar guiocavalcanti avatar jandudulski avatar jocko-wowza avatar joefiorini avatar joshco avatar jrmhaig avatar mfrister avatar mikekelly avatar mpeychich avatar myabc avatar paulccarey avatar pgaertig avatar thegcat avatar timoschilling avatar whittle avatar yogeshjain999 avatar zavan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

roar-rails's Issues

Capybara test failing with undefined method `collect' for nil:NilClass thrown by representable-1.8.5/lib/representable/bindings/hash_bindings.rb:40:in 'serialize'

I have persistent intermittent undefined method 'collect' for nil:NilClass failures in rSpec/Capybara request specs. Different tests fail, but it is usually the failure to find a button or the failure to reach a page with a button click which causes them. Their are some other weird failures as well, but this one is the only stack trace I have right now.

I can point you to the app if it would help. Thanks!

Failures:

  1) User Pages & Sessions: new registration with invalid email and password 
     Failure/Error: page.find(:xpath, '//input[@name="sign_up_new_registration"]').click
     NoMethodError:
       undefined method `collect' for nil:NilClass
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/representable-1.8.5/lib/representable/bindings/hash_bindings.rb:40:in `serialize'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/representable-1.8.5/lib/representable/bindings/hash_bindings.rb:22:in `write'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/representable-1.8.5/lib/representable/hash/collection.rb:23:in `create_representation_with'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/representable-1.8.5/lib/representable/hash.rb:39:in `to_hash'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/roar-0.12.7/lib/roar/representer/json.rb:26:in `to_hash'
     # /Users/reconstructions/.rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/tmpdir.rb:126:in `try_convert'
     # /Users/reconstructions/.rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/tmpdir.rb:126:in `create'
     # /Users/reconstructions/.rvm/rubies/ruby-2.1.0/lib/ruby/2.1.0/tempfile.rb:136:in `initialize'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/form.rb:8:in `new'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/form.rb:8:in `initialize'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/form.rb:42:in `new'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/form.rb:42:in `block in params'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/nokogiri-1.6.2.1/lib/nokogiri/xml/node_set.rb:237:in `block in each'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/nokogiri-1.6.2.1/lib/nokogiri/xml/node_set.rb:236:in `upto'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/nokogiri-1.6.2.1/lib/nokogiri/xml/node_set.rb:236:in `each'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/form.rb:27:in `map'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/form.rb:27:in `params'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/form.rb:74:in `submit'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/rack_test/node.rb:56:in `click'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/node/element.rb:118:in `block in click'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/node/base.rb:81:in `synchronize'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/capybara-2.2.1/lib/capybara/node/element.rb:118:in `click'
     # ./spec/requests/users_pages_spec.rb:140:in `block (4 levels) in <top (required)>'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:237:in `instance_eval'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:237:in `instance_eval'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:21:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:85:in `block in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:85:in `each'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:85:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:446:in `run_hook'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:345:in `run_before_each_hooks'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:294:in `run_before_each'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:113:in `block in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:179:in `call'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:179:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/extensions/instance_eval_with_args.rb:16:in `instance_exec'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/extensions/instance_eval_with_args.rb:16:in `instance_eval_with_args'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:247:in `instance_eval_with_args'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:106:in `block (2 levels) in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:179:in `call'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:179:in `run'
     # ./spec/spec_helper.rb:36:in `block (2 levels) in <top (required)>'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/extensions/instance_eval_with_args.rb:16:in `instance_exec'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/extensions/instance_eval_with_args.rb:16:in `instance_eval_with_args'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:247:in `instance_eval_with_args'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:106:in `block (2 levels) in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:108:in `call'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:108:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/hooks.rb:446:in `run_hook'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:340:in `run_around_each_hooks'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:256:in `with_around_each_hooks'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example.rb:111:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:390:in `block in run_examples'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:386:in `map'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:386:in `run_examples'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:371:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:372:in `block in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:372:in `map'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:372:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:372:in `block in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:372:in `map'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/example_group.rb:372:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/command_line.rb:28:in `block (2 levels) in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/command_line.rb:28:in `map'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/command_line.rb:28:in `block in run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/reporter.rb:58:in `report'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/command_line.rb:25:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/runner.rb:80:in `run'
     # /Users/reconstructions/.rvm/gems/ruby-2.1.0@sfrc/gems/rspec-core-2.14.8/lib/rspec/core/runner.rb:17:in `block in autorun'

Collection representers not rendering json correctly

I have a collection and entity representer for a simple post model. Neither the collection nor the entities are being represented. Any help would be appreciated. Here is my setup:

class PostsController < ApplicationController
  include Roar::Rails::ControllerAdditions
  respond_to :json

  represents :json, :entity => Representers::PostRepresenter, :collection =>  Representers::PostCollectionRepresenter

  def index
    respond_with Post.all
  end
end
require 'roar/representer/json'
require 'roar/representer/json/hal'
require 'roar/representer/feature/hypermedia'

module Representers
  module PostCollectionRepresenter
    include Roar::Representer::JSON
    include Roar::Representer::JSON::HAL
    include Roar::Representer::Feature::Hypermedia

    collection :posts, :class => Post, :extend => Representers::PostRepresenter
  end
end
require 'roar/representer/json'
require 'roar/representer/json/hal'
require 'roar/representer/feature/hypermedia'

module Representers
  module PostRepresenter
    include Roar::Representer::JSON
    include Roar::Representer::JSON::HAL
    include Roar::Representer::Feature::Hypermedia

    property :id
    property :title
    property :body
    property :user_id
    property :created_at
    property :updated_at
  end
end

Warning when using include Roar::Rails::ControllerAdditions

This is very minor, but with the latest version of roar-rails (which installs the latest version of hooks by default), we get the following warning:

WARNING: Hooks::InheritableAttribute is deprecated, use Uber::InheritableAttr instead.

when we include Roar::Rails::ControllerAdditions.

Add support for Rails 4.1

I created 2 new Rails apps with the standard settings, one with Rails v.4.0.4 and the other with Rails v.4.1.0. In both, I changed the Gemfile to use roar-rails instead of jbuilder and ran bundle. Then, I applied the changes detailed at https://gist.github.com/whittle/76df2f1799713977ea4d to both apps, ran bundle exec rails server and queried them with curl -H "Accept: application/hal+json" http://localhost:3000/site_faqs/1

The Rails v.4.0.4 app correctly responds with a JSON document. The Rails v.4.1.0 app throws the exception ActionController::MissingRenderer (No renderer defined for format: hal)

Consuming URLs for Relationships

When updating or creating a resource in RESTful hypermedia APIs I have seen them designed in such a way where you relate resources via URLs instead of id's. To make up an example and follow your Fruit Bowl analogy, if I wanted to create a fruit as a part of a bowl I would do something along the lines of:

POST "http://fruits"
{ 
  "title": "Apple"
  "bowl": "http://bowls/1"
}

Behind the scenes, this would deserialize the passed in bowl url (http://bowls/1) to the bowl object with id 1 and relate the new fruit to the existing bowl.

Would really appreciate some insight on how I would go about this in roar-rails. :)

autoloading problem

Hi -- thanks for making roar, it's really valuable for us.

We recently starting experiencing a problem under JRuby. A particular test sometimes fails (depending on the randomization seed) like so under 0.1.6:

     Failure/Error: Unable to find matching line from backtrace
     NameError:
       uninitialized constant Bar::FoosController::BarRepresenter
     Shared Example Group: "bazing a thing" called from ./spec/features/baz_thing_spec.rb:36
     # ./app/controllers/bar/foos_controller.rb:10:in `show'

and like so under 0.1.5:

    Failure/Error: Unable to find matching line from backtrace
    RuntimeError:
      Circular dependency detected while autoloading constant BarRepresenter
    Shared Example Group: "bazing a thing" called from ./spec/features/baz_thing_spec.rb:36
    # ./app/controllers/bar/foos_controller.rb:10:in `show'

I looked through all the roar-rails source and I'm embarrassed to say I couldn't figure out where or how "app/representers/*" gets required or autoloaded, so I didn't make any headway figuring out what the problem could be.

/cc @dadadadave

Sending JSON data in rspec

I have this code in my spec:

before { post :update, { profile: { current_city: "Washington, DC" } }, format: :json }

Controller:

  def update
    @profile = consume!(Profile.find(params[:id]))
    @profile.save
    respond_with @profile, location: profile_url(@profile)
  end

When I run this, I get this error:

Failure/Error: before { post :update, profile: { current_city: { value: "Washington, DC" } }.to_json, format: :json }
  JSON::ParserError:
    757: unexpected token at 'profile=%7B%22current_city%22%3A%7B%22value%22%3A%22Washington%2C+DC%22%7D%7D'

It's interesting that request.body.read doesn't have the JSON string.

representation_wrap for collections versus members?

Hi. I've been having some trouble figuring out how to setup a smarter wrapper for my representers and wanted to see if this was a current feature or I was just going about it the wrong way. As this is an issue with a specific rails convention in using similar architecture for members and collections, I am filing this under the roar-rails repo specifically.

I essentially want a standardized data node for all responses. This is easy enough for collections with collection :properties, :as => :data but I require wrapping of individual member items as well (#index versus #show actions).

To demonstrate the issue I have a PropertiesRepresenter and a PropertyRepresenter.

module PropertiesRepresenter
  include Roar::Representer::JSON

  collection :properties,
    :extend => PropertyRepresenter,
    :class => Property,
    :as => :data

  def properties
    collect
  end
end
module PropertyRepresenter
  include Roar::Representer::JSON

  self.representation_wrap = :data

  property :id,
  property :created_at

end

At present this causes double-wrapping of the members in a collection like so:

{
  "data": [
    {
      "data": {...}
    },
    {
      "data": {...}
    }
  ]
}

I think what would be ideal is a conditional representation_wrap. Something like self.representation_wrap = :data if :root => true for PropertyRepresenter.

Is there any preexisting functionality similar to this or maybe a tip for getting it done? If not I can work on a PR of some sort but was hoping it was currently supported.

Fail to evaluate ActiveRecord::Relation

I have an action such as:

  class FriendshipsController < Api::ApiController
    def index
      user = User.find(params[:user_id])
      friendships = friendships.page(params[:page])

      respond_with(:api, friendships)
    end
  end

As you can see, respond_with receives an ActiveRecord::Relation. The problem is roar-rails fails to infer the collection representer name because when we issue model.kind_of?(Array) here it return false.

The error raised is:

NameError:
       uninitialized constant ActiveRecord::RelationRepresenter
     # ./app/controllers/api/friendships_controller.rb:5:in `index'

errors when "Rendering from render"

the issue appears with the includes in the controller


class Api::V2::AppointmentsController < ApplicationController
  include ::Roar::Rails::ControllerAdditions
  include ::Roar::Rails::ControllerAdditions::Render

  # GET /api/v2/appointments/:id
  def show
    @appointment = Appointment.first
    if @appointment
      # render json: AppointmentRepresenter.new(@appointment).to_json, status: :ok
      render @appointment, status: :ok

in my rspecs, I consistently get:


Failure/Error: get "/api/v2/appointments/123"
     ActionController::RoutingError:
       undefined method `responder=' for Api::V2::AppointmentsController:Class
     # ./app/controllers/api/v2/appointments_controller.rb:2:in`include'

AND


 Failure/Error: get "/api/v2/appointments/#{@appointment.id}"
     AbstractController::ActionNotFound:
       The action 'show' could not be found for Api::V2::AppointmentsController

When remove those includes and use the alternative:


render json: AppointmentRepresenter.new(@appointment).to_json, status: :ok'

this works out as expected

Am I doing something wrong?

Ruby 2.1.2
Rails 4.1.5
Rspec 3.1.0

thanks

Rails 4.2 compatibility

Updating to rails 4.2.0.beta2 I got the following error message booting up the server:

/Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/roar-rails-0.1.6/lib/roar/rails/rails4_0_strategy.rb:2:in `<module:Rails>': cannot load such file -- action_controller/metal/responder (LoadError)
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/roar-rails-0.1.6/lib/roar/rails/rails4_0_strategy.rb:1:in `<top (required)>'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/roar-rails-0.1.6/lib/roar-rails.rb:34:in `require'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/roar-rails-0.1.6/lib/roar-rails.rb:34:in `<module:Rails>'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/roar-rails-0.1.6/lib/roar-rails.rb:21:in `<module:Roar>'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/roar-rails-0.1.6/lib/roar-rails.rb:20:in `<top (required)>'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/bundler-1.7.3/lib/bundler/runtime.rb:76:in `require'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/bundler-1.7.3/lib/bundler/runtime.rb:76:in `block (2 levels) in require'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/bundler-1.7.3/lib/bundler/runtime.rb:72:in `each'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/bundler-1.7.3/lib/bundler/runtime.rb:72:in `block in require'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/bundler-1.7.3/lib/bundler/runtime.rb:61:in `each'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/bundler-1.7.3/lib/bundler/runtime.rb:61:in `require'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/bundler-1.7.3/lib/bundler.rb:133:in `require'
        from /Users/leifg/projects/coruscant/config/application.rb:5:in `<top (required)>'
        from /Users/leifg/projects/coruscant/config/environment.rb:2:in `require'
        from /Users/leifg/projects/coruscant/config/environment.rb:2:in `<top (required)>'
        from /Users/leifg/projects/coruscant/spec/spec_helper.rb:5:in `require'
        from /Users/leifg/projects/coruscant/spec/spec_helper.rb:5:in `<top (required)>'
        from /Users/leifg/projects/coruscant/spec/controllers/api/v1/api_keys_controller_spec.rb:1:in `require'
        from /Users/leifg/projects/coruscant/spec/controllers/api/v1/api_keys_controller_spec.rb:1:in `<top (required)>'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/configuration.rb:1105:in `load'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/configuration.rb:1105:in `block in load_spec_files'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/configuration.rb:1105:in `each'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/configuration.rb:1105:in `load_spec_files'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/runner.rb:96:in `setup'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/runner.rb:84:in `run'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/runner.rb:69:in `run'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/lib/rspec/core/runner.rb:37:in `invoke'
        from /Users/leifg/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/rspec-core-3.1.5/exe/rspec:4:in `<top (required)>'
        from /Users/leifg/.rbenv/versions/2.1.3/bin/rspec:23:in `load'
        from /Users/leifg/.rbenv/versions/2.1.3/bin/rspec:23:in `<main>'

The solution is rather simple though. Add the current version of the responders gem above roar-rails in your Gemfile:

gem "responders", "~> 2.0.0"
gem "roar-rails"

Background: respond_with has been pulled into the separate gem responders. See here

I don't know if this is a good solution or something of the dependencies of roar-rails needs to be changed.

properties/collections with a defined 'getter' block ignore represent_with parameter

We started using Roar::Decorator's a while ago so I forget if this was an issue on modules too, but definitely the case on Decorator's.... I've been burned by this a few times now when I forget about it, it feels like a bug to me, but perhaps it's documented/known? Seems like it would be nice to use it when it's specified though.

To provide an example:

class V3::AnswerRepresenter < Roar::Decorator

property :id
property :response, represent_with: V3::ResponseRepresenter, getter: ->(opts) { responses.first }, embedded: true

end

Normally this would work fine without the getter option, but when the getter is present, it's response is not passed through the specified representer and instead has to_json called on it directly or something.

ControllerAdditions::Render#render doesn't pass options like :callback through usefully to super #render

This might be an expectation problem, but the following does not work as I thought it would. I want to use my representer to return an object as JSON, and following (old) conventions, if a callback is specified in my request, I assume I am to return a JSONP-suitable call of that function with my JSON-represented thing. However, I can't get the roar-rails rendering paths to do this. I have to resort to explicitly calling the representer and then using render rather than any of the respond_with fanciness that roar-rails patches.

class API::V1::PublicFeedsController < ApplicationController
  include Roar::Rails::ControllerAdditions
  # If this is included, #render is hijacked and won't go through normal ActionController processing
  #include Roar::Rails::ControllerAdditions::Render

  respond_to :json

  def show
    @thing = Thing.find(123)
    if params[:callback]
      # These do not work
      # option 1 -- respond_with @thing, represent_with: ::API::ThingRepresenter, callback: params['callback']
      # option 2 -- render json: @thing, represent_with: ::API::ThingRepresenter, callback: params['callback']
      #... but this does
      render json: @thing.extend(::API::ThingRepresenter), callback: params['callback']
    else
      # don't need callback so this works okay
      respond_with @thing, represent_with: ::API::ThingRepresenter
    end
end

I think the patched render needs to pass all the remaining options besides represent_with through to super, but my attempt to have it do so was not sufficient to combine the behaviors such that option 2 above was successful. If that's the right track, I can keep pursuing it.

Unable to user roar-rails gem with rails-api gem.

When my base controller inherits from ActionController::API, roar doesn't seem to kick in. There's no errors, and my representers are called, yet the representer's output isn't used in the resulting response (rather the output of the model's native to_json method seems to be being used). If I change my ApplicationController to inherit from ActionController::Base everything magically begins working. I dug into the code a bit on both sides but was unable to figure out what part of ActionController::Base roar is depending on.

Does anyone have any idea what the problem is and how I can use both these gems together in my app?

Thanks!

rendered models wrapped in body key

I am currently using the latest version of rails (4.2.0), responders (2.0.2) and roar-rails (1.0.0). My end to end test renders a json I find a little bit confusing. This is what is returned:

{
  "body": "{\"id\":33,\"alias\":\"yapi\",\"name\":\"new team\"}",
  "content_type": {
    "synonyms": [
      "text/x-json",
      "application/jsonrequest"
    ],
    "symbol": "json",
    "string": "application/json"
  }
}

But I expect only the content of "body". I used the "standard way" of rendering the Team model, described in roar and roar-rails.

With a little bit of debugging I found out, that somehow the display method of the responder does not render the model, but a wrapping object, that is defined in the Roar::Rails::Responder here I'm not quite sure if there is a workaround, but I'm pretty sure, that rendering the wrapper object is not what should happen.

License

Thanks for your work on this gem.

I was hoping you could clarify the license for this project.

Thanks!

Not compatible with Roar 1.0

Today I installed roar and roar-rails gems and I was going to generate a representer when I got this

/home/diego/.rvm/gems/ruby-2.1.2@sistema-mip/gems/roar-rails-0.1.6/lib/roar-rails.rb:10:in module:Representer': cannot load such file -- roar/representer/json (LoadError)`

Then I did some research and it turns out the last version (v0.12.x) before v1.0.0 release had another directory layout in which the faulty autoload call made sense.

Is there any workaround?

curie/template support for JSON-HAL?

JSON-HAL spec now has specification for "curies", and specifically templates for curies to support queries and supported manipulations and transformations of links... I haven't dug into it yet, and maybe we at Amicus can help to add support for this if there's interest, but we're using roar JSON-HAL for our API now and would like to become conformant with the new JSON-HAL specs, including curies, which we need for supporting parameterized queries without breaking spec.

We also have some other internal extensions we've made to roar that might make sense to merge in or publish as an additional extension gem, perhaps we can discuss both at some point if it makes sense.

undefined method `mounted_helpers' for rails 3.0.10

I'm getting this error when I try to startup the rails development server on my machine.

redu (api) > rails s
=> Booting WEBrick
=> Rails 3.0.10 application starting in development on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
Exiting
/Users/guiocavalcanti/.rvm/gems/ruby-1.8.7-p352/gems/roar-rails-0.0.2/lib/roar/rails/railtie.rb:12: undefined method `mounted_helpers' for #<ActionDispatch::Routing::RouteSet:0x103a2fc98> (NoMethodError)

I just created a representer as follows:

module EnvironmentRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

  property :name
  property :description
  property :description

  link :self do
    environment_url(self)
  end
end

And added this config to my development.rb config file:

  config.representer.default_url_options = {:host => "127.0.0.1:3000"}

Commenting this everything worked fine.

Validation errors

Doing a put

@event.put params[:id], 'application/json'

This is returned

{
    "errors": {
        "name": [
            "can't be blank"
        ]
    }
}

Which is what I expected, but the roar-rails gem does not capture this. I was hoping since @event/Event is a active model that that I could go @event.errors or have access to them some where.

With out having "builder.response :raise_error" it looks like it just works but does not update the record.

Validation failures when modifying collections

In our API, one of the primary models has several sub-model collections. These sub models all have validations, and the primary model also validates various aspects of the relationship between the collections. (For example: you can't save the Order if there isn't at least one Item. You can't have more Items than item_limit. Etc.)

When creating a record using representers, everything goes smoothly. However, when updating a record, these validations don't really work: even when using the find_or_instantiate strategy, it seems like the core call (somewhere along the line) is:

order.items = [new array of items]

Unfortunately, on an existing record, this is an instant call-to-action by Rails -- it runs off and inserts and deletes records, all before save is ever called on the parent object.

This behavior by Rails is one reason all our web forms use nested_attributes_for: the web forms are submitting the "items_attributes" collection, which means Rails doesn't attempt any database modifications until all the models are validated.

I saw another ticket (#81) where you mentioned that you shouldn't need to use nested_attributes with roar. Do you have any patterns that would allow roar to behave the way way nested_attributes does? (That is: that will not make any database updates until all of the models being touched have been validated.)

Including multiple representers overrides LinkCollection methods

It would be great to be able to include multiple representers with link definitions, currently this doesn't work, so for example:

class ArtistsController
    represents :json, Artist
    represents :xml, Artist
end

module ArtistRepresenter
    include Roar::Representer::JSON::HAL
    include Roar::Representer::XML

    link :self do |opts|
        artists_url
    end
end

returns

undefined method `to_hash' for #<Roar::Representer::Feature::Hypermedia::Hyperlink rel=:self, href="http://33.33.33.30:3000/artists">

I guess the Roar::Representer::XML call to Hyperlink is overriding the previously set to_hash method. Any simple resolution?
#8 seems to touch on this subject

Consume! not consuming

Working on the create action for an app. When inspecting the new lander in #create after calling #consume!, all attributes are empty. lander_params does exist. Any ideas? Maybe missing something simple. Thanks!

New Lander after calling consume!

#<Lander id: nil, name: nil, location: nil, default_offer_id: nil, created_at: nil, updated_at: nil>

Lander Params in create

{"name"=>"foo", "location"=>"www.google.com"}
module Api
  module V1
    class LandersController < ApplicationController
      include Roar::Rails::ControllerAdditions
      before_action :set_lander, only: [:show, :edit, :update, :destroy]
      respond_to :json

      def create
        lander = consume!(Lander.new, represent_with: LanderRepresenter)
        lander.save
        respond_with @lander, represent_with: LanderRepresenter
      end
  end
end  
module LanderRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

  property :name
  property :location
  property :default_offer

  link :self do
    v1_lander_url self
  end
end
  describe '#create' do
    before do
      json = { format: 'json', lander: { :name => "foo", :location => "www.google.com" } }
      post v1_landers_path, json.to_json, {'CONTENT_TYPE' => "application/json", 'ACCEPT' => 'application/json'}
    end

    it 'should create a lander' do
      binding.pry
    end
  end

Collection Representer seem boiler plate, any way to auto generate them?

Hi, I've been playing around with roar and roar-rails. Great job by the way, exactly what I was looking for.

I'm interested to know if there is a way to represent collections without having to create the boilerplate representer code that just reuses the singular form and provides the "collect" call. Is that possible now and I'm missing something? If not, I think just have a template with the plural collection and helper method, then use the singular form as the class and representer.

Related to this, is there a way to auto-discover the property and collection representers too? That seems like boiler plate (i.e. just do the same search that is performed with the controller "represents" logic.

Let me know if I'm way off base here...

representer_name_for returns representer name based on controller path for will paginate collection object

So I have something like

class Api::V1::UsersController < Api::V1::ApiController

  def index
    users = User.paginate(:per_page=>20, :page=>params[:page] || 1).all
    respond_with users
  end

  def show
    user = User.find params[:id]
    respond_with user
  end
end

roar rails is trying to use Api::V1::UsersRepresenter instead of UsersRepresenter for the index action, but for show action it correctly uses UserRepresenter. It seems that I can work around this by adding this on the controller.

represents :json, User

but maybe it should by default try to use UsersRepresenter for consistency?

Problem inferring representer class name with namespaced controllers

Loving the gem so far, but I did run into a problem.

I have an endpoint named Api::V1::ShopsController. In my #show action, ControllerAdditions#representer_name_for will use the model name for infer the representer name. However, in the case of an array, it will use a camelized controller path.

This means that for name spaced controllers, you have to put your representers in the same namespace as the controller when dealing with collections, but not in a namespace when you're dealing with single objects.

For example, in my case, my representers are named:

Api::V1::ShopsPresenter
ShopPresenter

I can take a look at doing a patch / pull request later, but I'm not yet really familiar with the codebase

consume! doesn't work with Unicorn

I'm trying to setup a create action, I have the following code:

def create
  @partner = Partner.new
  consume! @partner
  respond_with @partner
end

However, when I run it I get an exception:

NoMethodError (undefined method `string' for #<Unicorn::TeeInput:0x007f9ff1041ee0>):
  app/controllers/api/partners_controller.rb:16:in `create'

I'm not sure, but it looks like in controller_additions.rb:40 it should call #read instead of #string on request.body.

I'm using roar-rails 0.0.8, roar 0.11.2 and unicorn 4.3.1.

accepts_nested_attributes_for

Is is possible to create a record using accepts_nested_attributes_for in Roar?

For example, I have a Person model and a Subscriber model.

Say I want to create a User when I make subscriber, typically in Rails it is done like this:

Subscriber.create(
    :region          => 'West',
    :referrer        => 'John Smith',
    :user_attributes => {
        :email    => '[email protected]',
        :name     => 'Jane Doe',
        :password => 'foobar',
    }
)

How can I accomplish this using Representers?

Collection representor not using property constraints of representer

After some digging trying to find the best way to respond_with a collection of landers... in my app I have:

module LanderRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

  property :name
  property :location

  link :self do
    v1_lander_url self
  end
end
module LanderCollectionRepresenter
  include Representable::JSON::Collection

  items extend: LanderRepresenter, class: Lander
end
module Api
  module V1
    class LandersController < ApplicationController
      include Roar::Rails::ControllerAdditions
      before_action :set_lander, only: [:show, :edit, :update, :destroy]
      respond_to :json

      def index
        @landers = Lander.all
        respond_with @landers, :represent_with => LanderCollectionRepresenter
      end

      def show
        respond_with @lander, :represent_with => LanderRepresenter
      end


      private

      def set_lander
        @lander = Lander.find params[:id]
      end
    end
  end
end

Is this the proper way to respond_with a collection? Rendering the LanderRepresenter works perfectly; however, when I render the collection, it spits out the entire object (without limiting the response to the property constraints on :name, :location, :link). i.e:

[{"id":1,"name":"Dr. Jeffery Skiles1","location":"http://schusterbeahan.com/eddie/1","default_offer_id":null,"created_at":"2014-09-22T20:06:26.296Z","updated_at":"2014-09-22T20:06:26.296Z"},{...},{...}]

Handle validation errors

It seems for now roar-rails' responder doesn't handle validation errors transparently (correct me if I'm wrong) like rails' builtin responder does, see ActionController::Responder docs. Today I write this:

def create
  singer = Singer.new(params[:singer])
  if singer.save
    respond_with singer
  else
    render json: { message: "Validation Failed", errors: singer.errors }.to_json
  end
end

but I'd like this to automatically happen (depending if resource has_errors?..) so we can write this:

def create
  respond_with Singer.create(params[:singer])
end

Could you :

  • confirm this doesn't work like that for now ? (just tested it with rails 4 / ruby 2.0)
  • tell me if this could be interesting or if it's achievable in an other way ?

If it sounds good I can work on that and submit a pull request in a few weeks.

Rails Generators - Coder Needed!

We could need generators for creating representers in Rails. Anyone?

Usage could be like

rails g roar:representer singer name band:Band albums:collection

This API is subject to discussion, what kind of properties are common and could be handy being generated from the command line?

Examples for writing and testing generators can be found here:

Uninitialized constant Mongoid for index action

Hi,

I'm trying roar-rails for the first time (Rails 3.2.13, Mongoid 3.1.4), and I was able to get the show action to work just fine, but the index action throws an Uninitialized constant Mongoid error. What am I doing wrong?

Here is app/representers/programs_representer.rb:

require 'representable/json/collection'

module ProgramsRepresenter
  include Representable::JSON::Collection
  items extend: ProgramRepresenter, class: Program
end

Here is programs_controller.rb:

module Api
  module V1
    class ProgramsController < ApplicationController

      include Roar::Rails::ControllerAdditions
      respond_to :json

      def index
        programs = Program.all
        respond_with programs
      end

      def show
        program = Program.find(params[:id])
        respond_with program
      end

    end
  end
end

Here is program_representer.rb, which works with the show action:

require 'roar/representer/json'

module ProgramRepresenter
  include Roar::Representer::JSON

  property :id
  property :name
  property :description
end

Undefined method `merge'

Wohoo first issue...

lib/representers/user_representer.rb

module UserRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

 property :username

   link :self do
     api_user_url self
    end
end

controllers/api/users_controller.rb

class Api::UsersController < ApplicationController

def show
    @user = User.find(params[:id])
    @user.extend UserRepresenter
    respond_with @user
 end
end

api_user_url is a valid route

Undefined method `merge' for nil:NilClass thrown in api_user_url self . Actually I don't find this merge method...
More and more I think, these problems arise because I don't use ActiveResource.

Access controller/helper methods

It would be great to be able to access controller and helper methods from within the responders. I tried including the helper modules I wanted, but of course that didn't work, because those are instance methods, and property some_method(:whatever) is going to try evaluating immediately when the responder file is loaded by ruby.

Am I going about this wrong, or is the functionality not there?

Upgrading from 0.1.6 to 1.0.0

I have a project using roar-rails 0.1.6 and everything is working great 👌

I want to upgrate to 1.0.0 but when I tried to do so I started receiving the following error in my specs:

Failure/Error: post v1_groups_path, json(:group), headers_for(:json)
     NoMethodError:
       undefined method `group_url' for #<Api::V1::GroupsController:0x007fcd83e7d5a8>
     # ./app/controllers/api/v1/groups_controller.rb:25:in `create'
     # ./spec/api/v1/groups_spec.rb:31:in `block (3 levels) in <top (required)>'

Everything except for the #create action is working fine. For some reason calling respond_with @group in #create throws the above error.

This is my GroupsController:

module Api
  module V1
    class GroupsController < ApplicationController
      include Roar::Rails::ControllerAdditions

      before_action :set_group, only: [:show, :update, :destroy]
      respond_to :json

      def index
        @groups = Group.paginate(page: params[:page], per_page: params[:per_page])

        respond_with @groups
      end

      def show
        respond_with @group
      end

      def create
        @group = consume! Group.new
        @group.save

        respond_with @group
      end

      def update
        consume! @group
        @group.save

        respond_with @group
      end

      def destroy
        @group.destroy

        head :no_content
      end

      private

      def set_group
        @group = Group.find params[:id]
      end
    end
  end
end

Any idea what's going on? Missing something simple maybe?

Collection representer example

Hi,

I'm trying to write a collection representer (for index action) but I think I'm doing something wrong because I get this error:

> undefined method `_id' for #<Array:0x00000004ccf868>

This sounds logical since my index action returns an array and not an model instance, but how should my representer handle this ?

For now, my representer just contains property :id

Using :represent_items_with works fine but the doc suggests this is rather deprecated...

I haven't seen any collection representer example. Could you please provide one or tell me what I'm doing wrong ?

Thanks !

consume! is based on the accept header instead of the content-type header

Trying out the consume! method, I received the following error message: "undefined method `from_html' for #<...> (NoMethodError)", despite setting the content-type header to "application/json".

It turns out that parsing is based on the accept header (https://github.com/apotonick/roar-rails/blob/master/lib/roar/rails/controller_additions.rb#L28). Setting the accept header to "application/json" instead of "/" (curl's default), causes the formats array to go from [:html, :text, :js, :css, :ics, :csv, :png, :jpeg, :gif, :bmp, :tiff, :mpeg, :xml, :rss, :atom, :yaml, :multipart_form, :url_encoded_form, :json, :pdf, :zip] to [:json]. And then my representer succeeds at parsing the json document.

`render` accepting status, (or any other attributes for that matter)

I've been using the include Roar::Rails::ControllerAdditions::Render helpers so that I can still make use of classic render, but apparently it doesn't accept extra params, such as status code… I maybe mistaken but it looks like https://github.com/apotonick/roar-rails/blob/master/lib/roar/rails/controller_additions.rb#L80-L83 only cares about the format:

def render(options)
  format = options.keys.first
  super format => prepare_model_for(format, options.values.first, options)
end

I have a simple fix fork here… https://github.com/christopherhein/roar-rails/blob/render-status-code/lib/roar/rails/controller_additions.rb#L80-L84 I've been trying to figure out if there are other params that should be passed correctly…

Let me know what you think… I can always put a pull request…
Thanks!

Wrong status code for successful POST request when using respond_with

Seems to return a 200 status code rather than a 201 for successful post requests in the example below. Respond_with's normal behavior would be to set the status code based on the HTTP verb and whether the request was successful.

class PostsController < ApplicationController
  include Roar::Rails::ControllerAdditions

  def create
    post = Post.create(params[:post])

    respond_with post, represent_with: PostRepresenter
  end
end

You can override the status code by passing status: 201 but that's not ideal to do every time. I was going to take a shot at fixing this myself but had trouble getting any tests running locally :/

Pagination with Lonely Collections

I am following your pagination with roar post. However, total_entries is not valid within a lonely collection.

  1 module Api
  2   module V1
  3     class LandersRepresenter < Representable::Decorator
  4       include Representable::JSON::Collection
  5       property :total_entries
  6
  7       items decorator: LanderRepresenter
  8
  9       self.representation_wrap = :landers
 10     end
 11   end
 12 end

When I instead attempt to do the following, it expects a lander property on the lander active record relation: undefined method landers for #<Lander::ActiveRecord_Relation:0x007fd696127658>. Is this because I am using the Decorator class? However, I do get the total_entries property in this case.

 module Api
   module V1
     class LandersRepresenter < Representable::Decorator
       include Roar::Representer::JSON
       property :total_entries

       collection :landers, decorator: LanderRepresenter

       def landers
         self
       end
     end
   end
 end

What's a good way of handling pagination for index actions while using the decorator class?

Thanks for the help as always!

Fallback to default responder when there are no representers defined

I'd like to propose a feature

When defining a controller-wide responder using:

  def self.responder
    Class.new(super).send :include, Roar::Rails::Responder
  end

I get uninitialized constant NilClassRepresenter whenever respond_with nil is called. Wouldn't it be nice if Roar::Rails::Responder fallback to default rails responder when there aren't representers defined for the resource? With that in mind, any call to respond_with nil would call nil.to_json.

When a more complex class is passed to respond_with, say Post, and there arent a PostRepresenter defined, the responder would fallback to the default rails responder calling post.to_json or post.to_xml on the resource instance.

respond_with funkiness

Hey again! 😄

So after messing around a bit, we noticed that when using respond_with @something only an empty string was returned. render :json however works flawlessly. Any idea what that might be about?

require "roar/representer/json/hal" fails with roar-rails, but not with roar alone

I've got an API client written with roar-rails and embedded in a rails application. I'm trying to factor it out to its own gem so I can reuse it in other apps (including non-rails apps & scripts). It uses Roar::Representer::JSON::HAL for its representers, so in the separate gem I require the appropriate file to make that mixin available:

require "roar/representer/json/hal"

When I try to use the gem in the original app, I get an exception like this:

/Users/rsutphin/.rvm/gems/ruby-2.0.0-p481/gems/roar-rails-0.1.6/lib/roar/rails/hal.rb:3:in `<top (required)>': uninitialized constant Roar::Representer::JSON::HAL (NameError)
    from /Users/rsutphin/.rvm/gems/ruby-2.0.0-p481/gems/roar-0.12.9/lib/roar/representer/json/hal.rb:44:in `<module:JSON>'
    from /Users/rsutphin/.rvm/gems/ruby-2.0.0-p481/gems/roar-0.12.9/lib/roar/representer/json/hal.rb:4:in `<module:Representer>'
    from /Users/rsutphin/.rvm/gems/ruby-2.0.0-p481/gems/roar-0.12.9/lib/roar/representer/json/hal.rb:3:in `<top (required)>'

... originating on the line in the gem that requires "roar/representer/json/hal". I've reproduced this in a clean rails app: reproduction script.

In the rails app, if you just reference the constant Roar::Representer::JSON::HAL, everything works fine (which is why the client was working when it was embedded in the app). But this doesn't work with plain Roar, so I can't use this approach in my shared gem.

The problem appears to be a conflict caused by an autoload definition in roar-rails. This autoload tells ruby to require 'roar/rails/hal' to get Roar::Representer::JSON::HAL. When roar/representer/json/hal attempts to define module HAL, the autoload is triggered, resulting in roar/rails/hal being evaluated from within roar/representer/json/hal, before Roar::Representer::JSON::HAL is defined. Since roar/representer/json/hal is already being required, the require for it from within roar/rails/hal is ignored, and the subsequent attempt to access Roar::Representer::JSON::HAL fails.

I'm trying to figure out how to work around this. I wonder if the best solution might just to be to define the to_hal and from_hal aliases in Roar instead, then remove roar/rails/hal from the picture.

Representer for index action

Apologies if I'm being incredibly dumb here, but I can't seem to find a way to change the representer for an index action.

First I assumed a convention that would work with a plural name, and then I tried by explicitly specifying the with_representer option on the respond_with, but I still can't get it to pick up. I had a look through the tests but I couldn't find any examples, is this not possible?

Cheers,
M

Notes on our experience so far with roar-rails

Nick,

We've been using roar rails for about a week now, and we're liking it. Here are some of the things that we've notice which you may want to take under consideration

consuming could have better defaults

We find that so much of our code ends up like this

def create
  @song = Song.new
  consume! @foo
  respond_with @song
end

Since this is the SongsController, we know that we're working with the Song model, so why not make it implied:

def create
  @song = consume
  respond_with @song
end

Model named something else that can't be inferred? how about :

def create
  @track = consume :track # alternatively @track = consume Track
  respond_with @track
end

consume form url-encoded

We had to write an html representer in order to consume form parameters. Not sure if this is the right way, but we wanted to be able to use the exact same controller logic whether the request is coming as a form submission or an AJAX request posting JSON. It seems like this should come right out of the box.

It's kindof a hack because we don't want to use the representer for to_html, only for its from_html. Also, it isn't really 'html' per-se, just application/www-form-urlencoded. Not really sure what the best way is here, just wanted to make you aware of the shims we had to put in to make the app work.

module Roar
  module Representer
    module HTML

      def self.included(base)
        base.class_eval do
          include Representer
          extend ClassMethods
        end
      end

      module InstanceMethods
        def from_html(query_string, options = {})
          Rack::Utils.parse_nested_query(query_string).each do |k, v|
            self.instance_variable_set("@#{k}", v)
          end
        end
      end
    end
  end
end

generators and defaults

figuring out which representers you want to use can be tedious and it would be nice if you could have a ApplicationRepresenter where you could store your defaults, so that you could say:

module SongRepresenter
  include ApplicationRepresenter
  #...
end

roar:install could generate this for you in application_representer.rb, and roar:representation would generate a representation that included it by default.

What's the best practice to tweak representations based on authentication?

There are some attributes, and hypermedia that are exposed for different roles. Is the answer to use a different representer? that seems too heavy handed. It would be nice to have scopes or profiles or something that could be passed in as an option to the respond_with. Then you could mark it up in the representer DSL.

module SongReprestener
  property :name
  link :purchase { purchase_song_path(self) }
  scope(:admin) do
    link :upload { upload_song_path(self) }
  end
end

What's in a name?

personally I prefer FooRepresentation as opposed to FooRepresenter, but that's just me. It implies that "this defines what the representation is", as opposed to "This defines is how the representation is generated" A small opinionated nitpick, but worth mentioning nonetheless.

Conclusion

It's shaping up very nicely so far, is this a good forum to submit feedback? There doesn't seem to be a mailing list.

1.8 test fix?

one of 1.8.x the problems is, that Hashes in 1.8 are not order save so we get this problem:

https://github.com/apotonick/roar-rails/blob/master/test/representer_test.rb#L8

1) Failure:
test_representers_can_use_URL_helpers(RepresenterTest)
<"{\"name\":\"Bumi\",\"links\":[{\"rel\":\"self\",\"href\":\"http://http://roar.apotomo.de/singers/Bumi\"}]}"> expected but was
<"{\"name\":\"Bumi\",\"links\":[{\"href\":\"http://http://roar.apotomo.de/singers/Bumi\",\"rel\":\"self\"}]}">.

has any one an idea, how to fix that?

Strange intermittent error /w multipart posts

ruby 1.9.3-p429
rails 3.2.13
representable 1.5.3
roar 0.11.18
roar-rails 0.1.0

I'm experiencing a strange intermittent error involving multipart posts. It happens rarely, but once it starts to happen, it keeps happening until the app server is restarted.

My question is, why is Roar::Representer::JSON::InstanceMethods::to_hash being called? It doesn't seem like roar should be involved here, in the processing of an options hash.

Additionally, considering that Rack::Multipart::Parser always calls Tempfile.new with no options, it seems very strange that the error only manifests some of the time.

Stack trace:

NoMethodError: undefined method `id' for nil:NilClass
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/binding.rb:77:in `block in get'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/binding.rb:93:in `represented_exec_for'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/binding.rb:76:in `get'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/binding.rb:36:in `block in compile_fragment'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/binding.rb:93:in `represented_exec_for'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/binding.rb:35:in `compile_fragment'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable.rb:95:in `compile_fragment'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/roar-0.11.18/lib/roar/representer/json/hal.rb:41:in `compile_fragment'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable.rb:62:in `serialize_property'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/feature/readable_writeable.rb:11:in `serialize_property'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable.rb:55:in `block in create_representation_with'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable.rb:54:in `each'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable.rb:54:in `create_representation_with'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/representable-1.5.3/lib/representable/hash.rb:33:in `to_hash'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/roar-0.11.18/lib/roar/representer/json.rb:21:in `to_hash'
  /usr/local/lib/ruby/1.9.1/tmpdir.rb:117:in `try_convert'
  /usr/local/lib/ruby/1.9.1/tmpdir.rb:117:in `create'
  /usr/local/lib/ruby/1.9.1/tempfile.rb:134:in `initialize'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/multipart/parser.rb:104:in `new'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/multipart/parser.rb:104:in `get_current_head_and_filename_and_content_type_and_name_and_body'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/multipart/parser.rb:19:in `block in parse'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/multipart/parser.rb:17:in `loop'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/multipart/parser.rb:17:in `parse'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/multipart.rb:25:in `parse_multipart'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/request.rb:336:in `parse_multipart'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/request.rb:201:in `POST'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/methodoverride.rb:26:in `method_override'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/methodoverride.rb:14:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/runtime.rb:17:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/activesupport-3.2.13/lib/active_support/cache/strategy/local_cache.rb:72:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/lock.rb:15:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:136:in `forward'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:143:in `pass'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:155:in `invalidate'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:71:in `call!'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-cache-1.2/lib/rack/cache/context.rb:51:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/engine.rb:479:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/application.rb:223:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/railties-3.2.13/lib/rails/railtie/configurable.rb:30:in `method_missing'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/lint.rb:48:in `_call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/lint.rb:36:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/showexceptions.rb:24:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/commonlogger.rb:33:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/chunked.rb:43:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/rack-1.4.5/lib/rack/content_length.rb:14:in `call'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/unicorn-4.6.2/lib/unicorn/http_server.rb:552:in `process_client'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/unicorn-4.6.2/lib/unicorn/http_server.rb:632:in `worker_loop'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/unicorn-4.6.2/lib/unicorn/http_server.rb:500:in `spawn_missing_workers'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/unicorn-4.6.2/lib/unicorn/http_server.rb:142:in `start'
  /mnt/app/shared/bundle/ruby/1.9.1/gems/unicorn-4.6.2/bin/unicorn:126:in `<top (required)>'
  /mnt/app/shared/bundle/ruby/1.9.1/bin/unicorn:23:in `load'
  /mnt/app/shared/bundle/ruby/1.9.1/bin/unicorn:23:in `<main>'

Strange Model class name lookups when using namespaces

The following case fails (assume that ApplicationRepresenter sets up the included modules for Roar and ApplicationController includes Roar::Rails::ControllerAdditions:

module Instruments
  class Trumpet < ActiveRecord::Base
  end

  class TrumpetRepresenter < ApplicationRepresenter
  end

  class TrumpetsController < ApplicationController
    def show
      trumpet = Trumpet.find(params[:id])
      respond_with trumpet
    end
  end
end

The error is: Uninitialized Constant Instruments::Instruments. Seems like a better method of selecting a representer would be the following:

Assume you have a controller namespaced as N1::N2::Controller and a representer namespaced as N1::ModelRepresenter:

Check for the following constants in order and if none is found then fail with Uninitialized constant: N1::ModelRepresenter

N1::N2::N1::ModelRepresenter
N1::N1::ModelRepresenter
N1::ModelRepresenter

This way if the model is not namespaced it takes on the controllers namespace by default but can still find an un-namespaced representer.

Collections are not wrapped with with the right root tag in XML

When serializing using XML, the root tag is incorrect. It is either or depending on the version of rails. I believe this ultimately tracks back to here: https://github.com/apotonick/representable/blob/master/lib/representable.rb#L58

The self.class.name does not properly track back to the model when it is a collection. A work-around for now is to place self.representation_wrap = :collection_name in the collection representer. Maybe this can be set by roar-rails, since I don't think representable is the right place to fix it.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.