Coder Social home page Coder Social logo

roar's Introduction

Trailblazer

Battle-tested Ruby framework to help structuring your business logic.

Gem Version

What's Trailblazer?

Trailblazer introduces new abstraction layers into Ruby applications to help you structure your business logic.

It ships with our canonical "service object" implementation called operation, many conventions, gems for testing, Rails support, optional form objects and much more.

Should I use Trailblazer?

Give us a chance if you say "yes" to this!

  • You hate messy controller code but don't know where to put it?
  • Moving business code into the "fat model" gives you nightmares?
  • "Service objects" are great?
  • Anyhow, you're tired of 12 different "service object" implementations throughout your app?
  • You keep asking for additional layers such as forms, policies, decorators?

Yes? Then we got a well-seasoned framework for you: Trailblazer.

Here are the main concepts.

Operation

The operation encapsulates business logic and is the heart of the Trailblazer architecture.

An operation is not just a monolithic replacement for your business code. It's a simple orchestrator between the form objects, models, your business code and all other layers needed to get the job done.

# app/concepts/song/operation/create.rb
module Song::Operation
  class Create < Trailblazer::Operation
    step :create_model
    step :validate
    left :handle_errors
    step :notify

    def create_model(ctx, **)
      # do whatever you feel like.
      ctx[:model] = Song.new
    end

    def validate(ctx, params:, **)
      # ..
    end
    # ...
  end
end

The step DSL takes away the pain of flow control and error handling. You focus on what happens: creating models, validating data, sending out notifications.

Control flow

The operation takes care when things happen: the flow control. Internally, this works as depicted in this beautiful diagram.

Flow diagram of a typical operation.

The best part: the only way to invoke this operation is Operation.call. The single entry-point saves programmers from shenanigans with instances and internal state - it's proven to be an almost bullet-proof concept in the past 10 years.

result = Song::Operation::Create.(params: {title: "Hear Us Out", band: "Rancid"})

result.success? #=> true
result[:model]  #=> #<Song title="Hear Us Out" ...>

Data, computed values, statuses or models from within the operation run are exposed through the result object.

Operations can be nested, use composition and inheritance patterns, provide variable mapping around each step, support dependency injection, and save you from reinventing the wheel - over and over, again.

Leveraging those functional mechanics, operations encourage a high degree of encapsulation while giving you all the conventions and tools for free (except for a bit of a learning curve).

Tracing

In the past years, we learnt from some old mistakes and improved developer experience. As a starter, check out our built-in tracing!

result = Song::Operation::Create.wtf?(params: {title: "", band: "Rancid"})

Tracing the internal flow of an operation.

Within a second you know which step failed - a thing that might seem trivial, but when things grow and a deeply nested step in an iteration fails, you will start loving #wtf?! It has saved us days of debugging.

We even provide a visual debugger to inspect traces on the webs.

There's a lot more

All our abstraction layers such as operations, form objects, view components, test gems and much more are used in hundreds of OSS projects and commercial applications in the Ruby world.

We provide a visual debugger, a BPMN editor for long-running business processes, thorough documentation and a growing list of onboarding videos (TRAILBLAZER TALES).

Trailblazer is both used for refactoring legacy apps (we support Ruby 2.5+) and helps big teams organizing, structuring and debugging modern, growing (Rails) applications.

Documentation

Make sure to check out the new beginner's guide to learning Trailblazer. The new book discusses all aspects in a step-wise approach you need to understand Trailblazer's mechanics and design ideas.

The new begginer's guide.

roar's People

Contributors

andresf avatar apotonick avatar bernd avatar bkeepers avatar caike avatar csexton avatar edejong avatar eterps avatar felixbuenemann avatar fernandes avatar franzliedke avatar fwolfst avatar gilesbowkett avatar greggilbert avatar gregkare avatar guiocavalcanti avatar hilary avatar jandudulski avatar miyagawa avatar mogman1 avatar myabc avatar olleolleolle avatar paulccarey avatar pd avatar seuros avatar summera avatar theunixbeard avatar thhermansen avatar timoschilling avatar yogeshjain999 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  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

roar's Issues

Problem with from_json(strong_parameters)

I'm using roar with roar-rails in my rails 4 app.
And I'm sending data as JSON with header "Content-Type": "application/json"

def create
  machine = Machine.new.extend(Api::V1::MachineRepresenter)
  machine.from_json(machine_params)
  machine.save
  respond_with machine, represent_with: Api::V1::MachineRepresenter
end

Manually providing the json in console/pry works fine.

machine = Machine.new.extend(Api::V1::MachineRepresenter)

machine_json = "{\"name\":\"Centipede\",\"type\":\"Tractor\",\"sub_type\":\"Pickup\",\"license\":\"WA\",\"manufacturer\":\"Mahindra\",\"model\":\"Bolero\",\"year\":\"1990\",\"vin\":\"12345678\"}"

machine.from_json(machine_params)
machine.save

But in the rails controller context, it blows up with:

Failure/Error: Unable to find matching line from backtrace
     TypeError:
       no implicit conversion of ActionController::Parameters into String

This exception is raised when the controller create action hits machine.from_json(machine_params) line.

Previously I was thinking it was bug with the https://github.com/zipmark/rspec_api_documentation library, which I am using to test and document the Api.
And tracked around at zipmark/rspec_api_documentation#103

But finally it was not its issue, rather the from_json issue because if I just do railsy way,

def create
  machine.create(machine_params)
  respond_with machine, represent_with: Api::V1::MachineRepresenter
end

it works.

Is it really due to the strong_parameters?

Is this question fine here or should I post it to roar-rails repo?

rails 2.3? ruby 1.8.7?

Hello,

Have you tested this gem with rails 2.3 or ruby 1.8.7?

I've been messing with the gem, and while it does not seem to be declared, it does not work with ruby 1.8.7 because it uses public_send in 3 places:

./lib/roar/representer/feature/model_representing.rb:62 value = public_send(definition.getter)
./lib/roar/representer.rb:19 attributes.each { |p,v| representer.public_send("#{p}=", v) }
./lib/roar/representer.rb:29 value = public_send(definition.getter)

I used the patch fro ActiveSupport to backport public_send to 1.8.7, but without that I got a method not found for these.

I am also seeing issues where extending a Representer in an AR 2.3 object does not act as expected.
For attributes that are in the representer and the model, assignment does not stick.

I'm debugging it now, but seems to be something about how the attribute assignment method are implemented in 2.3 (method_missing perhaps?) that does not play with the representer.

Out of the box custom media type?

Hey,

I just tried rabl, restfulie, argonaut, draper, json_builder and think that roar could be the one to implement my rest api. ;-)
Configuration for my custom mime type:

config/initializers/mime_types.rb

Mime::Type.register "application/vnd.xxx-v1+json", :xxx_v1

app/controllers/api/v1/base_controller.rb

class Api::V1::BaseController < ActionController::Base
respond_to :xxx_v1

# just a try
#  ActionController.add_renderer :xxx_v1 do |obj, options|
#   json = obj.to_xxx_v1
#    self.content_type ||= Mime::Type.lookup('application/vnd.xxx-v1+json')
#   self.response_body  = json
#   end
end

app/controllers/api/v1/users_controller.rb

class Api::V1::UsersController < Api::V1::BaseController 
  def show
    @user = User.find(params[:id])
    @user.extend(UserRepresenter)
    respond_with @user
  end
end

lib/representers/user_representer.rb

require 'roar/representer/json'

module UserRepresenter
  include Roar::Representer::JSON

  property :username
end

GET http://127.0.0.1:3000/api/users/4ef853273bdc010ffa000017
Accept application/vnd.xxx-v1+json

Error:
Missing template api/v1/users/show, api/v1/base/show with {:handlers=>[:erb, :builder], :formats=>[:ginkgo_v1], :locale=>[:en, :en]}. Searched in: * "app/views"

When using Accept application/json and respond to_json everything is working fine. Do I have to add an renderer somewhere?

Another small question: Can I rename properties? Something like

 property :username => :user_name

Data passed to render method isn't passed to embedded resources

Following the example in lib/roar/representer/feature/hypermedia.rb, I'd like to pass data to link blocks so that some URLs are only present when some condition is true.

It looks like I can't pass data to embedded resources. Is that intended? It seems to limit the usefulness of the feature.

example:

require 'roar/representer/json'
require 'roar/representer/json/hal'

class Foo
  attr_reader :bar

  def initialize bar
    @bar = bar
  end
end

module FooRepresenter
  include Roar::Representer::JSON::HAL

  property :bar

  link :qux do |context|
    if context[:qux]
      "http://example.com/foo/1/qux"
    else
      nil
    end
  end
end

module FoosRepresenter
  include Roar::Representer::JSON::HAL

  collection :foos,
    :class => Foo,
    :extend => FooRepresenter,
    :embedded => true

  def foos
    self
  end

  link :qux do |context|
    if context[:qux]
      "http://example.com/foo/qux"
    else
      nil
    end
  end
end

single_foo = Foo.new "a"
single_foo.extend FooRepresenter
puts single_foo.to_json(:qux => true)

multiple_foos = %w(b c).map {|bar| Foo.new(bar)}
multiple_foos.extend FoosRepresenter
puts multiple_foos.to_json(:qux => true)
{
  "bar":"a",
  "_links": {"qux":{"href":"http://example.com/foo/1/qux"}}}

{
  "_embedded":{
    "foos":[
      {"bar":"b","_links":{}},
      {"bar":"c","_links":{}}]
  },"_links":{
    "qux":{"href":"http://example.com/foo/qux"}}}

Cannot reproduce README example

Hi Nick,

I've got a problem trying out the README example using the following versions:

  • roar: 0.9.1
  • representable: 1.0.1
  • ruby: 1.9.3p0

Code

require 'roar/representer/json'
require 'roar/representer/feature/hypermedia'

class Article
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

  property :title
  property :id

  link :self do
    article_url(represented)
  end 
end

Then running

Article.new(
  title: "Lonestar",
  id:    666)

leads to

ArgumentError: wrong number of arguments(1 for 0)
    from (irb):15:in `initialize'
    from (irb):15:in `new'

And

Article.from_hash(
  title: "Lonestar",
  id:    666)

leads to

NoMethodError: undefined method `title=' for #<Article:0x0000000235e240>
    from /home/dan/.rvm/gems/ruby-1.9.3-p0/gems/representable-1.0.1/lib/representable.rb:54:in `block in update_properties_from'
    from /home/dan/.rvm/gems/ruby-1.9.3-p0/gems/representable-1.0.1/lib/representable.rb:50:in `each'
    from /home/dan/.rvm/gems/ruby-1.9.3-p0/gems/representable-1.0.1/lib/representable.rb:50:in `update_properties_from'
    from /home/dan/.rvm/gems/ruby-1.9.3-p0/gems/representable-1.0.1/lib/representable/json.rb:47:in `from_hash'
    from /home/dan/.rvm/gems/ruby-1.9.3-p0/gems/representable-1.0.1/lib/representable/json.rb:31:in `from_hash'

Choose representers for nested elements on runtime

I wonder if it is feasible to let roar guess the representer class of a collection/property.

Imagine a generic collection class:

class Api::Collection
    include Roar::Representer
    include CollectionRepresenter

    def initialize(type, total=0, members=[], self_link, next_link, prev_link)
      @type = type
      @total = total
      @members = members
      @self_link = self_link
      @next_link = next_link
      @prev_link = prev_link
     end

    attr_accessor :type, :total, :members, :self_link, :next_link, :prev_link
end

And the corresponding representer:

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

  property :type
  property :total

  link :self do
    self_link
  end
  link :next do 
    next_link
  end
  link :prev do
    prev_link
  end
   collection :members #, :class => User, :extend => UserRepresenter
end

As members could contain different objects, we cannot set :class and :extend options.
What do you think about letting roar extract the class name of a collection element and set the sought_type and representer_module of the corresponding Definition according to a configurable convention. Something like class.singularize+"Representer".

Representer Ignored When Object Part of a Data Structure

I like my API's responses to contain a single root node which describes the contents of the file (like well-formed XML). Let's say I have a Fruit class which uses a FruitRepresenter. In my controller, then, I'd like to do something like this:

render :json => {"fruit" => @fruit}

However, this doesn't work because when to_json is called on the hash the built-in Rails serialization (which uses as_json) is used, instead of the structure defined in FruitRepresenter. I can fix this by adding the following to FruitRepresenter:

def as_json(options = {})
  to_hash
end

but it seems like this should be handled by Roar. What do you think?

roar 0.11.5 error if representer doesn't define links

require 'roar/representer/json'
require 'roar/representer/feature/hypermedia'

class Foo
  def bar
    "qux"
  end
end

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

  property :bar

  # link :self do
  #   "what"
  # end
end

f = Foo.new
f.extend FooRepresenter
puts f.to_json

with roar-0.11.4 and representable-1.2.9

{"bar":"qux"}

with roar-0.11.5 and representable-1.2.9

/home/anthony/.rbenv/versions/1.9.3-p362/lib/ruby/gems/1.9.1/gems/roar-0.11.5/lib/roar/representer/feature/hypermedia.rb:56:in `links_definition_options': undefined method `links_definition_options' for Foo:Class (NoMethodError)
    from /home/anthony/.rbenv/versions/1.9.3-p362/lib/ruby/gems/1.9.1/gems/roar-0.11.5/lib/roar/representer/feature/hypermedia.rb:46:in `create_links_definition'
    from /home/anthony/.rbenv/versions/1.9.3-p362/lib/ruby/gems/1.9.1/gems/roar-0.11.5/lib/roar/representer/feature/hypermedia.rb:51:in `links_definition'
    from /home/anthony/.rbenv/versions/1.9.3-p362/lib/ruby/gems/1.9.1/gems/roar-0.11.5/lib/roar/representer/feature/hypermedia.rb:85:in `prepare_links!'
    from /home/anthony/.rbenv/versions/1.9.3-p362/lib/ruby/gems/1.9.1/gems/roar-0.11.5/lib/roar/representer/feature/hypermedia.rb:60:in `before_serialize'
    from /home/anthony/.rbenv/versions/1.9.3-p362/lib/ruby/gems/1.9.1/gems/roar-0.11.5/lib/roar/representer/json.rb:20:in `to_hash'
    from /home/anthony/.rbenv/versions/1.9.3-p362/lib/ruby/gems/1.9.1/gems/representable-1.2.9/lib/representable/json.rb:35:in `to_json'
    from test.rb:21:in `<main>'

if the link is uncommented, then there is no error with 0.11.5

How the error information is represented if the resource validation fails?

Since I didn't find any place to ask and its not a bug, just want to know the way out.

I am using roar with roar-rails in my rails app with the HAL+JSON media type.

My question is, if a resource save fails with the validation errors, how to represent it in HAL? Or maybe I am not understanding the Hypermedia in true sense.
Can anyone put some light on this?
Sample code in context of a bare bone Rails app would be much better?

How do I run the minitest suite?

I'm trying to run a regression test that I wrote but I can't make test suit run. I've never used minitest before so I tried the obvious:

roar (master) > cd .
BUNDLE_BIN: bin
roar (master) > rake test
/Users/guiocavalcanti/.rvm/rubies/ruby-1.8.7-p352/bin/ruby -I"lib:test" -I"/Users/guiocavalcanti/.rvm/gems/ruby-1.8.7-p352/gems/rake-0.9.2.2/lib" "/Users/guiocavalcanti/.rvm/gems/ruby-1.8.7-p352/gems/rake-0.9.2.2/lib/rake/rake_test_loader.rb" "test/client_test.rb" "test/hal_json_test.rb" "test/http_verbs_feature_test.rb" "test/hypermedia_feature_test.rb" "test/json_representer_test.rb" "test/representer_test.rb" "test/transport_test.rb" "test/xml_representer_test.rb" 
Loaded suite /Users/guiocavalcanti/.rvm/gems/ruby-1.8.7-p352/gems/rake-0.9.2.2/lib/rake/rake_test_loader
Started

Finished in 0.000232 seconds.

0 tests, 0 assertions, 0 failures, 0 errors

As you can see it finished with no tests run. I've never used minitest before, so excuse me for the stupid question.

Inheriting links from Roar::Decorator class

I have two classes, a parent Roar::Decorator class that defines links and a child class that inherits from it.

A simplified example:

class Listings < BaseListings
end

class BaseListings < Roar::Decorator
  include Roar::Decorator::HypermediaConsumer
  include Representable::JSON
  include Roar::Representer::JSON::HAL

  link :next do
    "this is a link"
  end
end

The link doesn't show up on the base class when it gets decorated however. But if I move the link into the child class it shows up fine.

Any ideas? Something I'm missing?

Thanks!

Readme needs some love

Note: This README sucks and will be updated this week (April 10, 2013).

Looking at posts from @apotonick blog and comparing to README it looks that README is quite outdated. Is there any chance that it will be improved soon?

Providing an empty array of HAL links errors out upon #rel

When using a HAL representer in Roar 0.12.0, if one returns an empty array from a links block, Roar blows up when it calls LinkArray#rel because there is no first element: https://github.com/apotonick/roar/blob/master/lib/roar/representer/json/hal.rb#L139

...producing a stack trace like this:

Rack app error: #<NoMethodError: undefined method `rel' for nil:NilClass>
roar-0.12.0/lib/roar/representer/json/hal.rb:139:in `rel'
roar-0.12.0/lib/roar/representer/feature/hypermedia.rb:113:in `add'
roar-0.12.0/lib/roar/representer/feature/hypermedia.rb:83:in `block in prepare_links!'
roar-0.12.0/lib/roar/representer/feature/hypermedia.rb:82:in `each'
roar-0.12.0/lib/roar/representer/feature/hypermedia.rb:82:in `prepare_links!'
roar-0.12.0/lib/roar/representer/feature/hypermedia.rb:44:in `before_serialize'

Inline Decorators

They appear to be broken. I can create an anonymous class and assign it to the :decorator key, but when trying to use a block I get a NoMethodError on represented#to_hash. After some light skimming through the code, I suspect this may be a problem with #representer_engine not being defined on Representable::Decorator.

Support arrays of links with JSON HAL

JSON HAL spec (http://tools.ietf.org/html/draft-kelly-json-hal-03#section-4.1.1) says,

[_links] is an object whose property names are link relation types (as defined by [RFC5988]) and values are either a Link Object or an array of Link Objects.

Mike Kelly gives an example of what that looks like (https://groups.google.com/group/hal-discuss/msg/5afa41ab1740aee1):

{ 
 "_links": { 
   "self": { "href": "/product/987" }, 
   "upsell": [ 
     { "href": "/product/452", "name": "FP01", "title": "Flower pot" }, 
     { "href": "/product/832", "name": "HD77", "title": "Hover donkey" } 
   ] 
}

Presently, if you try to use an array of links, the output is more like:

{ 
  "_links": { 
    "self": { "href": "/product/987" }, 
    "upsell": {
      "href": [ "/product/452", "/product/832" ]
    }
  }
}

undefined method `inheritable_array' for #<Representable::Config>

I updated Roar from 0.11.9 to 0.11.11 and I get the following error:

undefined method `inheritable_array' for #<Representable::Config:0x007fe5c4f52a60>
# ./app/controllers/api/v1/discussions_controller.rb:6:in `index'

Controller

def index
    discussions = current_eclass.discussions.paginate(page: params[:page], per_page: params[:per_page]).all
    respond_with discussions
  end

Representer

module Api::V1::DiscussionsRepresenter
  include Roar::Representer::JSON
  include Api::V1::PaginationRepresenter

  collection :items, :extend => Api::V1::DiscussionRepresenter, :class => Discussion

  def items
    collect
  end

end

PaginationRepresenter

module Api::V1::PaginationRepresenter
  include Roar::Representer::JSON
  include Roar::Representer::Feature::Hypermedia

  property :total_entries
  property :per_page
  property :current_page

  def items
    self
  end

end

It was correctly working, until I updated. Is there anything I need to change in order to be compatible with the new version?

Filter out fields and properties per request

This is more of an inquiry rather than an issue, but I've been trying to figure out how to pass options such as :include and :exclude to the Representable#to_hash method in the runtime from the controller.

The goal here is to filter out properties in representers per request basis, and the use cases are:

  • do not include private information (for example, 'email' field) unless a request is made as authenticated by the user herself
  • implement lightweight representations based on query parameters to reduce bandwidth (like Facebook does with ?fields=id,name).

I'm digging roar and representable's to_hash code and Rails responder implementation, and I think you can pass :include and :exclude to the method to implement this, but the call seems to be made deep within the stack and wonder what's the best way to pass this option around. Or there may be a better way to do this, like adding some hooks in display in the Roar responder.

Previously I was using RABL and in RABL this was possible by accessing controller's helper methods inside templates.

Any suggestions would be appreciated.

parse_strategy feature should allow richer matching with existing linked or embedded resources

I've got a model for People and Addresses. People have many addresses.
In my PersonRepresenter, I've got:

 collection :addresses, :class => Address, :extend => AddressRepresenter, 
 :embedded => true , :parse_strategy => :sync

I initially stumbled upon the sync thing because I noticed that if I did a POST of a person, but didn't include the _embedded address array, it would delete my existing address collection for a given person.

Note, I'm doing some matching on POSTs. If you do a post of a person and the server finds that a matching person already exists then it passes the existing person object to the consume! call in the person controller.

Is there a way I can:

  1. If a collection property is not present in the request to the server, then leave the collection alone. Dont get rid of it.
  2. control how parse_strategy does its matching? How does it know which record it is supposed to be updating?

Using context outside of link definitions

Right now the params passed when creating the representation (e.g. representer.to_json(user: current_user)) are only available in the context of links, but there are scenarios where it would be useful to access it elsewhere.

Some use cases I can think of,

  • choosing to embed certain resources depending on the request context
  • properties whose value depend on the current user (e.g. is a post unread?)
  • authorization decisions?

Of course, all of these can be achieved by other means, but should this be something that roar provides in some form? I'm on the fence myself.

Render :links to JSON as hash instead of array

LOVING ROAR, thanks Nick! Only surprise has been consuming hypermedia links in a JSON struct client-side, I have to know the array index of the link, of discover the link by iterating over the array, or render the link in my representer class as a property (faster but yucky).

We can access links as a hash server-side so why not client-side. Might make the LinkCollection class leaner if it were a Hash instead of an Array and didn't have to look up array elements? Then look up would be based on the rel value as the key? Here's some stoopid code, I have not tested it but I will if you think I'm in the correct location:

    class LinkCollection < Hash
      def [](rel)
        self[rel] ? self[rel].href : nil
      end

      # Checks if the link is already contained by querying for its +rel+.
      # If so, it gets replaced. Otherwise, the new link gets appended.
      def update_link(link)
        if self[link.rel]
          # this assumes that the rel value is not changed
          return self[link.rel] = link
        end
        self[link.rel] = link
      end
    end

I'm sure it's not that simple, please advise. Maybe you made links an array so that the rel attribute can change and not screw up the collection? In that case, just tell us to not screw around with the rel assignment, ha ha. Oh, but wait: rel is derived from the symbol given as the first argument to links so maybe it's unlikely we'll screw with it, right? We treat it as immutable, yeah? Hmm...

Thanks again!

How to dynamically exclude links from documents?

Has anybody a good idea how I could dynamically exclude links from represented documents?

For attributes I could use something like a hack:

 def show
   user = ...
   if exclude_comment? 
     user.private_comment = nil
   end
   respond_with user.extend(UserRepresenter)
 end

But for links? OK I could put decider logic into the link block but this only hides the href attribute and not the rel. Furthermore I "only" have the user instance available for deciding.

module UserRepresenter

  link : privateComments do
    if check_something?
       user_comment_link id
    end
end 

By the way: For incoming documents :exclude and :include options in from_json work pretty good!

Property renamed with :from is not set

My user model has the property :password_confirmation and includes the UserRepresenter.

My UserRepresenter defines

property :password_confirmation, :from => :passwordConfirmation

I do a POST with HTTP body:
{
"passwordConfirmation":"123"
}

My controller:

def create
 @user = User.new.from_json(request.raw_post)
 puts @user.password_confirmation # =====> ""
 puts @user.passwordConfirmation  # ==== => "123"
end

Why is password_confirmation not set?

Decorator links

Love the decorator links, but the consuming through represented#links= is clunky. Since properties can have :getter and :setter defined, wouldn't it make sense for links to have these keys too?

Code organization and re-use

This is less of an issue and more of a question.

I have an app that uses Roar quite heavily. It is a fairly wide API for a small set of resources.

As an example, one resource is for a User

GET /users/123
GET /users/123/subordinates
GET /users/search/firstname/abc

In this particular example, each end-point is returning an instance, or a collection of, my User model. Since the search results and the "has many" associations can return a large set of data, and the intended use cases for these resources don't necessitate the need to return the entire User record, I ended up creating a different Representer for each end-point.

I don't think this is necessarily bad, but it forced me into a situation where I am duplicating code from the master UserRepresenter since these are modules and not classes that can be subclassed.

The end result is that I have suddenly amassed a collection of 21 Representers for 9 models. Ideally, I'd like to at least provide some base functionality that I could mixin in to the more focused Representers, but, in my experience, that is difficult to do with the Roar DSL.

Do you have any suggestions on how to better organize an application's representers or develop some sort of code re-use pattern? Or am I missing something higher level (aka "You're doing it wrong")?

And as an aside, has anyone used Roar within a versioned API? It seems like namespacing all the representers would add even more code duplication. This problem seems to be averted by an approach like version_cake, but you lose the nice Roar DSL in the process.

Unable to install on Windows?

gem install roar

ERROR:  While executing gem ... (Errno::EINVAL)
    Invalid argument - C:/Ruby193/lib/ruby/gems/1.9.1/gems/roar-0.11.2/test/dummy/module - (2011-05-14 15:26:19)

I think the problem is that there is a file named "module - (2011-05-14 15:26:19)", and something (Windows or gem itself) is choking on it. I can probably figure out some workaround, but I thought I'd check if you intended for this file to be named that?

Always show the field, event if it is nil

Given this representer:

module BadgeRepresenter
  include Roar::Representer::JSON::HAL
  include Roar::Representer::Feature::Hypermedia

  property :name
  property :id

end

I get this, if name if set:

{
  "name": "Wise",
  "id": 1
}

or this, if name is not nil

{  
  "id": 1,
}

What I would like to get in that case is:

{  
  "id": 1,
"name": Nil,
}

Would this be possible?

Coercion feature ignores properties without :type

Just discovered something weird. The representer skips properties that weren't defined with the :type option while using the Roar::Representer::Feature::Coercion module. When trying to do a .from_json definition, an exception is raised. Also, when calling the to_hash method, only properties with :type are returned.

gem "roar"
require "roar"
require "roar/representer/json"
require "roar/representer/feature/coercion"

class Profile
  module Representer
    include Roar::Representer::JSON
    include Roar::Representer::Feature::Coercion

    property :id
    property :name
    property :email
    property :created_at, :type => Time
  end
end

profile = Profile.extend(Profile::Representer)
attrs   = {
  :id         => 1234,
  :name       => "John Doe",
  :email      => "[email protected]",
  :created_at => "Dec 14, 2012"
}

profile.from_json(JSON.dump(attrs))
#=> representable-1.2.8/lib/representable.rb:105:in `uncompile_fragment': undefined method `id=' for Profile:Class (NoMethodError)

p profile.to_hash
#=> {:created_at=>nil}

Feature request: camelcase field naming

I'm working on a JSON HAL API and using roar for it, which is great btw.

One thing on my wishlist before I hit production is camel-casing the JSON response properties.

I thought I would raise a ticket to discuss possibilities of implementing this. If you can point me to the right spot I should be able to help make the changes.

Certain rel values cause an exception when defining a link

I'm not sure which are all the character that cause problems, but so far I've seen it happen with :. Basically, when defining a link like so:

link 'namespaced:relation'
  relation_url
end

It causes the following stack trace:

NoMethodError (undefined method `merge!' for "namespaced:relation":String):

roar (0.11.4) lib/roar/representer/feature/hypermedia.rb:63:in `block in prepare_links!'
roar (0.11.4) lib/roar/representer/feature/hypermedia.rb:60:in `each'
roar (0.11.4) lib/roar/representer/feature/hypermedia.rb:60:in `prepare_links!'
roar (0.11.4) lib/roar/representer/feature/hypermedia.rb:43:in `before_serialize'
roar (0.11.4) lib/roar/representer/json.rb:20:in `to_hash'
representable (1.2.9) lib/representable/json.rb:35:in `to_json'

Currently the workaround is to define the link as follows:

link rel: 'namespaced:relation'
  relation_url
end

ArgumentError: wrong number of arguments(1 for 0)

56 tests, 41 assertions, 0 failures, 23 errors, 0 skips

/usr/share/ruby-rvm/gems/ruby-1.9.3-p0/gems/representable-1.1.1/lib/representable.rb:128:in `hash': wrong number of arguments (0 for 1) (ArgumentError)

JSON representations do no include object type

When I:

class Widget
  attr_accessor :id, :name
end

module WidgetRepresenter
  include Roar::Representer::JSON

  property :id
  property :name
end

w = Widget.new
w.id = 1
w.name = 'Thing'
w.extend( WidgetRepresenter )
w.to_json

I am getting:

{ 'id': 1, 'name': 'Thing' }

I would expect (from your README) to see something like (this is unescaped, of course):

{ 'widget': { 'id': 1, 'name': 'Thing' } }

XML Collections do not display links

With the following two presenters:

module XML::SegmentTypesRepresenter
  include Roar::Representer::XML
  include Roar::Representer::Feature::Hypermedia

  collection :items, class: SegmentType, extend: ::XML::SegmentTypeRepresenter
  def items
    self
  end

  link :self do
    api_segment_types_url(format: :xml)
  end
end


module XML::SegmentTypeRepresenter
  include Roar::Representer::XML
  include Roar::Representer::Feature::Hypermedia

  property :name

  link :self do
    api_segments_url(self.name, format: :xml)
  end
end

I use the SegmentTypesRepresenter with an array. That array renders the details from SegmentTypeRepresenter. The name property appears, but the api_segments_url does not.

When i view an API url that outputs the segment type directly, rather than in a collection, the self link is there.

I render it in the controller via:

respond_with Segment.types, represent_with: XML::SegmentTypesRepresenter

Using

format.xml { render xml: Segment.types.extend(XML::SegmentTypesRepresenter).to_xml }

Makes the outcome no different.

Note that the SegmentType is a simple ruby object that contains the slug for the particular segments. The Segment model is used when representing with the SegmentTypeRepresenter in the controller, like so

segments = Segment.where(something: true)
format.xml { render xml: segments.extend(XML::SegmentsRepresenter).to_xml(type_slug: params[:type_slug]) }

Not able to add custom fields when using Representable::JSON::Collection

Not able to add custom fields when using Lonely collections:

module ApiRepresenter
  include Roar::Representer::JSON

  property :name
end

module ApisRepresenter
  include Representable::JSON::Collection

  self.representation_wrap= :items
  items extend: ApiRepresenter

  property :foo
  def foo
   "bar"
   end
end

class Api < OpenStruct; end;

f = Api.new(:name => "Foo")
b = Api.new(:name => "Bar")
[f, b].extend(ApisRepresenter).to_json
 => "{\"items\":[{\"name\":\"Foo\"},{\"name\":\"Bar\"}]}"

Is there another way to add "item_count" representing the size of the collection?

thanks!

Client doesn't suppress nested links

When including Roar::Feature::Client, the before_serialize method is overridden so link rendering is suppressed. This doesn't work for nested objects that contain links themselves.

Can I sort/control the order of HAL JSON keys output?

The HAL/JSON output is:

"{\"id\":1,\"name\":\"Centipede\",\"sub_type\":\"100 wheeler\",\"manufacturer\":\"Mahindra\",\"model\":\"Bolero\",\"year\":\"1990\",\"vin\":\"12345678\",\"license\":\"WA\",\"
_links\":{\"self\":{\"href\":\"http://127.0.0.1:3000/api/machines/1\"}}}"

But how can I place the _links key before id?

XML representation omits XML declaration

After searching representable and roar, I can find no option to include the XML declaration line in the representation. Is there a way to accomplish this or is it an oversight?

HAL embedded nesting depth / zoom?

I've made some real progress implementing a HAL api using roar and rails, but I have a problem I haven't been able to figure out yet.

I have some property/collections that are embedded, which works great, however, I also want to be able to NOT embed them when the resource is itself is embedded.

If you retrieve a story, it comes with with an embedded account, and that's good for getting that one single story.

But, if I retrieve a list of stories, then I do not want each one to have the account embedded, only a link to the account in each.

I think of this as a kind of embed depth - I want to embed one level down, but I don't want an embedded resource to have any embeds of its own. In a perfect world, you could control this depth.

What I can't figure out is how to know the current depth in the context of deciding to included an embedded resource. I was thinking if I did, I could use it in an overridden skip_property?, since what I am trying to do is skip it based on embed depth:

https://github.com/apotonick/representable/blob/master/lib/representable/mapper.rb#L44

Any ideas on how to figure out depth, or how I could add tracking this depth?

Hypermedia feature slow due to OpenStruct

I've been having issues with hypermedia performance when returning a large collection that has hypermedia in each item. Running ruby prof, I found most time is spent in hypermedia link generation, specifically the OpenStruct creations. Doing some research, it looks like this is a know issue:
http://stackoverflow.com/questions/1177594/ruby-struct-vs-openstruct

It can be 100x slower than just using hashes and are memory hogs. Trying to think of a good way to replace this and keep hypermedia...

Short term hack would be to do inline collection, manually define a getter method for each link, and mark each read-only. There has to be a better way though.

Lacking ability to pass in base URL

I am having all sorts of trouble trying to pass in the base URL. I'm not using rails, so url helpers are not an option. Ideally, I would extract the host name from the request, and pass it in to the parent decorator, which would then propagate it down to each child decorator. My terrible hack so far to get around this is to do a substitution of a hardcoded BASE_URL_PLACEHOLDER

class BaseDecorator < Roar::Decorator
        include Roar::Representer::JSON::HAL
        include Roar::Representer::JSON::HAL::Links
        include PactBroker::Api::PactBrokerUrls

        def initialize represented, base_url = nil
          super(represented)
          @base_url = base_url
        end

        def to_json
          json = super()
          if @base_url
            json.gsub(BASE_URL_PLACEHOLDER, @base_url)
          else
            json
          end
        end

end

Have you had any thoughts about this? Having delved into the binding.rb class, I wondered if this comment was a reference to achieving something similar?

    private
      def representer_module_for(object, *args)
        call_proc_for(representer_module, object)   # TODO: how to pass additional data to the computing block?`
      end
    end

Admit null values in representations

If properties are null, they should still appear in the generated JSON.

require 'roar/representer'
require 'roar/representer/json'

module ProfileRepresenter
   include Roar::Representer::JSON

  property :name
  property :description
  property :gender
end

If description and gender are null, it should produce:

 profile":{
   "name":"John Doe", 
   "description":null,
   "gender":null
 } 

and not

profile":{
  "name":"John Doe"
} 

The client depends on the predefined representation format.

#links - representing an array of one link as just the link

This is a nice to have, as the implementation is HAL compliant at the moment.

For an array of links, the number of links may vary based on the request or the state of the resource, and ideally if only one link is necessary it would be rendered as just the link and not an array of one link.

Explained in code:

links :thumbnail do
  links = []

  links << { href: small_thumbnail_url, name: "small" }
  links << { href: large_thumbnail_url, name: "large" } if large_thumbnail

  links
end

Which right now gets represented as either:

One link:

"thumbnail": [
  {
    "href": "thumbnail/small", "name": "small"
  }
]

Two links:

"thumbnail": [
  {
    "href": "thumbnail/small", "name": "small"
  },
  {
    "href": "thumbnail/large", "name": "large"
  }
]

Ideally the single link should just be represented as:

"thumbnail": {
  "href": "thumbnail/small", "name": "small"
}

In particular I'd like to see this because otherwise clients can create false expectations about how multi-link rels work, but it's very low priority to be honest.

Upgrade representable dependecy to ~> 1.7.0

Hi!

I'd like Roar to use some of the features that Representable added in 1.7.x described here
http://nicksda.apotomo.de/2013/08/representable-1-7-is-out-with-syncing-support-inline-representers-and-more/

I did a checkout of Roar and ran the tests against Representable 1.7.1 and everything ran fine:

Finished in 0.233152s, 553.2871 runs/s, 707.6928 assertions/s.

129 runs, 165 assertions, 0 failures, 0 errors, 1 skips

You have skipped tests. Run with --verbose for details.

Can we bump the dependency?
Thanks!

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.