rails_json_serializer 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.
@@ -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: []