railsful 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 77c58909e21f08d21955de91651f80db0b6e149351e2819e0980c8215f938fbb
4
+ data.tar.gz: ad03136f8c07f8c9d8b7db6000f1aec732c91b2c3f450ec6446ed882b0f00884
5
+ SHA512:
6
+ metadata.gz: 1a8aab06a687afe8073b03541aa514e8e709983ea96b0f0e0802b08f93cabe4f3b71e70a465252b0b50e61288c1a300edda2433b9c2a7a2abae92d126a023696
7
+ data.tar.gz: 99ac9e457415aee433f83f20e48e483dc8176f2b8a73a6fabcd52ce8d1232e069a65deb8b30a89f2fb3d12526409d5d3a32d036a3e84092cfa45cb84780bc90f
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.reek.yml ADDED
@@ -0,0 +1,2 @@
1
+ exclude_paths:
2
+ - spec/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,18 @@
1
+ sudo: false
2
+ env:
3
+ global:
4
+ - CC_TEST_REPORTER_ID=2002e6cb033fc09cb3a764c5d1622f756e0c2d2b61f0a62e51b8e38070cdff00
5
+ language: ruby
6
+ rvm:
7
+ - 2.6
8
+ - 2.5
9
+ - 2.4
10
+ - 2.3
11
+ before_script:
12
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
13
+ - chmod +x ./cc-test-reporter
14
+ - ./cc-test-reporter before-build
15
+ script:
16
+ - bundle exec rspec
17
+ after_script:
18
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in railsful.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Henning Vogt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Railsful
2
+ [![Maintainability](https://api.codeclimate.com/v1/badges/d1e81476a4c63779815e/maintainability)](https://codeclimate.com/github/hausgold/railsful/maintainability)
3
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/d1e81476a4c63779815e/test_coverage)](https://codeclimate.com/github/hausgold/railsful/test_coverage)
4
+
5
+ A small but helpful collection of concerns and tools to create
6
+ a restful JSON API compliant Rails application.
7
+
8
+ ## Installation
9
+
10
+ Add these lines to your application's Gemfile:
11
+
12
+ ```ruby
13
+ # fast_jsonapi is used to serialize objects.
14
+ gem 'fast_jsonapi'
15
+ # kaminari needed for pagination.
16
+ gem 'kaminari'
17
+
18
+ gem 'railsful'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install railsful
28
+
29
+ ## Usage
30
+
31
+ ### Serialization
32
+
33
+ In order to serialize your objects it is necessary that their serializer follow
34
+ the same naming convention and modules as the object to be serialized.
35
+
36
+ ``` ruby
37
+ # app/models/some_module/user.rb
38
+ module SomeModule
39
+ class User
40
+ ...
41
+ end
42
+ end
43
+
44
+ # app/serializers/some_module/user_serializer.rb
45
+ module SomeModule
46
+ class UserSerializer
47
+ ...
48
+ end
49
+ end
50
+ ```
51
+
52
+ After that you can use `render json: ...` without specifying the serializer:
53
+
54
+ ``` ruby
55
+ module SomeModule
56
+ class UserController
57
+ def index
58
+ render json: User.all
59
+ end
60
+ end
61
+ end
62
+ ```
63
+
64
+ Will result in:
65
+
66
+ ``` json
67
+ GET /some_module/users
68
+ {
69
+ "data": [
70
+ { "type": "user", "id": 1, "attributes": { ... } },
71
+ { "type": "user", "id": 2, "attributes": { ... } }
72
+ ]
73
+ }
74
+ ```
75
+
76
+ ### Deserialization
77
+ For deserialization of jsonapi compliant request all controllers that
78
+ inherit from `ActionController` can use the `#deserialized_params` method.
79
+
80
+ ``` ruby
81
+ class UsersController < ApplicationController
82
+ def create
83
+ user = User.new(user_params)
84
+
85
+ # Return success/fail ...
86
+ end
87
+
88
+ private
89
+
90
+ def user_params
91
+ deserialized_params.permit(:first_name, :last_name, ...)
92
+ end
93
+ end
94
+ ```
95
+
96
+ ## Development
97
+
98
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
99
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
100
+ prompt that will allow you to experiment.
101
+
102
+ To install this gem onto your local machine, run `bundle exec rake install`. To
103
+ release a new version, update the version number in `version.rb`, and then run
104
+ `bundle exec rake release`, which will create a git tag for the version, push
105
+ git commits and tags, and push the `.gem` file to
106
+ [rubygems.org](https://rubygems.org).
107
+
108
+ ## Contributing
109
+
110
+ Bug reports and pull requests are welcome on GitHub at
111
+ https://github.com/hausgold/railsful.
112
+
113
+ ## License
114
+
115
+ The gem is available as open source under the terms of the [MIT
116
+ License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "railsful"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ module Deserializable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ def deserialized_params
9
+ Deserializer.new(params.to_unsafe_hash).deserialize
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deep_merge/rails_compat'
4
+
5
+ module Railsful
6
+ class Deserializer
7
+ attr_reader :params
8
+
9
+ def initialize(params)
10
+ @params = params
11
+ end
12
+
13
+ # Deserializes the given params.
14
+ #
15
+ # :reek:FeatureEnvy
16
+ def deserialize
17
+ deserialized = {}
18
+
19
+ data = params.fetch(:data, {})
20
+
21
+ # Merge the resources attributes
22
+ deserialized.merge!(data.fetch(:attributes, {}))
23
+
24
+ # Get the already existing relationships
25
+ data.fetch(:relationships, {}).each do |type, payload|
26
+ deserialized.merge!(relationship(type, payload))
27
+ end
28
+
29
+ # Get the included elements.
30
+ deserialized.deeper_merge!(included_hash(params))
31
+
32
+ # Return the deserialized params.
33
+ ActionController::Parameters.new(deserialized)
34
+ end
35
+
36
+ # Fetches all included associations/relationships from the
37
+ # included hash.
38
+ def included_hash(params)
39
+ included_hash = {}
40
+
41
+ params.fetch(:included, []).each do |inc|
42
+ type = inc[:type].to_sym
43
+ attrs = inc[:attributes]
44
+
45
+ if params.dig(:data, :relationships, type, :data).is_a?(Array)
46
+ # TODO: Pluralize the key here.
47
+ included_hash["#{type}_attributes"] ||= []
48
+ included_hash["#{type}_attributes"] << attrs
49
+ else
50
+ included_hash["#{type}_attributes"] = attrs
51
+ end
52
+ end
53
+
54
+ included_hash
55
+ end
56
+
57
+ def relationship(type, payload)
58
+ data = payload[:data]
59
+
60
+ return has_many_relationship(type, data) if data.is_a?(Array)
61
+
62
+ belongs_to_relationship(type, data)
63
+ end
64
+
65
+ # rubocop:disable Naming/PredicateName
66
+ def has_many_relationship(type, data)
67
+ return {} unless data.is_a?(Array)
68
+
69
+ ids = data.map { |relation| relation[:id] }.compact
70
+
71
+ return {} if ids.empty?
72
+
73
+ { :"#{type}_ids" => ids }
74
+ end
75
+ # rubocop:enable Naming/PredicateName
76
+
77
+ def belongs_to_relationship(type, data)
78
+ # Fetch a possible id from the data.
79
+ relation_id = data[:id]
80
+
81
+ # If no ID is provided skip it.
82
+ return {} unless relation_id
83
+
84
+ # Build the relationship hash.
85
+ { :"#{type}_id" => relation_id }
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ # The base error for this gem.
5
+ class Error < StandardError
6
+ attr_reader :detail, :status
7
+
8
+ # Initializer.
9
+ #
10
+ # @param detail [String] The detailed message.
11
+ # @param status [Integer] The status code.
12
+ def initialize(detail = nil, status = 400)
13
+ @detail = detail
14
+ @status = status
15
+ end
16
+
17
+ # Format the error as jsonapi wants it to.
18
+ #
19
+ # @return [Hash]
20
+ def as_json(_options = nil)
21
+ {
22
+ errors: [
23
+ {
24
+ status: status,
25
+ title: self.class.name.demodulize.underscore,
26
+ detail: detail
27
+ }
28
+ ]
29
+ }
30
+ end
31
+ end
32
+
33
+ # The error that is raised when pagination fails.
34
+ class PaginationError < Error; end
35
+ # The error that is raised when sorting fails.
36
+ class SortingError < Error; end
37
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/errors'
4
+
5
+ module Railsful
6
+ module Interceptors
7
+ # This interceptor checks the given json object for an 'errors' array
8
+ # and checks if any errors are available.
9
+ module Errors
10
+ def render(options)
11
+ super(errors_options(options))
12
+ end
13
+
14
+ def errors_options(options)
15
+ return options unless errors?(options)
16
+
17
+ # Fetch all the errors from the passed json value.
18
+ errors = errors(options.fetch(:json))
19
+
20
+ # Overwrite the json value and set the errors array.
21
+ options.merge(json: { errors: errors })
22
+ end
23
+
24
+ # Transform error output format into more "jsonapi" like.
25
+ def errors(raw_errors)
26
+ errors = []
27
+
28
+ raw_errors.details.each do |field, array|
29
+ errors += field_errors(field, array)
30
+ end
31
+
32
+ errors
33
+ end
34
+
35
+ def field_errors(field, array)
36
+ array.map do |hash|
37
+ formatted_error(hash, field)
38
+ end
39
+ end
40
+
41
+ # Format the error by adding additional status and field information.
42
+ #
43
+ # :reek:UtilityFunction
44
+ def formatted_error(hash, field)
45
+ {
46
+ status: '422',
47
+ field: field
48
+ }.merge(hash)
49
+ end
50
+
51
+ # Checks if given renderable is an ActiveModel::Error
52
+ #
53
+ # :reek:UtilityFunction
54
+ def errors?(options)
55
+ return false unless options
56
+
57
+ options.fetch(:json, nil).is_a?(ActiveModel::Errors)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ module Interceptors
5
+ # This interceptors implements the "include" functionality for
6
+ # a given record or a relation.
7
+ module Include
8
+ def render(options)
9
+ super(include_options(options))
10
+ end
11
+
12
+ def include_options(options)
13
+ # Check if include key should be merged into options hash.
14
+ return options unless should_include?
15
+
16
+ # Deep merge include options, so we do not override existing
17
+ # include options.
18
+ options.deeper_merge(include: includes)
19
+ end
20
+
21
+ # Check if options should contain includes.
22
+ #
23
+ # @return [Boolean] The answer.
24
+ def should_include?
25
+ # Only GET requests should have the "include" functionality,
26
+ # since it may be a parameter in a create or update action.
27
+ method == 'GET' && includes.any?
28
+ end
29
+
30
+ # Fetch the list of all includes.
31
+ #
32
+ # @return [Array] The list of all include options.
33
+ def includes
34
+ params.fetch(:include, nil).to_s.split(',')
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ module Interceptors
5
+ # Interceptor that paginates a given ActiveRecord::Relation
6
+ # with help of the Kaminari gem.
7
+ module Pagination
8
+ def render(options)
9
+ super(pagination_options(options))
10
+ end
11
+
12
+ def pagination_options(options)
13
+ # Check if json value should be paginated.
14
+ return options unless paginate?(options)
15
+
16
+ # Get the relation from options hash so we can paginate it and
17
+ # check the total count.
18
+ relation = options.fetch(:json)
19
+
20
+ # Paginate the relation and store new relation in temporary variable.
21
+ paginated = paginate(relation)
22
+
23
+ # Create a meta hash
24
+ meta = {
25
+ total_pages: paginated.try(:total_pages),
26
+ total_count: paginated.try(:total_count),
27
+ current_page: paginated.try(:current_page),
28
+ next_page: paginated.try(:next_page),
29
+ prev_page: paginated.try(:prev_page)
30
+ }
31
+
32
+ options.deeper_merge(
33
+ links: links(paginated), meta: meta
34
+ ).merge(json: paginated)
35
+ end
36
+
37
+ private
38
+
39
+ # Check if given entity is paginatable and request allows pagination.
40
+ #
41
+ # @param options [Hash] The global render options.
42
+ # @return [Boolean] The answer.
43
+ def paginate?(options)
44
+ method == 'GET' &&
45
+ params.fetch(:page, false) &&
46
+ relation?(options)
47
+ end
48
+
49
+ # Paginate given relation
50
+ #
51
+ # @param relation [ActiveRecord::Relation] The relation.
52
+ # @return [ActiveRecord::Relation] The paginated relation.
53
+ def paginate(relation)
54
+ # If page param is not a hash, raise an error.
55
+ unless params.to_unsafe_hash.fetch(:page, nil).is_a?(Hash)
56
+ raise PaginationError,
57
+ 'Wrong pagination format. Hash expected.'
58
+ end
59
+
60
+ # Get the per page size.
61
+ per_page = params.dig(:page, :size)
62
+
63
+ relation = relation.page(params.dig(:page, :number))
64
+ relation = relation.per(per_page) if per_page
65
+
66
+ relation
67
+ end
68
+
69
+ # Create the pagination links
70
+ #
71
+ # @param relation [ActiveRecord::Relation] The relation to be paginated.
72
+ # @return [Hash] The +links+ hash.
73
+ def links(relation)
74
+ per_page = params.dig(:page, :size)
75
+
76
+ {
77
+ self: collection_url(relation.try(:current_page), per_page),
78
+ next: collection_url(relation.try(:next_page), per_page),
79
+ prev: collection_url(relation.try(:prev_page), per_page)
80
+ }
81
+ end
82
+
83
+ # The assembled pagination url.
84
+ #
85
+ # @param per_page [Integer] The per page count.
86
+ # @param page [Integer] The page.
87
+ # @return [String] The URL.
88
+ def collection_url(page, per_page)
89
+ return nil unless page
90
+
91
+ # Set fallback pagination.
92
+ # TODO: Get it from the model.
93
+ per_page ||= 25
94
+
95
+ # The +#url_for+ method comes from the given controller.
96
+ controller.url_for \
97
+ controller: params[:controller],
98
+ action: params[:action],
99
+ params: {
100
+ page: { number: page, size: per_page }
101
+ }
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ module Interceptors
5
+ # Interceptor that sorts a given ActiveRecord::Relation
6
+ module Sorting
7
+ def render(options)
8
+ super(sorting_options(options))
9
+ end
10
+
11
+ def sorting_options(options)
12
+ # Check if json value should be sorted.
13
+ return options unless sort?(options)
14
+
15
+ # Get the relation from options hash so we can sort it
16
+ relation = options.fetch(:json)
17
+
18
+ options.merge(json: sort(relation))
19
+ end
20
+
21
+ private
22
+
23
+ # Check if given entity is sortable and request allows sorting.
24
+ #
25
+ # @param options [Hash] The global render options.
26
+ # @return [Boolean] The answer.
27
+ def sort?(options)
28
+ method == 'GET' && params.fetch(:sort, false) && relation?(options)
29
+ end
30
+
31
+ # Format a sort string to a database friendly order string
32
+ #
33
+ # @return [String] database order query e.g. 'name DESC'
34
+ #
35
+ # :reek:UtilityFunction
36
+ def order(string)
37
+ string.start_with?('-') ? "#{string[1..-1]} DESC" : "#{string} ASC"
38
+ end
39
+
40
+ # Map the sort params to a database friendly set of strings
41
+ #
42
+ # @return [Array] Array of string e.g. ['name DESC', 'age ASC']
43
+ def orders
44
+ params.fetch(:sort).split(',').map do |string|
45
+ next unless string =~ /\A-?\w+\z/ # allow only word chars
46
+
47
+ order(string)
48
+ end.compact
49
+ end
50
+
51
+ # Sort given relation
52
+ #
53
+ # @param relation [ActiveRecord::Relation] The relation.
54
+ # @return [ActiveRecord::Relation] The sorted relation.
55
+ def sort(relation)
56
+ order_string = orders.join(', ')
57
+ # support both #reorder and #order call on relation
58
+ return relation.reorder(order_string) if relation.respond_to?(:reorder)
59
+ return relation.order(order_string) if relation.respond_to?(:order)
60
+
61
+ raise SortingError, 'Relation does not respond to #reorder or #order.'
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ class Railtie < Rails::Railtie
5
+ initializer 'railsful.action_controller' do
6
+ ActiveSupport.on_load(:action_controller) do
7
+ prepend Railsful::Serializable
8
+ include Railsful::Deserializable
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ module Serializable
5
+ def render(options = nil, extra_options = {}, &block)
6
+ super(fast_jsonapi_options(options), extra_options, &block)
7
+ end
8
+
9
+ def fast_jsonapi_options(options)
10
+ Serializer.new(self).render(options)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'railsful/interceptors/errors'
4
+ require 'railsful/interceptors/include'
5
+ require 'railsful/interceptors/pagination'
6
+ require 'railsful/interceptors/sorting'
7
+
8
+ module Railsful
9
+ # This class allows to encapsulate the interceptor logic from the
10
+ # prepended controller, so the controller is not polluted with
11
+ # all needed (helper) methods.
12
+ class Serializer
13
+ # All interceptors that provide jsonapi logic.
14
+ prepend Interceptors::Include
15
+ prepend Interceptors::Pagination
16
+ prepend Interceptors::Sorting
17
+ prepend Interceptors::Errors
18
+
19
+ attr_reader :controller
20
+
21
+ # Keep a reference to the controller, so all helper methods
22
+ # like +url_for+ can be used.
23
+ def initialize(controller)
24
+ @controller = controller
25
+ end
26
+
27
+ # The render function every interceptor MUST implement in order to
28
+ # add certain functionality.
29
+ #
30
+ # @param options [Hash] The render options hash.
31
+ # @return [Hash] The (modified) render options hash.
32
+ #
33
+ # :reek:FeatureEnvy
34
+ def render(options)
35
+ # Get the renderable (Object that should be rendered) from options hash.
36
+ renderable = options[:json]
37
+
38
+ # Return if renderable is blank
39
+ return options unless renderable
40
+
41
+ # Try to fetch the right serializer for given renderable.
42
+ serializer = serializer_for(renderable)
43
+
44
+ # When no serializer is found just pass the original options hash.
45
+ return options unless serializer
46
+
47
+ # Replace json value with new serializer
48
+ options.merge(json: serializer.new(renderable, options))
49
+ end
50
+
51
+ # Find the right serializer for given object.
52
+ #
53
+ # @param [ApplicationRecord, ActiveRecord::Relation]
54
+ # @return [Class] The serializer class.
55
+ #
56
+ # :reek:UtilityFunction
57
+ def serializer_for(renderable)
58
+ # Get the class in order to find the right serializer.
59
+ klass = if renderable.is_a?(ActiveRecord::Relation)
60
+ renderable.model.name
61
+ else
62
+ renderable.class.name
63
+ end
64
+
65
+ "#{klass}Serializer".safe_constantize
66
+ end
67
+
68
+ # Fetch the params from controller.
69
+ #
70
+ # @return [] The params.
71
+ def params
72
+ controller.params
73
+ end
74
+
75
+ # Fetch the HTTP method from controllers request.
76
+ #
77
+ # @return [String] The method.
78
+ def method
79
+ controller.request.request_method
80
+ end
81
+
82
+ # Check if given options contain an ActiveRecord::Relation.
83
+ #
84
+ # @param options [Hash] The options.
85
+ # @return [Boolean] The answer.
86
+ #
87
+ # :reek:UtilityFunction
88
+ def relation?(options)
89
+ options[:json].is_a?(ActiveRecord::Relation)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Railsful
4
+ VERSION = '0.1.0'.freeze
5
+ end
data/lib/railsful.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'active_support'
2
+
3
+ require 'railsful/version'
4
+ require 'railsful/exceptions'
5
+ require 'railsful/railtie' if defined?(Rails)
6
+ require 'railsful/serializable'
7
+ require 'railsful/serializer'
8
+ require 'railsful/deserializable'
9
+ require 'railsful/deserializer'
10
+
11
+ # A small but helpful collection of concerns and tools to create
12
+ # a restful JSON API compliant Rails application.
13
+ module Railsful
14
+ # Nothing to do here
15
+ end
data/railsful.gemspec ADDED
@@ -0,0 +1,42 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'railsful/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'railsful'
7
+ spec.version = Railsful::VERSION
8
+ spec.authors = ['Henning Vogt', 'Marcus Geissler']
9
+ spec.email = ['henning.vogt@hausgold.de',
10
+ 'marcus.geissler@hausgold.de']
11
+
12
+ spec.summary = 'JSON API serializer and deserializer for Rails'
13
+ spec.description = 'This gem provides useful helper functions to interact' \
14
+ 'with JSON API.'
15
+ spec.homepage = 'https://github.com/hausgold/restful'
16
+ spec.license = 'MIT'
17
+
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
+ else
21
+ raise 'RubyGems 2.0 or newer is required to protect against public pushes.'
22
+ end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(%r{^(test|spec|features)/})
26
+ end
27
+
28
+ spec.bindir = 'exe'
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ['lib']
31
+
32
+ spec.required_ruby_version = '>= 2.3'
33
+
34
+ spec.add_dependency 'deep_merge', '~> 1'
35
+ spec.add_dependency 'rails', ['>=4', '< 6']
36
+
37
+ spec.add_development_dependency 'bundler', '>= 1.16', '< 3'
38
+ spec.add_development_dependency 'pry'
39
+ spec.add_development_dependency 'rake', '~> 10.0'
40
+ spec.add_development_dependency 'rspec-rails', '~> 3.0'
41
+ spec.add_development_dependency 'simplecov', '~> 0.15'
42
+ end
metadata ADDED
@@ -0,0 +1,180 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: railsful
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Henning Vogt
8
+ - Marcus Geissler
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2019-01-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: deep_merge
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rails
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '4'
35
+ - - "<"
36
+ - !ruby/object:Gem::Version
37
+ version: '6'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '4'
45
+ - - "<"
46
+ - !ruby/object:Gem::Version
47
+ version: '6'
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.16'
55
+ - - "<"
56
+ - !ruby/object:Gem::Version
57
+ version: '3'
58
+ type: :development
59
+ prerelease: false
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '1.16'
65
+ - - "<"
66
+ - !ruby/object:Gem::Version
67
+ version: '3'
68
+ - !ruby/object:Gem::Dependency
69
+ name: pry
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rake
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '10.0'
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '10.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: rspec-rails
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '3.0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3.0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: simplecov
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.15'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.15'
124
+ description: This gem provides useful helper functions to interactwith JSON API.
125
+ email:
126
+ - henning.vogt@hausgold.de
127
+ - marcus.geissler@hausgold.de
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".reek.yml"
134
+ - ".rspec"
135
+ - ".travis.yml"
136
+ - Gemfile
137
+ - LICENSE.txt
138
+ - README.md
139
+ - Rakefile
140
+ - bin/console
141
+ - bin/setup
142
+ - lib/railsful.rb
143
+ - lib/railsful/deserializable.rb
144
+ - lib/railsful/deserializer.rb
145
+ - lib/railsful/exceptions.rb
146
+ - lib/railsful/interceptors/errors.rb
147
+ - lib/railsful/interceptors/include.rb
148
+ - lib/railsful/interceptors/pagination.rb
149
+ - lib/railsful/interceptors/sorting.rb
150
+ - lib/railsful/railtie.rb
151
+ - lib/railsful/serializable.rb
152
+ - lib/railsful/serializer.rb
153
+ - lib/railsful/version.rb
154
+ - railsful.gemspec
155
+ homepage: https://github.com/hausgold/restful
156
+ licenses:
157
+ - MIT
158
+ metadata:
159
+ allowed_push_host: https://rubygems.org
160
+ post_install_message:
161
+ rdoc_options: []
162
+ require_paths:
163
+ - lib
164
+ required_ruby_version: !ruby/object:Gem::Requirement
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ version: '2.3'
169
+ required_rubygems_version: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirements: []
175
+ rubyforge_project:
176
+ rubygems_version: 2.7.6
177
+ signing_key:
178
+ specification_version: 4
179
+ summary: JSON API serializer and deserializer for Rails
180
+ test_files: []