backframe 0.0.49 → 1.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.
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