backframe 0.0.49 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +16 -0
  3. data/.gitignore +2 -2
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +1156 -0
  6. data/Gemfile +9 -13
  7. data/README.md +133 -4
  8. data/Rakefile +3 -7
  9. data/backframe.gemspec +10 -11
  10. data/circle.yml +5 -2
  11. data/lib/backframe.rb +21 -34
  12. data/lib/backframe/mime.rb +4 -0
  13. data/lib/backframe/query.rb +48 -0
  14. data/lib/backframe/query/sort.rb +29 -0
  15. data/lib/backframe/railtie.rb +1 -16
  16. data/lib/backframe/response.rb +60 -0
  17. data/lib/backframe/response/adapter/csv.rb +39 -0
  18. data/lib/backframe/response/adapter/json.rb +53 -0
  19. data/lib/backframe/response/adapter/xlsx.rb +41 -0
  20. data/lib/backframe/response/adapter/xml.rb +37 -0
  21. data/lib/backframe/response/collection.rb +43 -0
  22. data/lib/backframe/response/fields.rb +62 -0
  23. data/lib/backframe/response/record.rb +38 -0
  24. data/lib/backframe/service.rb +60 -0
  25. data/lib/backframe/service/result/base.rb +24 -0
  26. data/lib/backframe/service/result/failure.rb +21 -0
  27. data/lib/backframe/service/result/success.rb +21 -0
  28. data/lib/backframe/version.rb +1 -1
  29. data/spec/fixtures/active_record.rb +22 -0
  30. data/spec/fixtures/models.rb +25 -0
  31. data/spec/fixtures/queries.rb +28 -0
  32. data/spec/fixtures/seeds.rb +12 -0
  33. data/spec/fixtures/serializers.rb +23 -0
  34. data/spec/fixtures/services.rb +26 -0
  35. data/spec/query/sort_spec.rb +47 -0
  36. data/spec/query_spec.rb +63 -0
  37. data/spec/response/adapter/csv_spec.rb +47 -0
  38. data/spec/response/adapter/json_spec.rb +66 -0
  39. data/spec/response/adapter/xlsx_spec.rb +59 -0
  40. data/spec/response/adapter/xml_spec.rb +63 -0
  41. data/spec/response/fields_spec.rb +45 -0
  42. data/spec/response/record_spec.rb +45 -0
  43. data/spec/response_spec.rb +153 -0
  44. data/spec/service/result/failure_spec.rb +16 -0
  45. data/spec/service/result/sucess_spec.rb +17 -0
  46. data/spec/service_spec.rb +16 -0
  47. data/spec/spec_helper.rb +15 -52
  48. metadata +78 -81
  49. data/lib/backframe/actioncontroller/acts_as_activation.rb +0 -70
  50. data/lib/backframe/actioncontroller/acts_as_api.rb +0 -39
  51. data/lib/backframe/actioncontroller/acts_as_api/adapter.rb +0 -53
  52. data/lib/backframe/actioncontroller/acts_as_api/errors.rb +0 -48
  53. data/lib/backframe/actioncontroller/acts_as_api/headers.rb +0 -11
  54. data/lib/backframe/actioncontroller/acts_as_api/page.rb +0 -181
  55. data/lib/backframe/actioncontroller/acts_as_reset.rb +0 -86
  56. data/lib/backframe/actioncontroller/acts_as_resource.rb +0 -92
  57. data/lib/backframe/actioncontroller/acts_as_resource/actions.rb +0 -100
  58. data/lib/backframe/actioncontroller/acts_as_session.rb +0 -80
  59. data/lib/backframe/activerecord/acts_as_activable.rb +0 -50
  60. data/lib/backframe/activerecord/acts_as_distinct.rb +0 -49
  61. data/lib/backframe/activerecord/acts_as_enum.rb +0 -62
  62. data/lib/backframe/activerecord/acts_as_orderable.rb +0 -40
  63. data/lib/backframe/activerecord/acts_as_percent.rb +0 -46
  64. data/lib/backframe/activerecord/acts_as_phone.rb +0 -59
  65. data/lib/backframe/activerecord/acts_as_user.rb +0 -101
  66. data/lib/backframe/activerecord/default_values.rb +0 -32
  67. data/lib/backframe/activerecord/filter_sort.rb +0 -79
  68. data/lib/backframe/activerecord/migration.rb +0 -25
  69. data/lib/backframe/image_cache/image_cache.rb +0 -45
  70. data/lib/backframe/image_cache/lib/asset.rb +0 -109
  71. data/lib/backframe/image_cache/lib/cache.rb +0 -67
  72. data/lib/backframe/image_cache/lib/conversions.rb +0 -132
  73. data/lib/backframe/models/activation.rb +0 -60
  74. data/lib/backframe/models/activity.rb +0 -40
  75. data/lib/backframe/models/reset.rb +0 -59
  76. data/lib/backframe/models/story.rb +0 -9
  77. data/lib/backframe/serializers/activity_serializer.rb +0 -44
  78. data/spec/backframe/acts_as_api_spec.rb +0 -225
  79. data/spec/backframe/acts_as_resource_spec.rb +0 -178
  80. data/spec/support/example_factory.rb +0 -9
  81. data/spec/support/example_serializer.rb +0 -3
  82. data/spec/support/schema.rb +0 -8
data/Gemfile CHANGED
@@ -2,16 +2,12 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'activesupport'
6
- gem 'write_xlsx'
7
- gem 'active_model_serializers'
8
- gem 'activerecord'
9
- gem 'factory_girl'
10
- gem 'faker'
11
- gem 'kaminari'
12
- gem 'rails'
13
- gem 'rake'
14
- gem 'rspec-rails'
15
- gem 'sqlite3'
16
- gem 'liquid'
17
- gem 'phony'
5
+ group :development, :test do
6
+ gem 'rake'
7
+ gem 'rspec', '>= 2.0'
8
+ gem 'rspec-benchmark'
9
+ gem "codeclimate-test-reporter", require: nil
10
+ gem 'rubocop'
11
+ gem 'sqlite3'
12
+ gem 'roo', '~> 2.4.0'
13
+ end
data/README.md CHANGED
@@ -1,10 +1,139 @@
1
1
  # Backframe
2
+ Backframe introduces a collection of new core objects to help you write testable
3
+ APIs for your Rails and Ruby Applications.
2
4
 
3
- Backframe is a library of server side bindings for building Rails backends for Reframe apps
5
+ <table>
6
+ <tr>
7
+ <td>Build Status</td>
8
+ <td>
9
+ <a href="https://circleci.com/gh/thinktopography/backframe">
10
+ <img src="https://img.shields.io/circleci/project/thinktopography/backframe.svg?maxAge=600" alt="Build Status" >
11
+ </a>
12
+ </td>
13
+ </tr>
14
+ <tr>
15
+ <td>Code Quality</td>
16
+ <td>
17
+ <a href="https://codeclimate.com/github/thinktopography/backframe">
18
+ <img src="https://img.shields.io/codeclimate/github/thinktopography/backframe.svg?maxAge=600" alt="Code Climate" />
19
+ </a>
20
+ <a href="https://codeclimate.com/github/thinktopography/backframe/coverage">
21
+ <img src="https://img.shields.io/codeclimate/coverage/github/thinktopography/backframe.svg?maxAge=600" alt="Code Coverage" />
22
+ </a>
23
+ </td>
24
+ </tr>
25
+ </table>
4
26
 
5
- ## Development
27
+ ## API Controllers
28
+ Backframe Controllers are just like regular controllers in Rails. Here's an
29
+ example of how you might use Backframe objects to fulfill requests:
6
30
 
7
- bundle install
8
- bundle exec rake spec
31
+ ```Ruby
32
+ class API::ContactsController < API::ApplicationController
9
33
 
34
+ def index
35
+ contacts = ContactQuery.perform(Contact, request.query_parameters)
36
+ render Backframe::Response.render(contacts, params)
37
+ end
10
38
 
39
+ def show
40
+ contact = Contact.find(params[:id])
41
+ render json: contact, status: 200
42
+ end
43
+
44
+ def create
45
+ result = CreateContactService.perform(params)
46
+ if result.success?
47
+ render json: result.contact, status: 201
48
+ else
49
+ render json: { message: result.message, errors: result.errors }, status: 422
50
+ end
51
+ end
52
+
53
+ def update
54
+ result = UpdateContactService.perform(params)
55
+ if result.success?
56
+ render json: result.contact, status: 201
57
+ else
58
+ render json: { message: result.message, errors: result.errors }, status: 422
59
+ end
60
+ end
61
+
62
+ def destroy
63
+ result = DestroyContactService.perform(params)
64
+ if result.success?
65
+ render json: result.contact, status: 201
66
+ else
67
+ render json: { message: result.message, errors: result.errors }, status: 422
68
+ end
69
+ end
70
+
71
+ end
72
+ ```
73
+
74
+ ##Query Objects
75
+ Backframe adds query objects to your application which can be placed in the
76
+ `app/queries` directory. These objects extend from `Backframe::Query` and enable
77
+ you to encapsulate and test your sorting and filtering logic.
78
+
79
+ ```Ruby
80
+ class ContactQuery < Backframe::Query
81
+
82
+ def filter(records, filters)
83
+ records = records.where('LOWER(first_name) like ?', '%'+filters[:first_name].downcase+'%') if filters.key?(:first_name)
84
+ records = records.where('LOWER(last_name) like ?', '%'+filters[:last_name].downcase+'%') if filters.key?(:last_name)
85
+ records = records.where('LOWER(email) like ?', '%'+filters[:email].downcase+'%') if filters.key?(:email)
86
+ records
87
+ end
88
+
89
+ def sort(sorts)
90
+ records = records.order(sorts)
91
+ records
92
+ end
93
+
94
+ end
95
+ ```
96
+
97
+ ##Service Objects
98
+ Backframe adds service objects to your application which can be placed in the
99
+ `app/services` directory. These objects extend from `Backframe::Service` and
100
+ enable you to abandon callbacks in favor of the service pattern.
101
+
102
+ ```Ruby
103
+ class CreateContactService < Backframe::Service
104
+
105
+ def initialize(params)
106
+ @params = params
107
+ end
108
+
109
+ def perform
110
+ create_contact
111
+ log_activity
112
+ send_email
113
+ end
114
+
115
+ def create_contact
116
+ ...
117
+ end
118
+
119
+ def log_activity
120
+ ...
121
+ end
122
+
123
+ def send_email
124
+ ...
125
+ end
126
+
127
+ end
128
+ ```
129
+
130
+ ##Serialization
131
+ Backframe lets you serialize your data in several formats - JSON, XML, XLS, CSV,
132
+ and TSV. The library provides response adapters for each target serialization
133
+ format
134
+
135
+ ## Author & Credits
136
+ Backframe was originally written by [Greg Kops](https://github.com/mochini) and
137
+ [Scott Nelson](https://github.com/scttnlsn) based upon their work at
138
+ [Think Topography](http://thinktopography.com). Backframe has been used in
139
+ production to support a handful of client applications.
data/Rakefile CHANGED
@@ -1,14 +1,10 @@
1
1
  $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
2
- require 'rspec/core/rake_task'
3
2
  require 'bundler/version'
4
3
  require './lib/backframe'
5
4
  require './lib/backframe/version'
5
+ require 'rspec/core/rake_task'
6
6
 
7
- RSpec::Core::RakeTask.new(:spec) do |t|
8
- t.rspec_opts = ['--color']
9
- end
10
-
11
- task default: :spec
7
+ RSpec::Core::RakeTask.new(:spec)
12
8
 
13
9
  desc "Build the gem"
14
10
  task :build do
@@ -23,4 +19,4 @@ end
23
19
  desc "Build and release the gem"
24
20
  task :release => :build do
25
21
  system "gem push backframe-#{Backframe::VERSION}.gem"
26
- end
22
+ end
@@ -5,21 +5,20 @@ require 'backframe/version'
5
5
 
6
6
  Gem::Specification.new do |gem|
7
7
  gem.name = 'backframe'
8
- gem.email = 'hello@thinktopography.com'
9
- gem.description = 'Rails bindings for Reframe'
8
+ gem.email = 'greg@thinktopography.com'
9
+ gem.description = 'A collection of core objects for writing testable APIs'
10
+ gem.homepage = 'https://github.com/thinktopography/backframe'
10
11
  gem.version = Backframe::VERSION
11
- gem.summary = 'Backframe'
12
- gem.authors = ['Greg Kops', 'Scott Nelson']
13
-
12
+ gem.summary = 'backframe'
13
+ gem.authors = ['Greg Kops']
14
14
  gem.files = `git ls-files`.split($/)
15
+ gem.license = 'MIT'
15
16
  gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
16
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
18
  gem.require_paths = ["lib"]
18
-
19
- gem.add_runtime_dependency 'write_xlsx', '~> 0.83.0'
20
- gem.add_runtime_dependency 'phony', '~> 2.15.21'
21
- gem.add_runtime_dependency 'activerecord', '~> 4.0'
19
+ gem.add_development_dependency 'rake'
22
20
  gem.add_runtime_dependency 'activesupport', '~> 4.0'
23
- gem.add_runtime_dependency 'active_model_serializers', '>= 0.10.0.rc4'
24
- gem.add_runtime_dependency 'kaminari', '~> 0.16'
21
+ gem.add_runtime_dependency 'activerecord', '~> 4.2'
22
+ gem.add_runtime_dependency 'active_model_serializers', '~> 0.10.0'
23
+ gem.add_runtime_dependency 'write_xlsx', '~> 0.83.0'
25
24
  end
data/circle.yml CHANGED
@@ -1,8 +1,11 @@
1
1
  machine:
2
2
  ruby:
3
3
  version: 2.2.2
4
+ database:
5
+ override:
6
+ - echo null
4
7
  deployment:
5
- production:
8
+ release:
6
9
  branch: release
7
10
  commands:
8
- - cd ~/backframe; bundle exec rake release
11
+ - cd ~/backframe; bundle exec rake release
@@ -1,46 +1,33 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
3
5
  require 'active_record'
4
- require 'active_record/version'
5
- require 'active_support/core_ext/module'
6
- require 'liquid'
6
+ require 'active_model_serializers'
7
+ require 'write_xlsx'
7
8
 
8
- require 'backframe/actioncontroller/acts_as_activation'
9
- require 'backframe/actioncontroller/acts_as_api'
10
- require 'backframe/actioncontroller/acts_as_reset'
11
- require 'backframe/actioncontroller/acts_as_resource'
12
- require 'backframe/actioncontroller/acts_as_session'
13
- require 'backframe/activerecord/acts_as_activable'
14
- require 'backframe/activerecord/acts_as_distinct'
15
- require 'backframe/activerecord/acts_as_enum'
16
- require 'backframe/activerecord/acts_as_orderable'
17
- require 'backframe/activerecord/acts_as_percent'
18
- require 'backframe/activerecord/acts_as_phone'
19
- require 'backframe/activerecord/acts_as_user'
20
- require 'backframe/activerecord/default_values'
21
- require 'backframe/activerecord/filter_sort'
22
- require 'backframe/activerecord/migration'
23
- require 'backframe/models/activity'
24
- require 'backframe/models/activation'
25
- require 'backframe/models/reset'
26
- require 'backframe/models/story'
27
- require 'backframe/serializers/activity_serializer'
28
- require 'backframe/image_cache/image_cache'
9
+ require 'backframe/query'
10
+ require 'backframe/query/sort'
29
11
 
30
- module Backframe
31
-
32
- extend ActiveSupport::Autoload
12
+ require 'backframe/response'
13
+ require 'backframe/response/adapter/csv'
14
+ require 'backframe/response/adapter/json'
15
+ require 'backframe/response/adapter/xlsx'
16
+ require 'backframe/response/adapter/xml'
17
+ require 'backframe/response/collection'
18
+ require 'backframe/response/fields'
19
+ require 'backframe/response/record'
33
20
 
34
- autoload :Activity
35
- autoload :Activation
36
- autoload :Reset
21
+ require 'backframe/service'
22
+ require 'backframe/service/result/base'
23
+ require 'backframe/service/result/failure'
24
+ require 'backframe/service/result/success'
37
25
 
38
- module Exceptions
39
- class Unauthenticated < StandardError; end
40
- class Unauthorized < StandardError; end
41
- end
26
+ ActiveModelSerializers.logger = nil
42
27
 
28
+ module Backframe
43
29
  end
30
+
44
31
  if defined? Rails
45
32
  require 'backframe/mime'
46
33
  require 'backframe/railtie'
@@ -1,12 +1,16 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Backframe
4
+
4
5
  module Mime
6
+
5
7
  extend self
6
8
 
7
9
  def register_types
8
10
  ::Mime::Type.register('text/tab-separated-values', :tsv)
9
11
  ::Mime::Type.register('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', :xlsx)
10
12
  end
13
+
11
14
  end
15
+
12
16
  end
@@ -0,0 +1,48 @@
1
+ # encoding: utf-8
2
+
3
+ module Backframe
4
+
5
+ class Query
6
+
7
+ EXCLUDE_IDS_REGEX = /^[\d,]*$/
8
+ SORT_REGEX = /^[\w\_\-,]*$/
9
+
10
+ class << self
11
+
12
+ def perform(*args)
13
+ new.perform(*args)
14
+ end
15
+
16
+ end
17
+
18
+ def perform(records, params = {})
19
+ filters = params.except([:exclude_ids,:fields,:page,:per_page,:sort])
20
+ if filters.any?
21
+ records = filter(records, filters)
22
+ end
23
+ if params.key?(:exclude_ids) && params[:exclude_ids] =~ EXCLUDE_IDS_REGEX
24
+ table = records.arel_table.name
25
+ records = records.where('"'+table+'"."id" NOT IN (?)', params[:exclude_ids].split(","))
26
+ end
27
+ if params.key?(:sort) && params[:sort] =~ SORT_REGEX
28
+ sorts = Backframe::Query::Sort.parse(params[:sort])
29
+ records = sort(records, sorts)
30
+ end
31
+ records
32
+ end
33
+
34
+ def filter(records, _filters)
35
+ records
36
+ end
37
+
38
+ def sort(records, sorts)
39
+ order = []
40
+ sorts.each do |sort|
41
+ order << sort[:key] + " " + sort[:order]
42
+ end
43
+ records.order(order.join(", "))
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ module Backframe
4
+
5
+ class Query
6
+
7
+ class Sort
8
+
9
+ class << self
10
+
11
+ def parse(sort_string = nil)
12
+ sort = []
13
+ sort_string ||= '-created_at'
14
+ sort_string.split(',').each do |token|
15
+ token.strip!
16
+ key = (token[0] == '-') ? token[1..-1] : token
17
+ order = (token[0] == '-') ? 'DESC' : 'ASC'
18
+ sort << { key: key, order: order }
19
+ end
20
+ sort
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -3,22 +3,7 @@
3
3
  module Backframe
4
4
  class Railtie < ::Rails::Railtie
5
5
  initializer 'backframe' do |_app|
6
- ActionController::Base.send(:include, Backframe::ActsAsAPI)
7
- ActionController::Base.send(:include, Backframe::ActsAsResource)
8
- ActionController::Base.send(:include, Backframe::ActsAsActivation)
9
- ActionController::Base.send(:include, Backframe::ActsAsReset)
10
- ActionController::Base.send(:include, Backframe::ActsAsSession)
11
- ActiveRecord::Base.send(:include, Backframe::ActsAsActivable)
12
- ActiveRecord::Base.send(:include, Backframe::ActsAsDistinct)
13
- ActiveRecord::Base.send(:include, Backframe::ActsAsEnum)
14
- ActiveRecord::Base.send(:include, Backframe::ActsAsOrderable)
15
- ActiveRecord::Base.send(:include, Backframe::ActsAsPercent)
16
- ActiveRecord::Base.send(:include, Backframe::ActsAsPhone)
17
- ActiveRecord::Base.send(:include, Backframe::ActsAsUser)
18
- ActiveRecord::Base.send(:include, Backframe::DefaultValues)
19
- ActiveRecord::Base.send(:include, Backframe::FilterSort)
20
- ActiveRecord::Migration.send(:include, Backframe::Migration)
21
6
  Backframe::Mime.register_types
22
7
  end
23
8
  end
24
- end
9
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ module Backframe
4
+
5
+ class Response
6
+
7
+ FIELDS_REGEX = /^[A-Za-z0-9\_,]*$/
8
+ PAGE_REGEX = /^[0-9]*$/
9
+
10
+ class << self
11
+
12
+ def render(records, params = {})
13
+ begin
14
+ fields = Backframe::Response::Fields.new(records, params[:fields])
15
+ collection = Backframe::Response::Collection.new(records, params[:page], params[:per_page])
16
+ if params[:format] == 'json'
17
+ data = Backframe::Response::Adapter::Json.render(collection, fields)
18
+ success(json: data, content_type: 'application/json')
19
+ elsif params[:format] == 'xml'
20
+ data = Backframe::Response::Adapter::Xml.render(collection, fields)
21
+ success(xml: data, content_type: 'application/xhtml+xml')
22
+ elsif params[:format] == 'csv'
23
+ data = Backframe::Response::Adapter::Csv.render(collection, fields, ",")
24
+ success(text: data, content_type: 'text/plain')
25
+ elsif params[:format] == 'tsv'
26
+ data = Backframe::Response::Adapter::Csv.render(collection, fields, "\t")
27
+ success(text: data, content_type: 'text/plain')
28
+ elsif params[:format] == 'xlsx'
29
+ data = Backframe::Response::Adapter::Xlsx.render(collection, fields)
30
+ success(text: data, content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
31
+ else
32
+ failure('Unknown Format', 404)
33
+ end
34
+ rescue Exception => e
35
+ failure('Application Error', 500)
36
+ end
37
+ end
38
+
39
+ def success(*response)
40
+ response[0].merge(status: 200)
41
+ end
42
+
43
+ def failure(message, status)
44
+ {
45
+ json: {
46
+ error: {
47
+ message: message,
48
+ status: status
49
+ }
50
+ },
51
+ content_type: 'application/json',
52
+ status: status
53
+ }
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ end