backframe 0.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.idea/vcs.xml +6 -0
- data/Gemfile +14 -0
- data/README.md +6 -0
- data/Rakefile +25 -0
- data/backframe.gemspec +22 -0
- data/lib/backframe/api/adapter.rb +49 -0
- data/lib/backframe/api/errors.rb +46 -0
- data/lib/backframe/api/headers.rb +9 -0
- data/lib/backframe/api/page.rb +177 -0
- data/lib/backframe/api.rb +35 -0
- data/lib/backframe/mime.rb +11 -0
- data/lib/backframe/resource/actions.rb +99 -0
- data/lib/backframe/resource.rb +90 -0
- data/lib/backframe/version.rb +3 -0
- data/lib/backframe.rb +10 -0
- data/spec/backframe/api_spec.rb +225 -0
- data/spec/backframe/resource_spec.rb +178 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/example.rb +20 -0
- data/spec/support/example_factory.rb +9 -0
- data/spec/support/example_serializer.rb +3 -0
- data/spec/support/schema.rb +8 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6893ca50993e2054bfa00298833e7d1d95762c6b
|
4
|
+
data.tar.gz: fcaf54876f10f59cc5e9a8c4306caccfc8e9b5ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1b3d72462843957ff8c6c5b8380f7e29d0b7133d99e79bc737527f56e9dbe985d42818c3dfd13fb2304a3bf028da1085ad7ecfe2dcab9fb33259b5e60bcd6e09
|
7
|
+
data.tar.gz: 68ebc292ef2ea169459348d7c31955976bcdd8432afb30b066ee7d88da46ca9270cb037bfa816ad4997ce7287b52e096772230ee61357bbcb5b2dd752e541743
|
data/.gitignore
ADDED
data/.idea/vcs.xml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'bundler/version'
|
4
|
+
require './lib/backframe'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
7
|
+
t.rspec_opts = ['--color']
|
8
|
+
end
|
9
|
+
|
10
|
+
task default: :spec
|
11
|
+
|
12
|
+
desc "Build the gem"
|
13
|
+
task :build do
|
14
|
+
system "gem build backframe.gemspec"
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "install the gem"
|
18
|
+
task :install do
|
19
|
+
system "gem install backframe-#{Backframe::VERSION}.gem"
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Build and release the gem"
|
23
|
+
task :release => :build do
|
24
|
+
system "gem push backframe-#{Backframe::VERSION}.gem"
|
25
|
+
end
|
data/backframe.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
|
4
|
+
require 'backframe/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'backframe'
|
8
|
+
gem.email = 'hello@thinktopography.com'
|
9
|
+
gem.description = 'Rails bindings for Reframe'
|
10
|
+
gem.version = Backframe::VERSION
|
11
|
+
gem.summary = 'Backframe'
|
12
|
+
gem.authors = ['Greg Kops', 'Scott Nelson']
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_runtime_dependency 'activesupport', '~> 4.0'
|
20
|
+
gem.add_runtime_dependency 'active_model_serializers', '>= 0.10.0.rc4'
|
21
|
+
gem.add_runtime_dependency 'kaminari', '~> 0.16'
|
22
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Backframe
|
2
|
+
module API
|
3
|
+
class Adapter < ActiveModel::Serializer::Adapter::Attributes
|
4
|
+
attr_reader :fields, :links
|
5
|
+
|
6
|
+
def initialize(serializer, options = {})
|
7
|
+
super
|
8
|
+
@fields = options[:fields]
|
9
|
+
@links = options[:links]
|
10
|
+
end
|
11
|
+
|
12
|
+
def serializable_hash(options = nil)
|
13
|
+
if paginated?
|
14
|
+
with_pagination_metadata(super)
|
15
|
+
else
|
16
|
+
select_fields(super)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def paginated?
|
21
|
+
serializer.respond_to?(:paginated?) && serializer.paginated?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def with_pagination_metadata(records)
|
27
|
+
{
|
28
|
+
records: records.map(&method(:select_fields)),
|
29
|
+
total_records: paginated.total_count,
|
30
|
+
total_pages: paginated.total_pages,
|
31
|
+
current_page: paginated.current_page,
|
32
|
+
links: links
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def paginated
|
37
|
+
serializer.object
|
38
|
+
end
|
39
|
+
|
40
|
+
def select_fields(object)
|
41
|
+
if fields.present?
|
42
|
+
object.select { |key, val| fields.include?(key) }
|
43
|
+
else
|
44
|
+
object
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Backframe
|
2
|
+
module API
|
3
|
+
module Errors
|
4
|
+
def unauthenticated_request
|
5
|
+
error_response(:unauthenticated, 401)
|
6
|
+
end
|
7
|
+
|
8
|
+
def unauthorized_request
|
9
|
+
error_response(:unauthorized, 403)
|
10
|
+
end
|
11
|
+
|
12
|
+
def resource_not_found
|
13
|
+
error_response(:resource_not_found, 404)
|
14
|
+
end
|
15
|
+
|
16
|
+
def route_not_found
|
17
|
+
error_response(:route_not_found, 404)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def error_response(code, status = 500)
|
23
|
+
# TODO: Create generator for translations
|
24
|
+
|
25
|
+
result = {
|
26
|
+
error: {
|
27
|
+
message: I18n.t("backframe.api.#{code}", method: request.method),
|
28
|
+
status: status
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
render json: result, status: status
|
33
|
+
end
|
34
|
+
|
35
|
+
def resource_error_response(resource, status = 500)
|
36
|
+
result = {
|
37
|
+
message: I18n.t('backframe.api.resource_error', request: request),
|
38
|
+
errors: resource.errors,
|
39
|
+
status: status
|
40
|
+
}
|
41
|
+
|
42
|
+
render json: result, status: status
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Backframe
|
2
|
+
module API
|
3
|
+
module Page
|
4
|
+
DEFAULT_PAGE = 1
|
5
|
+
DEFAULT_PER_PAGE = 100
|
6
|
+
|
7
|
+
def page(collection, serializer = nil)
|
8
|
+
classname = collection.base_class.name+'Serializer'
|
9
|
+
serializer ||= classname.constantize
|
10
|
+
args = params.except(*request.path_parameters.keys)
|
11
|
+
filters = args.except([:sort,:page,:per_page,:fields,:exclude_ids])
|
12
|
+
model = (collection.respond_to?(:klass)) ? collection.klass.name.constantize : collection
|
13
|
+
collection = model.filter(collection, filters) if collection.respond_to?(:filter)
|
14
|
+
|
15
|
+
if args.key?(:sort)
|
16
|
+
args[:sort].split(',').each do |sort|
|
17
|
+
key = (sort[0] == '-') ? sort[1..-1] : sort
|
18
|
+
order = (sort[0] == '-') ? 'desc' : 'asc'
|
19
|
+
collection = model.sort(collection, key, order)
|
20
|
+
end
|
21
|
+
else
|
22
|
+
collection = model.sort(collection)
|
23
|
+
end
|
24
|
+
|
25
|
+
if args.key?(:exclude_ids)
|
26
|
+
ids = args[:exclude_ids].split(',')
|
27
|
+
collection = collection.where('id NOT IN (?)', ids)
|
28
|
+
end
|
29
|
+
|
30
|
+
if args.key?(:all)
|
31
|
+
args[:page] = 1
|
32
|
+
args[:per_page] = 10000
|
33
|
+
end
|
34
|
+
|
35
|
+
args[:page] ||= DEFAULT_PAGE
|
36
|
+
args[:per_page] ||= DEFAULT_PER_PAGE
|
37
|
+
|
38
|
+
|
39
|
+
collection = collection.page(args[:page]).per(args[:per_page])
|
40
|
+
fields = (args.key?(:fields)) ? args[:fields].split(',').map(&:to_sym) : serializer._attributes
|
41
|
+
|
42
|
+
respond_to do |format|
|
43
|
+
format.json {
|
44
|
+
render json: collection,
|
45
|
+
content_type: 'application/json',
|
46
|
+
adapter: Backframe::API::Adapter,
|
47
|
+
fields: fields,
|
48
|
+
links: pagination_links(collection, args[:per_page], args[:page])
|
49
|
+
}
|
50
|
+
format.csv {
|
51
|
+
content_type = (args.key?(:plain)) ? 'text/plain' : 'text/csv'
|
52
|
+
render :text => collection_to_csv(collection, serializer, fields, ","), :content_type => content_type, :status => 200
|
53
|
+
}
|
54
|
+
format.tsv {
|
55
|
+
content_type = (args.key?(:plain)) ? 'text/plain' : 'text/tab-separated-values'
|
56
|
+
render :text => collection_to_csv(collection, serializer, fields, "\t"), :content_type => content_type, :status => 200
|
57
|
+
}
|
58
|
+
format.xls {
|
59
|
+
content_type = (args.key?(:plain)) ? 'text/xml' : 'application/xls'
|
60
|
+
render :text => collection_to_xls(collection, serializer, fields), :content_type => content_type, :status => 200
|
61
|
+
}
|
62
|
+
format.xlsx {
|
63
|
+
content_type = (args.key?(:plain)) ? 'text/xml' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
64
|
+
render :text => collection_to_xls(collection, serializer, fields), :content_type => content_type, :status => 200
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def collection_to_csv(collection, serializer, fields, separator)
|
70
|
+
rows = []
|
71
|
+
line = []
|
72
|
+
headers = []
|
73
|
+
collection.each do |record|
|
74
|
+
headers = []
|
75
|
+
line = []
|
76
|
+
serialized = OpenStruct.new(ActiveModel::SerializableResource.new(record).as_json)
|
77
|
+
fields.each do |fullkey|
|
78
|
+
value = serialized
|
79
|
+
fullkey.to_s.split(".").each do |key|
|
80
|
+
if value.respond_to?(key)
|
81
|
+
value = value.send(key)
|
82
|
+
if value.is_a?(Time)
|
83
|
+
value = value.strftime("%F %T")
|
84
|
+
elsif value.is_a?(Date)
|
85
|
+
value = value.strftime("%F")
|
86
|
+
elsif value.is_a?(Hash)
|
87
|
+
value = OpenStruct.new(value)
|
88
|
+
else
|
89
|
+
value = value.to_s
|
90
|
+
end
|
91
|
+
else
|
92
|
+
value = nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
if !value.is_a?(OpenStruct)
|
96
|
+
headers << fullkey
|
97
|
+
line << value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
rows << line.join(separator)
|
101
|
+
end
|
102
|
+
rows.unshift(headers.join(separator))
|
103
|
+
rows.join("\n")
|
104
|
+
end
|
105
|
+
|
106
|
+
def collection_to_xls(collection, serializer, fields)
|
107
|
+
rows = []
|
108
|
+
rows << '<?xml version="1.0"?>'
|
109
|
+
rows << '<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40">'
|
110
|
+
rows << '<Worksheet ss:Name="Sheet1">'
|
111
|
+
rows << '<Table>'
|
112
|
+
line = []
|
113
|
+
fields.each do |key|
|
114
|
+
line << '<Cell><Data ss:Type="String">'+key+'</Data></Cell>'
|
115
|
+
end
|
116
|
+
rows << "<Row>"+line.join+"</Row>"
|
117
|
+
collection.all.each do |record|
|
118
|
+
line = []
|
119
|
+
serialized = serializer.new(record)
|
120
|
+
fields.each do |fullkey|
|
121
|
+
value = serialized
|
122
|
+
fullkey.to_s.split(".").each do |key|
|
123
|
+
if value.respond_to?(key)
|
124
|
+
value = value.send(key)
|
125
|
+
if value.is_a?(Hash)
|
126
|
+
value = OpenStruct.new(value)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
value = nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
if value.is_a?(Numeric)
|
133
|
+
line << '<Cell><Data ss:Type="Number">'+value+'</Data></Cell>'
|
134
|
+
elsif value.is_a?(Time)
|
135
|
+
line << '<Cell><Data ss:Type="String">'+value.strftime("%F %T")+'</Data></Cell>'
|
136
|
+
elsif value.is_a?(Date)
|
137
|
+
line << '<Cell><Data ss:Type="String">'+value.strftime("%F")+'</Data></Cell>'
|
138
|
+
elsif value.is_a?(String)
|
139
|
+
line << '<Cell><Data ss:Type="String">'+value+'</Data></Cell>'
|
140
|
+
else
|
141
|
+
line << '<Cell><Data ss:Type="String"></Data></Cell>'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
rows << "<Row>"+line.join+"</Row>"
|
145
|
+
end
|
146
|
+
rows << '</Table>'
|
147
|
+
rows << '</Worksheet>'
|
148
|
+
rows << '</Workbook>'
|
149
|
+
rows.join("\n")
|
150
|
+
end
|
151
|
+
|
152
|
+
def pagination_links(collection, per_page, page)
|
153
|
+
return {} if collection.total_count.zero?
|
154
|
+
links = {}
|
155
|
+
links[:self] = pagination_link(per_page, page)
|
156
|
+
if collection.next_page.present?
|
157
|
+
links[:next] = pagination_link(per_page, collection.next_page)
|
158
|
+
end
|
159
|
+
if page.to_i < collection.total_pages
|
160
|
+
links[:last] = pagination_link(per_page, collection.total_pages)
|
161
|
+
end
|
162
|
+
if page.to_i > 1
|
163
|
+
links[:first] = pagination_link(per_page, 1)
|
164
|
+
end
|
165
|
+
if collection.prev_page.present?
|
166
|
+
links[:prev] = pagination_link(per_page, collection.prev_page)
|
167
|
+
end
|
168
|
+
links
|
169
|
+
end
|
170
|
+
|
171
|
+
def pagination_link(per_page, page)
|
172
|
+
args = (per_page.to_i != DEFAULT_PER_PAGE) ? "per_page="+per_page.to_s+"&page="+page.to_s : "page="+page.to_s
|
173
|
+
base_api_url+request.path+"?"+args
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'active_model_serializers'
|
4
|
+
|
5
|
+
require 'backframe/api/adapter'
|
6
|
+
require 'backframe/api/errors'
|
7
|
+
require 'backframe/api/headers'
|
8
|
+
require 'backframe/api/page'
|
9
|
+
|
10
|
+
module Backframe
|
11
|
+
module API
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def acts_as_api
|
16
|
+
include Errors
|
17
|
+
include Headers
|
18
|
+
include Page
|
19
|
+
|
20
|
+
before_action :set_expiration_header
|
21
|
+
|
22
|
+
rescue_from Exceptions::Unauthenticated, :with => :unauthenticated_request
|
23
|
+
rescue_from Exceptions::Unauthorized, :with => :unauthorized_request
|
24
|
+
rescue_from 'ActiveRecord::RecordNotFound', :with => :resource_not_found
|
25
|
+
rescue_from 'ActionController::RoutingError', :with => :route_not_found
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
included do
|
30
|
+
def base_api_url
|
31
|
+
raise 'must be overridden'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Backframe
|
2
|
+
module Mime
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def register_types
|
6
|
+
::Mime::Type.register('text/tab-separated-values', :tsv)
|
7
|
+
::Mime::Type.register('application/vnd.ms-excel', :xls)
|
8
|
+
::Mime::Type.register('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :xlsx)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Backframe
|
2
|
+
module Resource
|
3
|
+
module Actions
|
4
|
+
module Index
|
5
|
+
def index
|
6
|
+
page(resource.includes(self.class.resource_opts[:include]), nil)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Create
|
11
|
+
def create
|
12
|
+
@item = resource.new(allowed_params)
|
13
|
+
if @item.save
|
14
|
+
if @item.respond_to?(:activities)
|
15
|
+
Activity.create!(subject: current_user, text: 'created {object1}', object1: @item)
|
16
|
+
end
|
17
|
+
|
18
|
+
render json: @item, status: 201, adapter: Backframe::API::Adapter
|
19
|
+
else
|
20
|
+
resource_error_response(@item, 422)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Show
|
26
|
+
def show
|
27
|
+
render json: @item, adapter: Backframe::API::Adapter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Edit
|
32
|
+
def edit
|
33
|
+
json = {}
|
34
|
+
self.class.resource_opts[:allowed].each do |attribute|
|
35
|
+
if(attribute.is_a?(Hash))
|
36
|
+
attribute.each do |key,val|
|
37
|
+
json[key] = @item.send(key)
|
38
|
+
end
|
39
|
+
else
|
40
|
+
json[attribute] = @item.send(attribute)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
render json: json
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module Update
|
48
|
+
def update
|
49
|
+
if @item.update_attributes(allowed_params)
|
50
|
+
if @item.respond_to?(:activities)
|
51
|
+
Activity.create!(subject: current_user, text: 'updated {object1}', object1: @item)
|
52
|
+
end
|
53
|
+
|
54
|
+
render json: @item, adapter: Backframe::API::Adapter
|
55
|
+
else
|
56
|
+
resource_error_response(@item, 422)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module UpdateAll
|
62
|
+
def update_all
|
63
|
+
records = []
|
64
|
+
|
65
|
+
ActiveRecord::Base.transaction do
|
66
|
+
records = update_all_params.map(&method(:update_record))
|
67
|
+
end
|
68
|
+
|
69
|
+
render json: records, adapter: Backframe::API::Adapter
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def update_record(attrs)
|
75
|
+
attrs = attrs.with_indifferent_access
|
76
|
+
record = resource.find(attrs[:id])
|
77
|
+
record.attributes = attrs
|
78
|
+
record.save!
|
79
|
+
record
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_all_params
|
83
|
+
permitted = self.class.resource_opts[:allowed] << :id
|
84
|
+
params.permit(records: permitted)[:records] || []
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module Destroy
|
89
|
+
def destroy
|
90
|
+
if @item.destroy
|
91
|
+
render nothing: true, status: 204
|
92
|
+
else
|
93
|
+
resource_error_response(@item, 422)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
require 'backframe/resource/actions'
|
4
|
+
|
5
|
+
module Backframe
|
6
|
+
module Resource
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def acts_as_resource(resource, *args)
|
11
|
+
arguments = (args.present?) ? args[0] : {}
|
12
|
+
|
13
|
+
@resource = resource
|
14
|
+
@resource_opts = arguments
|
15
|
+
|
16
|
+
include Helpers
|
17
|
+
|
18
|
+
member_methods = [:show,:edit,:update,:destroy]
|
19
|
+
if arguments.key?(:has_many)
|
20
|
+
member_methods.concat(arguments[:has_many])
|
21
|
+
end
|
22
|
+
|
23
|
+
if arguments.key?(:only)
|
24
|
+
member_methods = (member_methods.to_set & arguments[:only].to_set).to_a
|
25
|
+
end
|
26
|
+
|
27
|
+
if arguments.key?(:except)
|
28
|
+
member_methods = (member_methods.to_set - arguments[:except].to_set).to_a
|
29
|
+
end
|
30
|
+
|
31
|
+
before_action :load_item, only: member_methods
|
32
|
+
|
33
|
+
include Actions::Index if include_method?(arguments, :index)
|
34
|
+
include Actions::Create if include_method?(arguments, :create)
|
35
|
+
include Actions::Show if include_method?(arguments, :show)
|
36
|
+
include Actions::Edit if include_method?(arguments, :edit)
|
37
|
+
include Actions::Update if include_method?(arguments, :update)
|
38
|
+
include Actions::UpdateAll if include_method?(arguments, :update)
|
39
|
+
include Actions::Destroy if include_method?(arguments, :destroy)
|
40
|
+
|
41
|
+
if arguments.key?(:has_many)
|
42
|
+
arguments[:has_many].each do |association|
|
43
|
+
class_eval <<-EOV
|
44
|
+
def #{association}
|
45
|
+
page(@item.#{association})
|
46
|
+
end
|
47
|
+
EOV
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def resource; @resource; end
|
53
|
+
def resource_opts; @resource_opts; end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def include_method?(arguments, method)
|
58
|
+
(arguments[:only].present? && arguments[:only].include?(method)) ||
|
59
|
+
(arguments[:except].present? && !arguments[:except].include?(method)) ||
|
60
|
+
(arguments[:only].nil? && arguments[:except].nil?)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module Helpers
|
66
|
+
def resource
|
67
|
+
self.class.resource.constantize
|
68
|
+
rescue NameError
|
69
|
+
instance_eval self.class.resource
|
70
|
+
end
|
71
|
+
|
72
|
+
def load_item
|
73
|
+
@item = resource.find(params[:id])
|
74
|
+
end
|
75
|
+
|
76
|
+
def allowed_params
|
77
|
+
allowed = params.permit(self.class.resource_opts[:allowed])
|
78
|
+
self.class.resource_opts[:allowed].each do |attribute|
|
79
|
+
if attribute.is_a?(Hash)
|
80
|
+
attribute.each do |key,value|
|
81
|
+
if value.is_a?(Array) && !allowed.key?(key)
|
82
|
+
allowed[key] = []
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
allowed
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/backframe.rb
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Backframe::API', type: :controller do
|
4
|
+
controller do
|
5
|
+
include Backframe::API
|
6
|
+
acts_as_api
|
7
|
+
|
8
|
+
def base_api_url
|
9
|
+
'/example'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'error handlng' do
|
14
|
+
before { get :index }
|
15
|
+
|
16
|
+
context 'resource not found' do
|
17
|
+
controller do
|
18
|
+
def index
|
19
|
+
raise ActiveRecord::RecordNotFound
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'responds with 404 status' do
|
24
|
+
expect(response.status).to eq 404
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'responds with JSON body' do
|
28
|
+
expect(json_response[:error][:status]).to eq 404
|
29
|
+
expect(json_response[:error][:message]).to be
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'route not found' do
|
34
|
+
controller do
|
35
|
+
def index
|
36
|
+
raise ActionController::RoutingError, ''
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'responds with 404 status' do
|
41
|
+
expect(response.status).to eq 404
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'responds with JSON body' do
|
45
|
+
expect(json_response[:error][:status]).to eq 404
|
46
|
+
expect(json_response[:error][:message]).to be
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'unauthenticated' do
|
51
|
+
controller do
|
52
|
+
def index
|
53
|
+
raise Backframe::Exceptions::Unauthenticated
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'responds with 401 status' do
|
58
|
+
expect(response.status).to eq 401
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'responds with JSON body' do
|
62
|
+
expect(json_response[:error][:status]).to eq 401
|
63
|
+
expect(json_response[:error][:message]).to be
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'unauthorized' do
|
68
|
+
controller do
|
69
|
+
def index
|
70
|
+
raise Backframe::Exceptions::Unauthorized
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'responds with 403 status' do
|
75
|
+
expect(response.status).to eq 403
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'responds with JSON body' do
|
79
|
+
expect(json_response[:error][:status]).to eq 403
|
80
|
+
expect(json_response[:error][:message]).to be
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'headers' do
|
86
|
+
controller do
|
87
|
+
def index
|
88
|
+
render nothing: true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'responds with Last-Modified header' do
|
93
|
+
get :index
|
94
|
+
|
95
|
+
# FIXME: Use timecop here
|
96
|
+
expect(response.headers['Last-Modified']).to eq Time.now.httpdate
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#page' do
|
101
|
+
let!(:records) { create_list(:example, 10) }
|
102
|
+
|
103
|
+
controller do
|
104
|
+
def index
|
105
|
+
page(Example.all, nil)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'responds with 200 status' do
|
110
|
+
get :index, format: :json
|
111
|
+
expect(response.status).to eq 200
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'responds with records' do
|
115
|
+
get :index, format: :json
|
116
|
+
expect(json_response[:records].length).to eq 10
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'responds with links' do
|
120
|
+
get :index, format: :json
|
121
|
+
expect(json_response[:links]).to be
|
122
|
+
expect(json_response[:links][:self]).to eq '/example/anonymous.json?page=1'
|
123
|
+
end
|
124
|
+
|
125
|
+
context 'with pagination params' do
|
126
|
+
before { get :index, page: 2, per_page: 3, format: :json }
|
127
|
+
|
128
|
+
it 'serializes one page of records' do
|
129
|
+
expect(json_response[:records].length).to eq 3
|
130
|
+
|
131
|
+
serialized = serialize(Example.sort(Example)[3]).stringify_keys
|
132
|
+
expect(json_response[:records].first).to eq serialized
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'includes pagination metadata' do
|
136
|
+
expect(json_response[:total_records]).to eq 10
|
137
|
+
expect(json_response[:total_pages]).to eq 4
|
138
|
+
expect(json_response[:current_page]).to eq 2
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context 'without pagination params' do
|
143
|
+
before { get :index, format: :json }
|
144
|
+
|
145
|
+
it 'serializes all records' do
|
146
|
+
expect(json_response[:records].length).to eq 10
|
147
|
+
|
148
|
+
serialized = serialize(Example.sort(Example).first).stringify_keys
|
149
|
+
expect(json_response[:records].first).to eq serialized
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'includes pagination metadata' do
|
153
|
+
expect(json_response[:total_records]).to eq 10
|
154
|
+
expect(json_response[:total_pages]).to eq 1
|
155
|
+
expect(json_response[:current_page]).to eq 1
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'with sort params' do
|
160
|
+
controller do
|
161
|
+
def index
|
162
|
+
page(Example.all)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'with single param' do
|
167
|
+
before do
|
168
|
+
create_list(:example, 10)
|
169
|
+
get :index, sort: 'a', format: :json
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'responds with records sorted ascending' do
|
173
|
+
values = json_response[:records].map { |record| record[:a] }
|
174
|
+
expect(values).to eq values.sort
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'with negated param' do
|
179
|
+
before do
|
180
|
+
create_list(:example, 10)
|
181
|
+
get :index, sort: '-a', format: :json
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'responds with records sorted descending' do
|
185
|
+
values = json_response[:records].map { |record| record[:a] }
|
186
|
+
expect(values).to eq values.sort.reverse
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'with multiple params' do
|
191
|
+
before do
|
192
|
+
create_list(:example, 10, a: 'a')
|
193
|
+
create_list(:example, 10)
|
194
|
+
get :index, sort: 'a,b', format: :json
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'responds with records sorted by both fields' do
|
198
|
+
values = json_response[:records].map { |record| [record[:a], record[:b]] }
|
199
|
+
expect(values).to eq values.sort
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'with field params' do
|
205
|
+
let!(:records) { create_list(:example, 10) }
|
206
|
+
|
207
|
+
before { get :index, fields: 'a,b', format: :json }
|
208
|
+
|
209
|
+
it 'only responds with given fields' do
|
210
|
+
expect(json_response[:records].first).to eq serialize(Example.sort(Example).first, fields: [:a, :b]).stringify_keys
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
context 'with exclude ids params' do
|
215
|
+
let!(:records) { create_list(:example, 10) }
|
216
|
+
let(:exclude_ids) { records.take(5).map(&:id).join(',') }
|
217
|
+
|
218
|
+
before { get :index, exclude_ids: exclude_ids, format: :json }
|
219
|
+
|
220
|
+
it 'responds with unexcluded records' do
|
221
|
+
expect(json_response[:records].length).to eq 5
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Backframe::Resource', type: :controller do
|
4
|
+
controller do
|
5
|
+
include Backframe::API
|
6
|
+
include Backframe::Resource
|
7
|
+
|
8
|
+
acts_as_api
|
9
|
+
acts_as_resource 'Example', allowed: [:a, :b, :c]
|
10
|
+
|
11
|
+
def base_api_url
|
12
|
+
'/example'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#index' do
|
17
|
+
it 'responds with 200 status' do
|
18
|
+
get :index, format: :json
|
19
|
+
expect(response.status).to eq 200
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#create' do
|
24
|
+
let(:params) { attributes_for(:example).merge(format: :json) }
|
25
|
+
|
26
|
+
it 'responds with 201 status' do
|
27
|
+
post :create, params
|
28
|
+
expect(response.status).to eq 201
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'creates record' do
|
32
|
+
expect { post :create, params }.to change { Example.count }.by 1
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'responds with record' do
|
36
|
+
post :create, params
|
37
|
+
expect(json_response).to eq serialize(Example.last).stringify_keys
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when save fails' do
|
41
|
+
before do
|
42
|
+
allow_any_instance_of(Example).to receive(:save) { false }
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'responds with 422 status' do
|
46
|
+
post :create, params
|
47
|
+
expect(response.status).to eq 422
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'does not create record' do
|
51
|
+
expect { post :create, params }.to change { Example.count }.by 0
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'responds with errors' do
|
55
|
+
post :create, params
|
56
|
+
expect(json_response[:errors]).to be
|
57
|
+
expect(json_response[:message]).to be
|
58
|
+
expect(json_response[:status]).to eq 422
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#show' do
|
64
|
+
let!(:record) { create(:example) }
|
65
|
+
|
66
|
+
it 'responds with 200 status' do
|
67
|
+
get :show, id: record.id
|
68
|
+
expect(response.status).to eq 200
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'responds with record' do
|
72
|
+
get :show, id: record.id
|
73
|
+
expect(json_response).to eq serialize(record).stringify_keys
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#edit' do
|
78
|
+
let!(:record) { create(:example) }
|
79
|
+
|
80
|
+
it 'responds with 200 status' do
|
81
|
+
get :edit, id: record.id
|
82
|
+
expect(response.status).to eq 200
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'responds with record' do
|
86
|
+
get :edit, id: record.id
|
87
|
+
expect(json_response).to eq serialize(record).stringify_keys
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#update' do
|
92
|
+
let!(:record) { create(:example) }
|
93
|
+
let(:params) { attributes_for(:example).merge(id: record.id) }
|
94
|
+
|
95
|
+
it 'responds with 200 status' do
|
96
|
+
patch :update, params
|
97
|
+
expect(response.status).to eq 200
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'udpates record' do
|
101
|
+
patch :update, params
|
102
|
+
expect(json_response[:a]).to eq params[:a]
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'responds with updated record' do
|
106
|
+
patch :update, params
|
107
|
+
expect(json_response).to eq serialize(record.reload).stringify_keys
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'when save fails' do
|
111
|
+
before do
|
112
|
+
allow_any_instance_of(Example).to receive(:save) { false }
|
113
|
+
patch :update, params
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'responds with 422 status' do
|
117
|
+
expect(response.status).to eq 422
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'does not update record' do
|
121
|
+
expect(record.a).to eq record.reload.a
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'responds with errors' do
|
125
|
+
expect(json_response[:errors]).to be
|
126
|
+
expect(json_response[:message]).to be
|
127
|
+
expect(json_response[:status]).to eq 422
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#update_all' do
|
133
|
+
let!(:records) { create_list(:example, 3) }
|
134
|
+
let(:attrs) { records.map { |record| { id: record.id, a: 'a' } } }
|
135
|
+
let(:params) { ActionController::Parameters.new(records: attrs, format: :json) }
|
136
|
+
|
137
|
+
before do
|
138
|
+
routes.draw { post :update_all, to: 'anonymous#update_all' }
|
139
|
+
post :update_all, params
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'responds with 200 status' do
|
143
|
+
expect(response.status).to eq 200
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'updates all records' do
|
147
|
+
records.each { |record| expect(record.reload.a).to eq 'a' }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '#destroy' do
|
152
|
+
let!(:record) { create(:example) }
|
153
|
+
|
154
|
+
it 'responds with 204 status' do
|
155
|
+
delete :destroy, id: record.id
|
156
|
+
expect(response.status).to eq 204
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'deletes record' do
|
160
|
+
expect { delete :destroy, id: record.id }.to change { Example.count }.by(-1)
|
161
|
+
end
|
162
|
+
|
163
|
+
context 'when delete fails' do
|
164
|
+
before do
|
165
|
+
allow_any_instance_of(Example).to receive(:destroy) { false }
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'responds with 422 status' do
|
169
|
+
delete :destroy, id: record.id
|
170
|
+
expect(response.status).to eq 422
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'does not delete record' do
|
174
|
+
expect { delete :destroy, id: record.id }.to change { Example.count }.by 0
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
ENV['RAILS_ENV'] = 'test'
|
2
|
+
|
3
|
+
require 'backframe'
|
4
|
+
|
5
|
+
require 'rails'
|
6
|
+
require 'action_controller/railtie'
|
7
|
+
require 'active_model_serializers'
|
8
|
+
require 'active_model_serializers/railtie'
|
9
|
+
require 'active_record'
|
10
|
+
require 'factory_girl'
|
11
|
+
require 'kaminari'
|
12
|
+
require 'rspec/rails'
|
13
|
+
|
14
|
+
LOGGER = Logger.new('/dev/null')
|
15
|
+
|
16
|
+
Rails.logger = LOGGER
|
17
|
+
ActiveModelSerializers.logger = LOGGER
|
18
|
+
ActiveRecord::Base.logger = LOGGER
|
19
|
+
|
20
|
+
DATABASE = {
|
21
|
+
adapter: 'sqlite3',
|
22
|
+
database: ':memory:'
|
23
|
+
}
|
24
|
+
|
25
|
+
ActiveRecord::Migration.verbose = false
|
26
|
+
ActiveRecord::Base.establish_connection(DATABASE)
|
27
|
+
|
28
|
+
module Backframe
|
29
|
+
class Application < ::Rails::Application
|
30
|
+
def self.find_root(from)
|
31
|
+
Dir.pwd
|
32
|
+
end
|
33
|
+
|
34
|
+
config.eager_load = false
|
35
|
+
config.secret_key_base = 'secret'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Backframe::Application.initialize!
|
40
|
+
Backframe::Mime.register_types
|
41
|
+
|
42
|
+
module Helpers
|
43
|
+
def json_response
|
44
|
+
@json_response ||= JSON.parse(response.body).with_indifferent_access
|
45
|
+
end
|
46
|
+
|
47
|
+
def serialize(value, opts = {})
|
48
|
+
opts[:adapter] ||= Backframe::API::Adapter
|
49
|
+
ActiveModel::SerializableResource.new(value, opts).as_json
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
RSpec.configure do |config|
|
54
|
+
config.include Helpers
|
55
|
+
config.include Rails.application.routes.url_helpers
|
56
|
+
config.include FactoryGirl::Syntax::Methods
|
57
|
+
|
58
|
+
config.use_transactional_fixtures = true
|
59
|
+
end
|
60
|
+
|
61
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Example < ActiveRecord::Base
|
2
|
+
def self.sort(relation, key = nil, order = nil)
|
3
|
+
self._sort(relation, key, order, 'created_at', 'desc', [self])
|
4
|
+
end
|
5
|
+
|
6
|
+
def self._sort(relation, key, order, default_key = 'created_at', default_order = 'desc', included = nil)
|
7
|
+
sortfields = {}
|
8
|
+
included ||= [self]
|
9
|
+
included.each do |model|
|
10
|
+
model.columns.each do |column|
|
11
|
+
sortkey = (model == self) ? column.name : "#{model.table_name.singularize}.#{column.name}"
|
12
|
+
sortfields[sortkey] = "\"#{model.table_name}\".\"#{column.name}\""
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
key = (key.present? && sortfields.has_key?(key)) ? key : default_key
|
17
|
+
order = (order.present?) ? order : default_order
|
18
|
+
relation.order("#{sortfields[key]} #{order}")
|
19
|
+
end
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: backframe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg Kops
|
8
|
+
- Scott Nelson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-04-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '4.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '4.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: active_model_serializers
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 0.10.0.rc4
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 0.10.0.rc4
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: kaminari
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0.16'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.16'
|
56
|
+
description: Rails bindings for Reframe
|
57
|
+
email: hello@thinktopography.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".idea/vcs.xml"
|
64
|
+
- Gemfile
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- backframe.gemspec
|
68
|
+
- lib/backframe.rb
|
69
|
+
- lib/backframe/api.rb
|
70
|
+
- lib/backframe/api/adapter.rb
|
71
|
+
- lib/backframe/api/errors.rb
|
72
|
+
- lib/backframe/api/headers.rb
|
73
|
+
- lib/backframe/api/page.rb
|
74
|
+
- lib/backframe/mime.rb
|
75
|
+
- lib/backframe/resource.rb
|
76
|
+
- lib/backframe/resource/actions.rb
|
77
|
+
- lib/backframe/version.rb
|
78
|
+
- spec/backframe/api_spec.rb
|
79
|
+
- spec/backframe/resource_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
- spec/support/example.rb
|
82
|
+
- spec/support/example_factory.rb
|
83
|
+
- spec/support/example_serializer.rb
|
84
|
+
- spec/support/schema.rb
|
85
|
+
homepage:
|
86
|
+
licenses: []
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubyforge_project:
|
104
|
+
rubygems_version: 2.4.6
|
105
|
+
signing_key:
|
106
|
+
specification_version: 4
|
107
|
+
summary: Backframe
|
108
|
+
test_files:
|
109
|
+
- spec/backframe/api_spec.rb
|
110
|
+
- spec/backframe/resource_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
- spec/support/example.rb
|
113
|
+
- spec/support/example_factory.rb
|
114
|
+
- spec/support/example_serializer.rb
|
115
|
+
- spec/support/schema.rb
|