grape-transformations 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +19 -0
  5. data/lib/grape/generators/templates/entity.rb +12 -0
  6. data/lib/grape/generators/templates/grape-transformations.rb +4 -0
  7. data/lib/grape/generators/transformations/entity_generator.rb +54 -0
  8. data/lib/grape/generators/transformations/install_generator.rb +29 -0
  9. data/lib/grape/tasks/grapi_tasks.rake +4 -0
  10. data/lib/grape/transformations/base.rb +67 -0
  11. data/lib/grape/transformations/engine.rb +14 -0
  12. data/lib/grape/transformations/loader.rb +86 -0
  13. data/lib/grape/transformations/version.rb +5 -0
  14. data/lib/grape/transformations.rb +112 -0
  15. data/spec/api/animal_spec.rb +37 -0
  16. data/spec/api/user_spec.rb +55 -0
  17. data/spec/generators/entity_generator_spec.rb +25 -0
  18. data/spec/generators/install_generator_spec.rb +33 -0
  19. data/spec/grapi_spec.rb +132 -0
  20. data/spec/spec_helper.rb +78 -0
  21. data/spec/test_app/README.rdoc +28 -0
  22. data/spec/test_app/Rakefile +6 -0
  23. data/spec/test_app/app/api/api.rb +7 -0
  24. data/spec/test_app/app/api/test_app/entities/animals/compact.rb +10 -0
  25. data/spec/test_app/app/api/test_app/entities/animals/full.rb +12 -0
  26. data/spec/test_app/app/api/test_app/entities/food.rb +8 -0
  27. data/spec/test_app/app/api/test_app/entities/users/compact.rb +10 -0
  28. data/spec/test_app/app/api/test_app/entities/users/default.rb +13 -0
  29. data/spec/test_app/app/api/test_app/modules/animal.rb +39 -0
  30. data/spec/test_app/app/api/test_app/modules/user.rb +39 -0
  31. data/spec/test_app/app/assets/javascripts/application.js +13 -0
  32. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  33. data/spec/test_app/app/controllers/application_controller.rb +5 -0
  34. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  35. data/spec/test_app/app/models/animal.rb +12 -0
  36. data/spec/test_app/app/models/food.rb +7 -0
  37. data/spec/test_app/app/models/user.rb +13 -0
  38. data/spec/test_app/app/views/layouts/application.html.erb +14 -0
  39. data/spec/test_app/bin/bundle +3 -0
  40. data/spec/test_app/bin/rails +4 -0
  41. data/spec/test_app/bin/rake +4 -0
  42. data/spec/test_app/config/application.rb +32 -0
  43. data/spec/test_app/config/boot.rb +5 -0
  44. data/spec/test_app/config/database.yml +25 -0
  45. data/spec/test_app/config/environment.rb +5 -0
  46. data/spec/test_app/config/environments/development.rb +37 -0
  47. data/spec/test_app/config/environments/production.rb +78 -0
  48. data/spec/test_app/config/environments/test.rb +39 -0
  49. data/spec/test_app/config/initializers/assets.rb +8 -0
  50. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  51. data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
  52. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  53. data/spec/test_app/config/initializers/grapi.rb +4 -0
  54. data/spec/test_app/config/initializers/inflections.rb +16 -0
  55. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  56. data/spec/test_app/config/initializers/session_store.rb +3 -0
  57. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  58. data/spec/test_app/config/locales/en.yml +23 -0
  59. data/spec/test_app/config/routes.rb +3 -0
  60. data/spec/test_app/config/secrets.yml +22 -0
  61. data/spec/test_app/config.ru +4 -0
  62. data/spec/test_app/db/development.sqlite3 +0 -0
  63. data/spec/test_app/log/development.log +72 -0
  64. data/spec/test_app/public/404.html +67 -0
  65. data/spec/test_app/public/422.html +67 -0
  66. data/spec/test_app/public/500.html +66 -0
  67. data/spec/test_app/public/favicon.ico +0 -0
  68. data/spec/test_app/tmp/cache/7F3/650/registered_entities +1 -0
  69. metadata +400 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 16befd94dc2420bbfb79747c0e0231f00b4594a1
4
+ data.tar.gz: 03e6b12432f44d2c8cc0276e0cd656453e0bc25d
5
+ SHA512:
6
+ metadata.gz: 842c56ccc9ffd37e7f4c8a7d440ee06be7ab8358168137a300c5212ad4603a5105bc32891f9ed7b10fb7f3ac331066994f024c410f462d7cf28e34ab6a8abd0f
7
+ data.tar.gz: 1bb9ed0dbb2697306db84ec13033aebb1082edbf258243e5dd93cffab8bb448c4e42f84ff1ac2c387e0cdb9abbc86954251257b3872e11f5c935c6a2f9f62cd2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Johan Tique, Miguel Diaz
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = Grape Transformations
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'grape-transformations'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.rdoc')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,12 @@
1
+ module <%= app_name.classify %>
2
+ module Entities
3
+ module <%= entity_name.classify.pluralize %>
4
+ class Default < Grape::Entity
5
+ <% fields.each do |field| %>
6
+ <% attribute, type = field.split(':')%>
7
+ expose :<%= attribute %>, documentation: { type: '<%= type %>', desc: 'write a description here', example: 'write an example here' }
8
+ <% end %>
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # it loads automatically entities that are associated with models in your app
2
+ Grape::Transformations.setup do |config|
3
+ config.load_entities_from File.join('<%= app_name %>', 'entities')
4
+ end
@@ -0,0 +1,54 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Grape
4
+ module Generators
5
+ module Transformations
6
+ class EntityGenerator < Rails::Generators::Base
7
+
8
+ desc <<-DESC.strip_heredoc
9
+ Create inherited Grape::Entity entity in your app/api/.../entities folder. this
10
+ created entity will have related with grape-transformations naming conventions
11
+
12
+ For example:
13
+
14
+ rails generate entity user
15
+
16
+ This will create a entity class at app/api/.../entities/users/default.rb like this:
17
+
18
+ module TestApp
19
+ module Entities
20
+ module Users
21
+ class Default < Grape::Entity
22
+
23
+ end
24
+ end
25
+ end
26
+ end
27
+ DESC
28
+
29
+ source_root File.expand_path('../../templates', __FILE__)
30
+
31
+ argument :entity_name, type: :string, required: true, desc: 'name of entity'
32
+ argument :fields, :type => :array, required: false, desc: 'field set that you want to expose'
33
+
34
+ def generate_layout
35
+ @fields ||= []
36
+ template "entity.rb", "app/api/#{app_name}/entities/#{underscored_entity_name.pluralize}/default.rb"
37
+ end
38
+
39
+ private
40
+
41
+ # Returns the app name
42
+ # @return [String]
43
+ def app_name
44
+ Rails.application.config.session_options[:key].sub(/^_/,'').sub(/_session/,'')
45
+ end
46
+
47
+ def underscored_entity_name
48
+ entity_name.underscore
49
+ end
50
+
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,29 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Grape
4
+ module Generators
5
+ module Transformations
6
+ class InstallGenerator < Rails::Generators::Base
7
+
8
+ source_root File.expand_path("../../templates", __FILE__)
9
+
10
+ def copy_initializer
11
+ template "grape-transformations.rb", "config/initializers/grape-transformations.rb"
12
+ end
13
+
14
+ def generate_layout
15
+ create_file "app/api/#{app_name}/entities/.keep"
16
+ end
17
+
18
+ private
19
+
20
+ # Returns the app name
21
+ # @return [String]
22
+ def app_name
23
+ Rails.application.config.session_options[:key].sub(/^_/,'').sub(/_session/,'')
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :transformations do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,67 @@
1
+ require 'active_support/concern'
2
+
3
+ module Grape
4
+ module Transformations
5
+ module Base
6
+ extend ::ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+
10
+ # Defines the target model associated to current transformations
11
+ # @param [Class], Class as a reference of model, bear in mind that you
12
+ # need to prefix the scope resolution operator at the beginning in order
13
+ # to point to the right model
14
+ # example:
15
+ # target_model ::User
16
+ def target_model(klass)
17
+ @grape_transformations_target_class = klass
18
+ end
19
+
20
+ # Defines the endpoints that will use with the existing transformations
21
+ # @param [Proc], all code related with transformable endpoints definition
22
+ def define_endpoints(&block)
23
+ @grape_transformations_endpoints = block.to_proc
24
+ end
25
+
26
+ # Defines the endpoints that will use with the existing transformations
27
+ # @param [Proc], all code related with non transformable endpoints definition
28
+ def define_non_transformable_endpoints(&block)
29
+ @grape_transformations_non_transformable_endpoints = block.to_proc
30
+ end
31
+
32
+ # Invokes the block associated with transformable endpoints
33
+ # @param [Hash], options hash that contains entity transformation definition
34
+ def add_endpoints_with(options = {})
35
+ return unless @grape_transformations_endpoints.is_a? Proc
36
+ entity = options[:entity] || entity_for_transformation(:default)
37
+ @grape_transformations_endpoints.call(entity)
38
+ end
39
+
40
+ # Invokes the block associated with non transformable endpoints
41
+ def add_non_transformable_endpoints
42
+ return unless @grape_transformations_non_transformable_endpoints.is_a? Proc
43
+ @grape_transformations_non_transformable_endpoints.call
44
+ end
45
+
46
+ # Abstracts entity_for_transformation grape_transformations method using the grape_transformations_target_class variable
47
+ def entity_for_transformation(transformation)
48
+ Grape::Transformations.entity_for_transformation(@grape_transformations_target_class, transformation)
49
+ end
50
+
51
+ # Invokes both transformable and non-transformable body endpoints
52
+ def add_endpoints
53
+ # transformable endpoints
54
+ Grape::Transformations.transformations_for(@grape_transformations_target_class).each do |transformation|
55
+ entity = entity_for_transformation transformation
56
+ namespace transformation do
57
+ add_endpoints_with entity: entity
58
+ end
59
+ end
60
+ # normal api
61
+ add_endpoints_with entity: entity_for_transformation(:default) if Grape::Transformations.simbolized_entities_for(@grape_transformations_target_class).include? :default
62
+ add_non_transformable_endpoints
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ # Rails Engine
2
+
3
+ module Grape
4
+ module Transformations
5
+ class Engine < ::Rails::Engine
6
+ # config.generators do |g|
7
+ # g.test_framework :rspec, :fixture => false
8
+ # g.fixture_replacement :factory_girl, :dir => 'spec/factories'
9
+ # g.assets false
10
+ # g.helper false
11
+ # end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,86 @@
1
+ module Grape
2
+ module Transformations
3
+ module Loader
4
+ # extend ActiveSupport::Autoload # TODO: Make entities autoloadable so that
5
+ # code is live reloaded in development mode
6
+
7
+ # Return a class by its name, id it is not valid
8
+ # it returns nil
9
+ # @param [String], full class name
10
+ # @return [Object]
11
+ def self.class_by_name(name)
12
+ name.safe_constantize
13
+ end
14
+
15
+ # Loads all valid entities located in relative_path_to_entities dir, it
16
+ # verifies if entity is valid and if it is associated with valid class.
17
+ # When this process is accomplished is saved a hash with classname as a
18
+ # key and its associated entity as a value (using Rails.cache)
19
+ # @param root_api_path [String], path to api dir
20
+ # @param relative_path_to_entities [String], relative path to entities
21
+ def self.load_entities(root_api_path, relative_path_to_entities)
22
+ available_entities = {}
23
+ path_to_entities = File.join(root_api_path, relative_path_to_entities)
24
+ api_entities_namespace = relative_path_to_entities.split('/').map { |module_name| module_name.gsub(' ','_').camelcase }.join('::')
25
+ Dir.glob(File.join(path_to_entities, '**', '*.rb')).each do |filename|
26
+ relative_path = filename.sub("#{path_to_entities}/", '')
27
+ class_hierarchy = relative_path.split("/").map {|filename| filename.gsub(' ','_').camelcase.sub('.rb','')}
28
+ class_name = class_hierarchy.join('::')
29
+ entity_name = "#{api_entities_namespace}::#{class_name}"
30
+ klass = class_by_name class_name
31
+ entity = class_by_name entity_name
32
+
33
+ # If there is a one to one match from Entities::..::Entity to ..::Klass, use it
34
+ if entity && klass
35
+ available_entities[class_name] = entity_name
36
+ else
37
+ # If not, try to find a "Default" transformation entity class
38
+ # namespaced in the pluralized klass
39
+
40
+ # An entity has to exist
41
+ if entity
42
+ # example: ['Admin', 'Billing', 'Addresses'], 'Default' = ['Admin', 'Billing', 'Addresses', 'Default']
43
+ *transformation_hierarchy, transformation_name = class_hierarchy
44
+
45
+ if transformation_name == 'Default'
46
+
47
+ # example: ['Admin', 'Billing'], 'Addresses' = ['Admin', 'Billing', 'Addresses']
48
+ *class_name_hierarchy, pluralized_transformable_class_name = transformation_hierarchy
49
+ # example: 'Address' = "Addresses".singularize
50
+ transformable_class_name = pluralized_transformable_class_name.singularize
51
+ # example: Admin::Billing::Address
52
+
53
+ class_name_hierarchy << transformable_class_name
54
+ class_name = class_name_hierarchy.join('::')
55
+ klass = class_by_name class_name
56
+ # example: Admin::Billing::Address
57
+
58
+ # a matching class was found
59
+ if klass
60
+ available_entities[class_name] = entity_name
61
+ else
62
+ # The entity is just an entity, no matching class... however seems like
63
+ # conventions were not properly followed
64
+ end
65
+
66
+ else
67
+ # No one to one match or Default transformation found, no entity used
68
+ end
69
+ else
70
+ puts "[WARN] - Conventions not followed? - Entity #{entity_name} was not found at #{filename}, please check"
71
+ end
72
+ end
73
+
74
+ end
75
+ register_entities available_entities
76
+ end
77
+
78
+ private
79
+ # Saves entities in cache by using Rails.cache approach
80
+ def self.register_entities(available_entities)
81
+ Rails.cache.delete(:registered_entities) # make sure it is clean
82
+ Rails.cache.fetch(:registered_entities){ available_entities }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,5 @@
1
+ module Grape
2
+ module Transformations
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,112 @@
1
+ require 'grape/transformations/version'
2
+ require 'grape/transformations/loader'
3
+ require 'grape/transformations/base'
4
+ require 'grape/transformations/engine' if defined?(Rails)
5
+ require 'grape'
6
+ require 'rails'
7
+
8
+ module Grape
9
+ module Transformations
10
+ # Sets default api location ../app/api
11
+ mattr_accessor :root_api_path
12
+
13
+ # relative_path_to_entities
14
+ mattr_accessor :relative_path_to_entities
15
+
16
+ # Uses Loader.load_entities method in order to load all valid entities located
17
+ # in relative path_to_entities
18
+ def self.load_entities_from(relative_path_to_entities)
19
+ self.root_api_path ||= File.join(Rails.root, 'app', 'api')
20
+ self.relative_path_to_entities = relative_path_to_entities
21
+ Loader.load_entities root_api_path, relative_path_to_entities
22
+ end
23
+
24
+ # defines wrapped accessor to grape-transformations configurator
25
+ def self.setup
26
+ yield self
27
+ end
28
+
29
+ # returns entities registered from rails cache
30
+ def self.registered_entities
31
+ Rails.cache.fetch(:registered_entities)
32
+ end
33
+
34
+ # returns the appropite class for a class name or class
35
+ def self.registered_entity_for(klass)
36
+ registered_entities[normalized_class_name(klass)]
37
+ end
38
+
39
+ # @return [Class] the entity class for a given transformation
40
+ # example:
41
+ # entity_for_transformation(User, :full)
42
+ # #=> MyApp::Entities::Users::Full
43
+ def self.entity_for_transformation(klass, transformation)
44
+ entity_hierarchy = root_entity_namespace_hierarchy << normalized_class_name(klass).pluralize
45
+ entity_hierarchy << transformation.to_s.camelize
46
+ entity_hierarchy.join('::').constantize
47
+ end
48
+
49
+ # @return [Array] the top level module entity namespace wrapping
50
+ # example: ['MyApp', 'Entities']
51
+ def self.root_entity_namespace_hierarchy
52
+ File.split(relative_path_to_entities).map{|namespace| namespace.camelize}
53
+ end
54
+
55
+ # @return [String] the top level module namespacing to begin looking entities within
56
+ # example: "MyApp::Entities"
57
+ def self.root_entity_namespace
58
+ root_entity_namespace_hierarchy.join('::')
59
+ end
60
+
61
+ # @return [String] the absolute path to entities directory
62
+ def self.full_path_to_entities
63
+ File.join(root_api_path, relative_path_to_entities)
64
+ end
65
+
66
+ # @return [Array] all transformations listed as symbols (excludes :default)
67
+ def self.transformations_for(klass)
68
+ simbolized_entities_for(klass) - [:default]
69
+ end
70
+
71
+ def self.simbolized_entities_for(klass)
72
+ class_name = normalized_class_name(klass)
73
+ entities_directory = File.join(full_path_to_entities, class_name.pluralize.underscore)
74
+ entity_files = Dir[File.join(entities_directory, '*')]
75
+ entity_files.map{|filename| File.split(filename).last.sub('.rb', '').to_sym}
76
+ end
77
+
78
+ # @return [Array] all entity classes including "Default" that relate to a given class
79
+ def self.all_entities_for(klass)
80
+ (transformations_for(klass) + [:default]).map{|transformation| entity_for_transformation(klass, transformation)}
81
+ end
82
+
83
+ # @return [Array] all entity classes except for "Default" that relate to a given class
84
+ def self.all_transformation_entities_for(klass)
85
+ all_entities_for(klass) - [entity_for_transformation(klass, :default)]
86
+ end
87
+
88
+ # return the class_name given a Class or :class or "class"...or anything like it
89
+ def self.normalized_class_name(klass)
90
+ klass.to_s.classify
91
+ end
92
+
93
+ # Monkey patch for Grape::Endpoint#present so that it
94
+ # autoloads a default entity when presenting a given class instance
95
+ ::Grape::Endpoint.send(:define_method, :present_with_autoload) do |instance, options = {}|
96
+ is_instance_a_set = false
97
+ is_instance_a_set = if defined? Mongoid
98
+ instance.kind_of?(Mongoid::Criteria)
99
+ elsif defined? ActiveRecord
100
+ instance.kind_of?(ActiveRecord::Relation)
101
+ end
102
+ is_instance_a_set ||= instance.kind_of?(Array)
103
+ klass = is_instance_a_set ? instance.first.class : instance.class
104
+ entity = Grape::Transformations.registered_entity_for klass
105
+ options[:with] = entity.constantize if options[:with].nil? && entity
106
+ present_without_autoload instance, options
107
+ end
108
+
109
+ ::Grape::Endpoint.alias_method_chain(:present, :autoload)
110
+
111
+ end
112
+ end