grape-transformations 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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