lightrail 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/lightrail +1 -0
- data/lib/lightrail/action_controller.rb +3 -0
- data/lib/lightrail/action_controller/haltable.rb +26 -0
- data/lib/lightrail/action_controller/metal.rb +42 -0
- data/lib/lightrail/action_controller/param.rb +12 -0
- data/lib/lightrail/core_ext/regexp.rb +7 -0
- data/lib/lightrail/encryptor.rb +62 -0
- data/lib/lightrail/models/errors.rb +21 -0
- data/lib/lightrail/railtie.rb +34 -0
- data/lib/lightrail/version.rb +3 -0
- data/lib/lightrail/wrapper.rb +65 -0
- data/lib/lightrail/wrapper/active_record.rb +15 -0
- data/lib/lightrail/wrapper/associations.rb +49 -0
- data/lib/lightrail/wrapper/controller.rb +55 -0
- data/lib/lightrail/wrapper/model.rb +114 -0
- metadata +78 -0
data/bin/lightrail
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "rails/cli"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Lightrail
|
2
|
+
module ActionController
|
3
|
+
module Haltable
|
4
|
+
def process_action(*)
|
5
|
+
opts = catch :halt do
|
6
|
+
return super
|
7
|
+
end
|
8
|
+
|
9
|
+
if opts
|
10
|
+
render opts
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def halt(opts = nil)
|
15
|
+
throw :halt, opts
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ActionController::Renderers.add :errors do |errors, opts|
|
22
|
+
json = { :errors => errors }.to_json(opts)
|
23
|
+
self.status = 422 if status == 200
|
24
|
+
self.content_type ||= Mime::JSON
|
25
|
+
self.response_body = json
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'lightrail/action_controller/haltable'
|
2
|
+
require 'lightrail/action_controller/param'
|
3
|
+
|
4
|
+
module Lightrail
|
5
|
+
module ActionController
|
6
|
+
# A configured ActionController::Metal that includes
|
7
|
+
# only the basic functionality.
|
8
|
+
class Metal < ::ActionController::Metal
|
9
|
+
abstract!
|
10
|
+
|
11
|
+
include ::ActionController::UrlFor
|
12
|
+
include ::ActionController::Head
|
13
|
+
include ::ActionController::Redirecting
|
14
|
+
include ::ActionController::Rendering
|
15
|
+
include ::ActionController::Renderers::All
|
16
|
+
include ::ActionController::ConditionalGet
|
17
|
+
include ::ActionController::RackDelegation
|
18
|
+
include ::ActionController::Caching
|
19
|
+
include ::ActionController::MimeResponds
|
20
|
+
include ::ActionController::ImplicitRender
|
21
|
+
|
22
|
+
include ::ActionController::ForceSSL if defined?(::ActionController::ForceSSL)
|
23
|
+
include ::ActionController::HttpAuthentication::Basic::ControllerMethods
|
24
|
+
include ::ActionController::HttpAuthentication::Digest::ControllerMethods
|
25
|
+
include ::ActionController::HttpAuthentication::Token::ControllerMethods
|
26
|
+
|
27
|
+
# Before callbacks should also be executed the earliest as possible, so
|
28
|
+
# also include them at the bottom.
|
29
|
+
include ::AbstractController::Callbacks
|
30
|
+
|
31
|
+
# The same with rescue, append it at the end to wrap as much as possible.
|
32
|
+
include ::ActionController::Rescue
|
33
|
+
|
34
|
+
# Add instrumentations hooks at the bottom, to ensure they instrument
|
35
|
+
# all the methods properly.
|
36
|
+
include ::ActionController::Instrumentation
|
37
|
+
|
38
|
+
include Haltable
|
39
|
+
include Param
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Lightrail
|
4
|
+
class Encryptor
|
5
|
+
class InvalidMessage < StandardError; end
|
6
|
+
OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
|
7
|
+
|
8
|
+
attr_accessor :addon_secret
|
9
|
+
|
10
|
+
def initialize(secret)
|
11
|
+
@addon_secret = secret
|
12
|
+
end
|
13
|
+
|
14
|
+
def encrypt(msg, initvec = nil)
|
15
|
+
msg = msg.to_s
|
16
|
+
cipher = new_cipher
|
17
|
+
initvec = decode64(initvec) if initvec
|
18
|
+
initvec ||= cipher.random_iv
|
19
|
+
|
20
|
+
cipher.encrypt
|
21
|
+
cipher.key = decode64(addon_secret)
|
22
|
+
cipher.iv = initvec
|
23
|
+
|
24
|
+
encrypted = cipher.update(msg)
|
25
|
+
encrypted << cipher.final
|
26
|
+
|
27
|
+
[ encrypted, initvec ].
|
28
|
+
map { |bytes| encode64(bytes) }.
|
29
|
+
join("--")
|
30
|
+
end
|
31
|
+
|
32
|
+
def decrypt(encrypted_message)
|
33
|
+
cipher = new_cipher
|
34
|
+
encrypted_data, iv = encrypted_message.split("--").map { |v| decode64(v) }
|
35
|
+
|
36
|
+
cipher.decrypt
|
37
|
+
cipher.key = decode64(addon_secret)
|
38
|
+
cipher.iv = iv
|
39
|
+
|
40
|
+
decrypted_data = cipher.update(encrypted_data)
|
41
|
+
decrypted_data << cipher.final
|
42
|
+
|
43
|
+
decrypted_data
|
44
|
+
rescue OpenSSLCipherError, TypeError
|
45
|
+
raise InvalidMessage
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def decode64(s)
|
51
|
+
ActiveSupport::Base64.decode64(s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def encode64(s)
|
55
|
+
ActiveSupport::Base64.encode64s(s)
|
56
|
+
end
|
57
|
+
|
58
|
+
def new_cipher
|
59
|
+
OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_model/errors'
|
2
|
+
|
3
|
+
module Lightrail
|
4
|
+
module Models
|
5
|
+
module Errors
|
6
|
+
def errors
|
7
|
+
@errors ||= Lightrail::Models::Errors::Errors.new(self)
|
8
|
+
end
|
9
|
+
|
10
|
+
class Errors < ::ActiveModel::Errors
|
11
|
+
# Only show one message per error key.
|
12
|
+
def as_json(options=nil)
|
13
|
+
hash = ActiveSupport::OrderedHash.new
|
14
|
+
target = @messages || self
|
15
|
+
target.each_key { |k| hash[k] = target[k][0] }
|
16
|
+
hash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "lightrail/action_controller"
|
2
|
+
require "rails/railtie"
|
3
|
+
|
4
|
+
module Lightrail
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
class Wrapper
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def remove_session_middlewares!
|
12
|
+
@config.app_middleware.delete "ActionDispatch::Cookies"
|
13
|
+
@config.app_middleware.delete "ActionDispatch::Session::CookieStore"
|
14
|
+
@config.app_middleware.delete "ActionDispatch::Flash"
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_browser_middlewares!
|
18
|
+
@config.app_middleware.delete "ActionDispatch::BestStandardsSupport"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
config.lightrail = Wrapper.new(config)
|
23
|
+
|
24
|
+
initializer "lightrail.action_controller.set_config" do |app|
|
25
|
+
Lightrail::ActionController::Metal.class_eval do
|
26
|
+
include app.routes.url_helpers
|
27
|
+
self.logger ||= Rails.logger
|
28
|
+
self.cache_store ||= RAILS_CACHE
|
29
|
+
prepend_view_path "app/views"
|
30
|
+
app.config.action_controller.each { |k,v| send("#{k}=", v) rescue nil }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Lightrail
|
2
|
+
module Wrapper
|
3
|
+
class Result < Hash
|
4
|
+
attr_reader :associations
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
@associations = Hash.new { |h,k| h[k] = [] }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Receives an object and returns its wrapper.
|
14
|
+
def find(model)
|
15
|
+
klass = model.is_a?(Class) ? model : model.class
|
16
|
+
ActiveSupport::Dependencies.constantize("#{klass.name}Wrapper")
|
17
|
+
end
|
18
|
+
|
19
|
+
# Receives an object, find its wrapper and render it.
|
20
|
+
# If the object is an array, loop for each element in the
|
21
|
+
# array and call +render_many+ (instead of +render+).
|
22
|
+
def render(object, scope, options={}, result={})
|
23
|
+
result = Result.new.merge!(result)
|
24
|
+
|
25
|
+
if object.blank?
|
26
|
+
handle_blank(object, result, options)
|
27
|
+
elsif object.respond_to?(:each)
|
28
|
+
object.each { |i| Lightrail::Wrapper.find(i).new(i, scope).render_many(options, result) }
|
29
|
+
else
|
30
|
+
Lightrail::Wrapper.find(object).new(object, scope).render(options, result)
|
31
|
+
end
|
32
|
+
|
33
|
+
result.associations.inject(result) do |final, (key, value)|
|
34
|
+
value.uniq!
|
35
|
+
render_association(value, scope, { :as => key }, final)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def render_association(value, scope, options, result)
|
40
|
+
if value.empty?
|
41
|
+
handle_blank(value, result, options)
|
42
|
+
result
|
43
|
+
else
|
44
|
+
wrapper = Lightrail::Wrapper.find(value.first)
|
45
|
+
wrapper.around_association(value, scope) do
|
46
|
+
value.each { |i| wrapper.new(i, scope).render_many(options, result) }
|
47
|
+
result
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def handle_blank(object, result, options)
|
55
|
+
name = options[:as] || options[:fallback]
|
56
|
+
name ||= object ? "resources" : "resource"
|
57
|
+
result[name] = object
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
require "lightrail/wrapper/model"
|
64
|
+
require "lightrail/wrapper/controller"
|
65
|
+
require "lightrail/wrapper/active_record"
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Lightrail
|
2
|
+
module Wrapper
|
3
|
+
module ActiveRecord
|
4
|
+
def slice(*attrs)
|
5
|
+
attrs.map! { |a| a.to_s }
|
6
|
+
attributes.slice(*attrs)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
ActiveSupport.on_load(:active_record) do
|
13
|
+
include Lightrail::Wrapper::ActiveRecord
|
14
|
+
end
|
15
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Lightrail
|
2
|
+
module Wrapper
|
3
|
+
module Associations
|
4
|
+
# +association+ returns the name of the association in the wrapped object.
|
5
|
+
# +key+ returns the JSON key to store the +association+ id.
|
6
|
+
# +as+ is the name of the +association+ in the flat JSON.
|
7
|
+
# +includes+ is the name expected on the include.
|
8
|
+
class AssociationConfig
|
9
|
+
attr_reader :name, :as, :key, :includes
|
10
|
+
|
11
|
+
def initialize(name, options)
|
12
|
+
@name = name.to_sym
|
13
|
+
end
|
14
|
+
|
15
|
+
# Receives a view and update it accordingly with the given object id
|
16
|
+
# and the association cardinality.
|
17
|
+
def update(view, object)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class HasOneConfig < AssociationConfig
|
23
|
+
def initialize(name, options)
|
24
|
+
super
|
25
|
+
@as = options[:as] || name.to_s.pluralize.to_sym
|
26
|
+
@key = options[:key] || :"#{name}_id"
|
27
|
+
@includes = options[:includes] || name.to_s.pluralize
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(view, object)
|
31
|
+
view[@key] = object && object.id
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class HasManyConfig < AssociationConfig
|
36
|
+
def initialize(name, options)
|
37
|
+
super
|
38
|
+
@as = options[:as] || name.to_sym
|
39
|
+
@key = options[:key] || name.to_sym
|
40
|
+
@includes = options[:includes] || name.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def update(view, object)
|
44
|
+
view[@key] = object.map(&:id)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Lightrail
|
2
|
+
module Wrapper
|
3
|
+
module Controller
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# Receives an object and render it as JSON considering its respective wrapper.
|
9
|
+
def json(object, options={})
|
10
|
+
json = Lightrail::Wrapper.render(object, wrapper_scope,
|
11
|
+
:include => wrapper_includes, :fallback => controller_name, :as => options.delete(:as))
|
12
|
+
render options.merge(:json => json)
|
13
|
+
end
|
14
|
+
|
15
|
+
def errors(object)
|
16
|
+
render :errors => { object.class.model_name.underscore => object.errors }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Receives a relation and add the appropriate includes and configuration.
|
20
|
+
# Useful when wrapping up arrays to avoid N+1 queries in the database.
|
21
|
+
def wrap_array(array)
|
22
|
+
array = array.to_a
|
23
|
+
return array if array.empty?
|
24
|
+
|
25
|
+
klass = array[0].class
|
26
|
+
valid = Lightrail::Wrapper.find(klass).valid_includes(wrapper_includes)
|
27
|
+
return array if valid.empty?
|
28
|
+
|
29
|
+
klass.send(:preload_associations, array, valid)
|
30
|
+
array
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a wrapper around the given object.
|
34
|
+
def wrapper(object, scope=wrapper_scope)
|
35
|
+
Lightrail::Wrapper.find(object).new(object, scope)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns given includes as a nested hash.
|
39
|
+
def wrapper_includes
|
40
|
+
@wrapper_includes ||= params[:include].to_s.split(",")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Pass the relevant scope to the wrapper object.
|
44
|
+
def wrapper_scope
|
45
|
+
raise NotImplementedError, "wrapper_scope needs to be implemented according to your application"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
ActiveSupport.on_load(:action_controller) do
|
52
|
+
include Lightrail::Wrapper::Controller
|
53
|
+
end
|
54
|
+
|
55
|
+
Lightrail::ActionController::Metal.send :include, Lightrail::Wrapper::Controller
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "lightrail/wrapper/associations"
|
2
|
+
|
3
|
+
module Lightrail
|
4
|
+
module Wrapper
|
5
|
+
class Model
|
6
|
+
class_attribute :associations
|
7
|
+
self.associations = []
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def inherited(base)
|
11
|
+
base.class_eval do
|
12
|
+
alias_method wrapped_class.model_name.underscore, :resource
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def around_association(collection, scope)
|
17
|
+
yield
|
18
|
+
end
|
19
|
+
|
20
|
+
# Declares that this object is associated to the given association
|
21
|
+
# with cardinality 1..1.
|
22
|
+
def has_one(*associations)
|
23
|
+
options = associations.extract_options!
|
24
|
+
self.associations += associations.map { |a| Associations::HasOneConfig.new(a, options) }
|
25
|
+
define_association_methods(associations)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Declares that this object is associated to the given association
|
29
|
+
# with cardinality 1..*.
|
30
|
+
def has_many(*associations)
|
31
|
+
options = associations.extract_options!
|
32
|
+
self.associations += associations.map { |a| Associations::HasManyConfig.new(a, options) }
|
33
|
+
define_association_methods(associations)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Based on the declared associations and the given parameters,
|
37
|
+
# generate an includes clause that can be passed down to the relation
|
38
|
+
# object to avoid doing N+1 queries.
|
39
|
+
def valid_includes(includes)
|
40
|
+
result = []
|
41
|
+
includes.each do |i|
|
42
|
+
if association = associations.find { |a| a.includes == i }
|
43
|
+
result << association.name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the original class wrapped by this wrapper.
|
50
|
+
def wrapped_class
|
51
|
+
@wrapped_class ||= name.sub(/Wrapper$/, "").constantize
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def define_association_methods(associations)
|
57
|
+
associations.each do |association|
|
58
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
59
|
+
def #{association}
|
60
|
+
@resource.#{association}
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
attr_reader :resource, :scope
|
68
|
+
|
69
|
+
def initialize(resource, scope)
|
70
|
+
@resource = resource
|
71
|
+
@scope = scope
|
72
|
+
end
|
73
|
+
|
74
|
+
def view
|
75
|
+
raise NotImplementedError, "No view implemented for #{self}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Gets the view and render the associated object.
|
79
|
+
def render(options={}, result=Result.new)
|
80
|
+
name = options[:as] || wrapped_model_name.underscore
|
81
|
+
result[name] = view = self.view
|
82
|
+
_include_associations(result, view, options[:include])
|
83
|
+
end
|
84
|
+
|
85
|
+
# Gets the view and render the associated considering it is part of a collection.
|
86
|
+
def render_many(options={}, result=Result.new)
|
87
|
+
name = options[:as] || wrapped_model_name.plural
|
88
|
+
view = self.view
|
89
|
+
array = (result[name] ||= []) << view
|
90
|
+
_include_associations(result, view, options[:include])
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def _include_associations(result, view, includes)
|
96
|
+
return result if includes.blank?
|
97
|
+
associations = result.associations
|
98
|
+
|
99
|
+
self.class.associations.each do |config|
|
100
|
+
next unless includes.include?(config.includes)
|
101
|
+
object = send(config.name)
|
102
|
+
config.update(view, object)
|
103
|
+
associations[config.as].concat Array.wrap(object)
|
104
|
+
end
|
105
|
+
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def wrapped_model_name
|
110
|
+
self.class.wrapped_class.model_name
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lightrail
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Carl Lerche
|
9
|
+
- José Valim
|
10
|
+
- Tony Arcieri
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2012-03-08 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: railties
|
18
|
+
requirement: &70316991038220 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 3.2.0
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *70316991038220
|
27
|
+
description: Lightrail slims Rails down to the bare essentials great JSON web services
|
28
|
+
crave
|
29
|
+
email:
|
30
|
+
- me@carllerche.com
|
31
|
+
- jose.valim@gmail.com
|
32
|
+
- tony.arcieri@gmail.com
|
33
|
+
executables:
|
34
|
+
- lightrail
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- lib/lightrail/action_controller/haltable.rb
|
39
|
+
- lib/lightrail/action_controller/metal.rb
|
40
|
+
- lib/lightrail/action_controller/param.rb
|
41
|
+
- lib/lightrail/action_controller.rb
|
42
|
+
- lib/lightrail/core_ext/regexp.rb
|
43
|
+
- lib/lightrail/encryptor.rb
|
44
|
+
- lib/lightrail/models/errors.rb
|
45
|
+
- lib/lightrail/railtie.rb
|
46
|
+
- lib/lightrail/version.rb
|
47
|
+
- lib/lightrail/wrapper/active_record.rb
|
48
|
+
- lib/lightrail/wrapper/associations.rb
|
49
|
+
- lib/lightrail/wrapper/controller.rb
|
50
|
+
- lib/lightrail/wrapper/model.rb
|
51
|
+
- lib/lightrail/wrapper.rb
|
52
|
+
- bin/lightrail
|
53
|
+
homepage: http://github.com/tarcieri/lightrail
|
54
|
+
licenses: []
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
requirements: []
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.8.10
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: Slim Rails stack for JSON services
|
77
|
+
test_files: []
|
78
|
+
has_rdoc:
|