Skip to content

Commit

Permalink
Inital commit
Browse files Browse the repository at this point in the history
  • Loading branch information
idyll committed Jan 12, 2013
0 parents commit 056dd9a
Show file tree
Hide file tree
Showing 18 changed files with 1,384 additions and 0 deletions.
37 changes: 37 additions & 0 deletions .gitignore
@@ -0,0 +1,37 @@
## MAC OS
.DS_Store
.com.apple.timemachine.supported

## TEXTMATE
*.tmproj
tmtags

## EMACS
*~
\#*
.\#*

## REDCAR
.redcar

## VIM
*.swp
*.swo

## RUBYMINE
.idea

## PROJECT::GENERAL
coverage
doc
pkg
.rvmrc
.bundle
.yardoc/*
dist
Gemfile.lock

## Rubinius
.rbx

## PROJECT::SPECIFIC
2 changes: 2 additions & 0 deletions .rspec
@@ -0,0 +1,2 @@
--color
--format=progress
8 changes: 8 additions & 0 deletions .travis.yml
@@ -0,0 +1,8 @@
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- jruby
- rbx
- ree

2 changes: 2 additions & 0 deletions .yardopts
@@ -0,0 +1,2 @@
--markup-provider=redcarpet
--markup=markdown
8 changes: 8 additions & 0 deletions CHANGELOG.markdown
@@ -0,0 +1,8 @@
Next Release
============
* Your contribution here.

0.1.0 (01/11/2013)
==================

* Initial Release
16 changes: 16 additions & 0 deletions Gemfile
@@ -0,0 +1,16 @@
source 'http://rubygems.org'

gemspec

group :development, :test do
gem 'pry'
gem 'guard'
gem 'guard-rspec'
gem 'guard-bundler'
gem 'rb-fsevent'
gem 'growl'
gem 'json'
gem 'rspec'
gem 'rack-test', "~> 0.6.2", :require => "rack/test"
gem 'github-markup'
end
15 changes: 15 additions & 0 deletions Guardfile
@@ -0,0 +1,15 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard 'rspec', :version => 2 do
watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^spec/support/shared_versioning_examples.rb$}) { |m| "spec/" }
watch('spec/spec_helper.rb') { "spec/" }
end


guard 'bundler' do
watch('Gemfile')
watch(/^.+\.gemspec/)
end
20 changes: 20 additions & 0 deletions LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2010 Michael Bleigh and Intridea, Inc.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
200 changes: 200 additions & 0 deletions README.markdown
@@ -0,0 +1,200 @@
# GrapeEntity

## CI
[![Build Status](https://travis-ci.org/agileanimal/grape_entity.png?branch=master)](https://travis-ci.org/agileanimal/grape_entity)

## Introduction

This gem is the Entity extracted out of [Grape](https://github.com/intridea/grape).

Grape's Entity is a great idea: a API focussed Facade that sits on top of a model object.

The intend is to allow the Entity to be used outside of Grape and provide additional exposure to parts of the entity needed to simplify testing parts of an API.

We intend to use the specs from grape to ensure we maintain compatability with Grape through our changes so that we can use the Entity to replace Grape's internal Entity.

## Project Tracking

* - Need to setup something up for this -

## Reusable Responses with Entities

Entities are a reusable means for converting Ruby objects to API responses.
Entities can be used to conditionally include fields, nest other entities, and build
ever larger responses, using inheritance.

### Defining Entities

Entities inherit from GrapeEntity::Entity, and define a simple DSL. Exposures can use
runtime options to determine which fields should be visible, these options are
available to `:if`, `:unless`, and `:proc`. The option keys `:version` and `:collection`
will always be defined. The `:version` key is defined as `api.version`. The
`:collection` key is boolean, and defined as `true` if the object presented is an
array.

* `expose SYMBOLS`
* define a list of fields which will always be exposed
* `expose SYMBOLS, HASH`
* HASH keys include `:if`, `:unless`, `:proc`, `:as`, `:using`, `:format_with`, `:documentation`
* `:if` and `:unless` accept hashes (passed during runtime) or procs (arguments are object and options)
* `expose SYMBOL, { :format_with => :formatter }`
* expose a value, formatting it first
* `:format_with` can only be applied to one exposure at a time
* `expose SYMBOL, { :as => "alias" }`
* Expose a value, changing its hash key from SYMBOL to alias
* `:as` can only be applied to one exposure at a time
* `expose SYMBOL BLOCK`
* block arguments are object and options
* expose the value returned by the block
* block can only be applied to one exposure at a time

```ruby
module API
module Entities
class Status < GrapeEntity::Entity
expose :user_name
expose :text, :documentation => { :type => "string", :desc => "Status update text." }
expose :ip, :if => { :type => :full }
expose :user_type, user_id, :if => lambda{ |status, options| status.user.public? }
expose :digest { |status, options| Digest::MD5.hexdigest(satus.txt) }
expose :replies, :using => API::Status, :as => :replies
end
end
end

module API
module Entities
class StatusDetailed < API::Entities::Status
expose :internal_id
end
end
end
```

#### Using the Exposure DSL

Grape ships with a DSL to easily define entities within the context
of an existing class:

```ruby
class Status
include GrapeEntity::Entity::DSL

entity :text, :user_id do
expose :detailed, if: :conditional
end
end
```

The above will automatically create a `Status::Entity` class and define properties on it according
to the same rules as above. If you only want to define simple exposures you don't have to supply
a block and can instead simply supply a list of comma-separated symbols.

### Using Entities

Once an entity is defined, it can be used within endpoints, by calling `present`. The `present`
method accepts two arguments, the object to be presented and the options associated with it. The
options hash must always include `:with`, which defines the entity to expose.

If the entity includes documentation it can be included in an endpoint's description.

```ruby
module API
class Statuses < GrapeEntity::API
version 'v1'

desc 'Statuses index', {
:object_fields => API::Entities::Status.documentation
}
get '/statuses' do
statuses = Status.all
type = current_user.admin? ? :full : :default
present statuses, with: API::Entities::Status, :type => type
end
end
end
```

### Entity Organization

In addition to separately organizing entities, it may be useful to put them as namespaced
classes underneath the model they represent.

```ruby
class Status
def entity
Status.new(self)
end

class Entity < GrapeEntity::Entity
expose :text, :user_id
end
end
```

If you organize your entities this way, Grape will automatically detect the `Entity` class and
use it to present your models. In this example, if you added `present User.new` to your endpoint,
Grape would automatically detect that there is a `Status::Entity` class and use that as the
representative entity. This can still be overridden by using the `:with` option or an explicit
`represents` call.

### Caveats

Entities with duplicate exposure names and conditions will silently overwrite one another.
In the following example, when `object.check` equals "foo", only `field_a` will be exposed.
However, when `object.check` equals "bar" both `field_b` and `foo` will be exposed.

```ruby
module API
module Entities
class Status < GrapeEntity::Entity
expose :field_a, :foo, :if => lambda { |object, options| object.check == "foo" }
expose :field_b, :foo, :if => lambda { |object, options| object.check == "bar" }
end
end
end
```

This can be problematic, when you have mixed collections. Using `respond_to?` is safer.

```ruby
module API
module Entities
class Status < GrapeEntity::Entity
expose :field_a, :if => lambda { |object, options| object.check == "foo" }
expose :field_b, :if => lambda { |object, options| object.check == "bar" }
expose :foo, :if => lambda { |object, options| object.respond_to?(:foo) }
end
end
end
```

## Installation

Add this line to your application's Gemfile:

gem 'grape-entity'

And then execute:

$ bundle

Or install it yourself as:

$ gem install grape-entity

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request

## License

MIT License. See LICENSE for details.

## Copyright

Copyright (c) 2010-2012 Michael Bleigh, and Intridea, Inc.
51 changes: 51 additions & 0 deletions Rakefile
@@ -0,0 +1,51 @@
require 'rubygems'
require 'bundler'
Bundler.setup :default, :test, :development

Bundler::GemHelper.install_tasks

require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
end

RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
spec.rcov = true
end

task :spec
task :default => :spec

#
# TODO: setup a place for documentation and then get this going again.
#
# begin
# require 'yard'
# DOC_FILES = ['lib/**/*.rb', 'README.markdown']
#
# YARD::Rake::YardocTask.new(:doc) do |t|
# t.files = DOC_FILES
# end
#
# namespace :doc do
# YARD::Rake::YardocTask.new(:pages) do |t|
# t.files = DOC_FILES
# t.options = ['-o', '../grape.doc']
# end
#
# namespace :pages do
# desc 'Generate and publish YARD docs to GitHub pages.'
# task :publish => ['doc:pages'] do
# Dir.chdir(File.dirname(__FILE__) + '/../grape.doc') do
# system("git add .")
# system("git add -u")
# system("git commit -m 'Generating docs for version #{version}.'")
# system("git push origin gh-pages")
# end
# end
# end
# end
# rescue LoadError
# puts "You need to install YARD."
# end

0 comments on commit 056dd9a

Please sign in to comment.