rails_json_serializer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b1a107c67b6ae1ccb5ad9abcd8f13b1255e46b07d48726c44b89e2e41186a2bb
4
+ data.tar.gz: be454318e55f60ca236b4fbb1ecb9a68fe144e636e13deb43587f94385f7c0cf
5
+ SHA512:
6
+ metadata.gz: 7af60e8a27c62b336d31ec574aa5b3321ec7d73e3097c90d4027763f71148dc587d5113c9ae36576ea8869f4908d40145997c657ce2d5f4321fa9695df56128b
7
+ data.tar.gz: 4b3e8f9aebd58d53de0dc13688fdfb3d0b8f4ffdf0015d688e8329fc0d50b3dc8d81cc69dda3e16e51c08fbc82fb35fd2755c984cd8de53f9ea0c59650c8cd7c
@@ -0,0 +1,27 @@
1
+ require 'active_support/concern'
2
+ require 'serializer/application_serializer'
3
+ Dir[File.join(Rails.root, 'app', 'serializers', '**', '*.rb')].each {|file| require file }
4
+ require 'serializer/configuration'
5
+ require 'serializer/concern'
6
+
7
+ module Serializer
8
+ # config src: http://lizabinante.com/blog/creating-a-configurable-ruby-gem/
9
+ class << self
10
+ attr_accessor :configuration
11
+ end
12
+
13
+ def self.configuration
14
+ @configuration ||= Configuration.new
15
+ end
16
+
17
+ def self.reset
18
+ @configuration = Configuration.new
19
+ end
20
+
21
+ def self.configure
22
+ yield(configuration)
23
+ end
24
+ end
25
+
26
+ # include the extension
27
+ ActiveRecord::Base.send(:include, Serializer::Concern)
@@ -0,0 +1,10 @@
1
+ module ApplicationSerializer
2
+ def serializer_query opts = {}
3
+ {
4
+ include: {
5
+ },
6
+ methods: %w(),
7
+ cache_key: __callee__,
8
+ }
9
+ end
10
+ end
@@ -0,0 +1,172 @@
1
+ module Serializer
2
+ module Concern
3
+ # ActiveSupport extend src: https://stackoverflow.com/questions/2328984/rails-extending-activerecordbase
4
+ extend ActiveSupport::Concern
5
+
6
+ # START MODEL INSTANCE METHODS
7
+ def clear_serializer_cache
8
+ if self.class.const_defined?("#{self.class.name}Serializer")
9
+ serializer_klass = "#{self.class.name}Serializer".constantize
10
+ serializer_klass.public_instance_methods.each do |query_name|
11
+ cache_key = "#{self.class.name}_____#{query_name}___#{self.id}"
12
+ Rails.logger.info "Serializer: CLEARING CACHE KEY: #{cache_key}" if Serializer.configuration.debug
13
+ Rails.cache.delete(cache_key)
14
+ end
15
+ return true
16
+ else
17
+ Rails.logger.info "Serializer: Class #{self.class.name} does not have the serializer module #{self.class.name}Serializer defined." if Serializer.configuration.debug
18
+ return nil
19
+ end
20
+ end
21
+
22
+ def as_json options = {}
23
+ # Not caching records that don't have IDs.
24
+ if !Serializer.configuration.disable_model_caching && self.id && options[:cache_key].present? && !(options.key?(:cache_for) && options[:cache_for].nil?)
25
+ cache_key = "#{self.class.name}_____#{options[:cache_key]}___#{self.id}"
26
+ if Rails.cache.exist?(cache_key)
27
+ Rails.logger.info "Serializer: Cache reading #{cache_key}" if Serializer.configuration.debug
28
+ return Rails.cache.read(cache_key)
29
+ else
30
+ data = super(options)
31
+ data = self.class.as_json_associations_alias_fix(options, data)
32
+ begin
33
+ Rails.logger.info "Serializer: Caching #{cache_key} for #{(options[:cache_for] || Serializer.configuration.default_cache_time)} minutes." if Serializer.configuration.debug
34
+ Rails.cache.write(cache_key, data, expires_in: (options[:cache_for] || Serializer.configuration.default_cache_time).minute)
35
+ rescue Exception => e
36
+ Rails.logger.error "Serializer: Internal Server Error on #{self.class}#as_json ID: #{self.id} for cache key: #{cache_key}"
37
+ Rails.logger.error e.class
38
+ Rails.logger.error e.message
39
+ Rails.logger.error e.backtrace
40
+ end
41
+ return data
42
+ end
43
+ else
44
+ if Serializer.configuration.debug && !Serializer.configuration.disable_model_caching && self.id && options[:cache_key].present? && options.key?(:cache_for) && options[:cache_for].nil?
45
+ Rails.logger.info "Serializer: Caching #{cache_key} NOT caching due to `cache_for: nil`"
46
+ end
47
+ data = super(options)
48
+ data = self.class.as_json_associations_alias_fix(options, data)
49
+ return data
50
+ end
51
+ end
52
+
53
+ # Can override the query, using the options. ex: {json_query_override: :tiny_serializer_query}
54
+ def serializer opts = {}
55
+ query = opts[:json_query_override].present? ? self.class.send(opts[:json_query_override], opts) : self.class.serializer_query(opts)
56
+ if Serializer.configuration.enable_includes && query[:include].present? && self.class.column_names.include?('id') && self.id.present? && !opts[:skip_eager_loading] && self.respond_to?(:persisted?) && self.persisted?
57
+ # It's an extra SQL call, but most likely worth it to pre-load associations
58
+ self.class.includes(self.class.generate_includes_from_json_query(query)).find(self.id).as_json(query)
59
+ else
60
+ as_json(query)
61
+ end
62
+ end
63
+ # END MODEL INSTANCE METHODS
64
+
65
+ class_methods do
66
+ # START MODEL CLASS METHODS
67
+ def inherited subclass
68
+ if subclass.const_defined?("#{subclass.name}Serializer")
69
+ serializer_klass = "#{subclass.name}Serializer".constantize
70
+
71
+ if serializer_klass.class == Module
72
+ if !serializer_klass.const_defined?("SerializerMethods")
73
+ serializer_klass.const_set('SerializerMethods', Module.new {})
74
+ end
75
+
76
+ serializer_klass.public_instance_methods.each do |query_name|
77
+ serializer_name = query_name[/(?<name>.+)_query/, :name]
78
+ if serializer_name.nil?
79
+ Rails.logger.info "Serializer: #{serializer_klass.name} method #{query_name} does not end in '(.+)_query', as is expected of serializers" if Serializer.configuration.debug
80
+ next
81
+ end
82
+ # Skip if chosen to override it.
83
+ next if serializer_klass.respond_to?(serializer_name)
84
+ if serializer_name == 'serializer'
85
+ serializer_klass::SerializerMethods.send(:define_method, serializer_name) do |opts = {}|
86
+ super({json_query_override: query_name}.merge(opts))
87
+ end
88
+ else
89
+ serializer_klass::SerializerMethods.send(:define_method, serializer_name) do |opts = {}|
90
+ serializer({json_query_override: query_name}.merge(opts))
91
+ end
92
+ end
93
+ end
94
+
95
+ # Inject instance methods
96
+ subclass.send(:include, serializer_klass::SerializerMethods)
97
+ # Inject class methods
98
+ subclass.send(:extend, serializer_klass::SerializerMethods)
99
+ # Inject class methods that has queries
100
+ subclass.send(:extend, serializer_klass)
101
+ else
102
+ Rails.logger.info "Serializer: #{serializer_klass.name} was not a Module as expected" if Serializer.configuration.debug
103
+ end
104
+ end
105
+ super(subclass)
106
+ end
107
+
108
+ # Can override the query, using the options. ex: {json_query_override: :tiny_children_serializer_query}
109
+ def serializer opts = {}
110
+ query = opts[:json_query_override].present? ? self.send(opts[:json_query_override], opts) : serializer_query(opts)
111
+ if Serializer.configuration.enable_includes && query[:include].present? && !opts[:skip_eager_loading]
112
+ includes(generate_includes_from_json_query(query)).as_json(query)
113
+ else
114
+ # Have to use 'all' gets stack level too deep otherwise. Not sure why.
115
+ all.as_json(query)
116
+ end
117
+ end
118
+
119
+ def as_json_associations_alias_fix options, data, opts = {}
120
+ if data
121
+ # Depth is almost purely for debugging purposes
122
+ opts[:depth] ||= 0
123
+ if options[:include].present?
124
+ options[:include].each do |include_key, include_hash|
125
+ # The includes doesn't have to have a hash attached. Skip it if it doesn't.
126
+ next if include_hash.nil?
127
+ data_key = include_key.to_s
128
+ if include_hash[:as].present?
129
+ if include_hash[:as].to_s == include_key.to_s
130
+ raise "Serializer: Cannot alias json query association to have the same as the original key; as: #{include_hash[:as].to_s}; original_key: #{include_key.to_s} on self: #{name}"
131
+ end
132
+ alias_name = include_hash[:as]
133
+ data[alias_name.to_s] = data[include_key.to_s]
134
+ data.delete(include_key.to_s)
135
+ data_key = alias_name.to_s
136
+ end
137
+ # At this point, the data could be an array of objects, with no as_json options.
138
+ if !data[data_key].is_a?(Array)
139
+ data[data_key] = as_json_associations_alias_fix(include_hash, data[data_key], {depth: opts[:depth] + 1})
140
+ else
141
+ data[data_key].each_with_index do |value,i|
142
+ data[data_key][i] = as_json_associations_alias_fix(include_hash, value, {depth: opts[:depth] + 1})
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+ return data
149
+ end
150
+
151
+ def generate_includes_from_json_query options = {}, klass = nil
152
+ query_filter = {}
153
+ klass = self if klass.nil?
154
+ if options[:include].present? && !options[:skip_eager_loading]
155
+ options[:include].each do |include_key, include_hash|
156
+ # Will 'next' if there is a scope that takes arguments, an instance-dependent scope.
157
+ # Can't eager load when assocation has a instance condition for it's associative scope.
158
+ # Might not be a real assocation
159
+ next if klass.reflect_on_association(include_key).nil?
160
+ next if klass.reflect_on_association(include_key).scope&.arity&.nonzero?
161
+ query_filter[include_key] = {}
162
+ next if include_hash.none?
163
+ query_filter[include_key] = generate_includes_from_json_query(include_hash, klass.reflect_on_association(include_key).klass)
164
+ end
165
+ end
166
+ return query_filter
167
+ end
168
+ # END MODEL CLASS METHODS
169
+
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,12 @@
1
+ module Serializer
2
+ class Configuration
3
+ attr_accessor :enable_includes, :default_cache_time, :disable_model_caching, :debug
4
+
5
+ def initialize
6
+ @enable_includes = true
7
+ @default_cache_time = 360
8
+ @disable_model_caching = false
9
+ @debug = false
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_json_serializer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - benjamin.dana.software.dev@gmail.com
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-04-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ description:
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/serializer.rb
34
+ - lib/serializer/application_serializer.rb
35
+ - lib/serializer/concern.rb
36
+ - lib/serializer/configuration.rb
37
+ homepage: https://github.com/danabr75/rails_json_serializer
38
+ licenses:
39
+ - GNU
40
+ metadata: {}
41
+ post_install_message:
42
+ rdoc_options: []
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '2.4'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ requirements: []
56
+ rubygems_version: 3.1.2
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: An ActiveRecord JSON Serializer with supported caching and eager-loading
60
+ test_files: []