fast_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
+ SHA1:
3
+ metadata.gz: 5f74dbde94fa39ac31bdc30753fe69645952f58e
4
+ data.tar.gz: bdce8bb4428da1e1a4ce866635c3a22a636d26d2
5
+ SHA512:
6
+ metadata.gz: 442fa957a7d296934849581cd5dd8aa777c6cfda8ea5e52d889f0b2e27f11ce953e88a0b58ada157ce0cb89f4451dd8168d76ed43211ff7214480f3313bc1aaf
7
+ data.tar.gz: 07157e747b2af7c9f3e6636cbc61f2f48dd0c075530151e897a7a232714a2e12dc23666db046f5172d487b30ceb6917a7d4d555a5dd711e05940ced9ce97d707
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,3 @@
1
+ 1.0.0
2
+
3
+ * Initial release
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2016 We Heart It
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.
@@ -0,0 +1,148 @@
1
+ This gem provides a highly optimized framework for serializing Ruby objects into hashes suitable for serialization to some other format (i.e. JSON). It provides many of the same features as other serialization frameworks like active_model_serializers, but it is designed to emphasize code efficiency over feature set.
2
+
3
+ ## Examples
4
+
5
+ For these examples we'll assume we have a simple Person class.
6
+
7
+ ```ruby
8
+ class Person
9
+ attr_accessor :id, :first_name, :last_name, :parents, :children
10
+
11
+ def intitialize(attributes = {})
12
+ @id = attributes[:id]
13
+ @first_name = attributes[:first_name]
14
+ @last_name = attributes[:last_name]
15
+ @gender = attributes(:gender)
16
+ @parent = attributes[:parents]
17
+ @children = attributes[:children] || {}
18
+ end
19
+
20
+ def ==(other)
21
+ other.instance_of?(self.class) && other.id == id
22
+ end
23
+ end
24
+
25
+ person = Person.new(:id => 1, :first_name => "John", :last_name => "Doe", :gender => "M")
26
+ ```
27
+
28
+ Serializers are classes that include `FastSerializer::Serializer`. Call the `serialize` method to specify which fields to include in the serialized object. Field values are gotten by calling the corresponding method on the serializer. By default each serialized field will define a method that delegates to the wrapped object.
29
+
30
+ ruby```
31
+ class PersonSerializer
32
+ include FastSerializer::Serializer
33
+ serialize :id, :name
34
+
35
+ def name
36
+ "#{object.first_name} #{object.last_name}"
37
+ end
38
+ end
39
+
40
+ PersonSerializer.new(person).as_json # => {:id => 1, :name => "John Doe"}
41
+ ```
42
+
43
+ You can alias fields so the serialized field name is different than the internal field name. You can also turn off creating the delegation method if it isn't needed for a field.
44
+
45
+ ```ruby
46
+ class PersonSerializer
47
+ include FastSerializer::Serializer
48
+ serialize :id, as: :person_id
49
+ serialize :name, :delegate => false
50
+
51
+ def name
52
+ "#{object.first_name} #{object.last_name}"
53
+ end
54
+ end
55
+
56
+ PersonSerializer.new(person).as_json # => {:person_id => 1, :name => "John Doe"}
57
+ ```
58
+
59
+ You can specify a serializer to use on fields that return complex objects.
60
+
61
+ ```ruby
62
+ class PersonSerializer
63
+ include FastSerializer::Serializer
64
+ serialize :id
65
+ serialize :name, :delegate => false
66
+ serialize :parent, serializer: PersonSerializer
67
+ serialize :children, serializer: PersonSerializer, enumerable: true
68
+
69
+ def name
70
+ "#{object.first_name} #{object.last_name}"
71
+ end
72
+ end
73
+
74
+ person.parent = Person.new(:id => 2, :first_name => "Sally", :last_name => "Smith")
75
+ person.children << Person.new(:id => 3, :first_name => "Jane", :last_name => "Doe")
76
+ PersonSerializer.new(person).as_json # => {
77
+ # :id => 1,
78
+ # :name => "John Doe",
79
+ # :parent => {:id => 2, :name => "Sally Smith"},
80
+ # :children => [{:id => 3, :name => "Jane Doe"}]
81
+ # }
82
+ ```
83
+
84
+ Serializer can have optional fields. You can also specify fields to exclude.
85
+
86
+ ```ruby
87
+ class PersonSerializer
88
+ include FastSerializer::Serializer
89
+ serialize :id
90
+ serialize :name, :delegate => false
91
+ serialize :gender, optional: true
92
+
93
+ def name
94
+ "#{object.first_name} #{object.last_name}"
95
+ end
96
+ end
97
+
98
+ PersonSerializer.new(person).as_json # => {:id => 1, :name => "John Doe"}
99
+ PersonSerializer.new(person, :include => [:gender]).as_json # => {:id => 1, :name => "John Doe", :gender => "M"}
100
+ PersonSerializer.new(person, :exclude => [:id]).as_json # => {:name => "John Doe"}
101
+ ```
102
+
103
+ You can specify custom options that control how the object is serialized.
104
+
105
+ ```ruby
106
+ class PersonSerializer
107
+ include FastSerializer::Serializer
108
+ serialize :id
109
+ serialize :name, :delegate => false
110
+
111
+ def name
112
+ if option(:last_first)
113
+ "#{object.last_name}, #{object.first_name}"
114
+ else
115
+ "#{object.first_name} #{object.last_name}"
116
+ end
117
+ end
118
+ end
119
+
120
+ PersonSerializer.new(person).as_json # => {:id => 1, :name => "John Doe"}
121
+ PersonSerializer.new(person, :last_first).as_json # => {:id => 1, :name => "Doe, John"}
122
+ ```
123
+
124
+ You can make serializers cacheable so that the serialized value can be stored and fetched from a cache.
125
+
126
+ ```ruby
127
+ class PersonSerializer
128
+ include FastSerializer::Serializer
129
+ serialize :id
130
+ serialize :name, :delegate => false
131
+
132
+ cacheable true, ttl: 60
133
+
134
+ def name
135
+ if option(:last_first)
136
+ "#{object.last_name}, #{object.first_name}"
137
+ else
138
+ "#{object.first_name} #{object.last_name}"
139
+ end
140
+ end
141
+ end
142
+
143
+ FastSerializer.cache = MyCache.new # Must be an implementation of FastSerializer::Cache
144
+ ```
145
+
146
+ ## Performance
147
+
148
+ Your mileage may vary. In many cases the performance of the serialization code doesn't particularly matter and this gem performs just about as well as other solutions. However, if you do have high throughput API or can utilize the caching features or have heavily nested models in your JSON responses, then the performance increase may be noticeable.
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc 'Default: run unit tests.'
4
+ task :default => :test
5
+
6
+ desc 'RVM likes to call it tests'
7
+ task :tests => :test
8
+
9
+ begin
10
+ require 'rspec'
11
+ require 'rspec/core/rake_task'
12
+ desc 'Run the unit tests'
13
+ RSpec::Core::RakeTask.new(:test)
14
+ rescue LoadError
15
+ task :test do
16
+ STDERR.puts "You must have rspec >= 2.0 installed to run the tests"
17
+ end
18
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "fast_serializer"
7
+ spec.version = File.read(File.expand_path("../VERSION", __FILE__)).chomp
8
+ spec.authors = ["We Heart It", "Brian Durand"]
9
+ spec.email = ["dev@weheartit.com", "bbdurand@gmail.com"]
10
+ spec.description = %q{Super fast object serialization for API's combining a simple DSL with many optimizations under the hood.}
11
+ spec.summary = %q{Super fast object serialization for API's.}
12
+ spec.homepage = "https://github.com/weheartit/fast_serializer"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~>1.3"
21
+ spec.add_development_dependency "rake"
22
+ spec.add_development_dependency "rspec", "~>3.0"
23
+ end
@@ -0,0 +1,37 @@
1
+ require 'json'
2
+ require 'time'
3
+ require 'date'
4
+
5
+ module FastSerializer
6
+ require_relative 'fast_serializer/cache'
7
+ require_relative 'fast_serializer/cache/active_support_cache'
8
+ require_relative 'fast_serializer/serialization_context'
9
+ require_relative 'fast_serializer/serialized_field'
10
+ require_relative 'fast_serializer/serializer'
11
+ require_relative 'fast_serializer/array_serializer'
12
+
13
+ class << self
14
+ # Get the global cache implementation used for storing cacheable serializers.
15
+ def cache
16
+ @cache if defined?(@cache)
17
+ end
18
+
19
+ # Set the global cache implementation used for storing cacheable serializers.
20
+ # The cache implementation should implement the +fetch+ method as defined in
21
+ # FastSerializer::Cache. By default no cache is set so caching won't do anything.
22
+ #
23
+ # In a Rails app, you can initialize the cache by simply passing in the value :rails
24
+ # to use the default Rails.cache.
25
+ def cache=(cache)
26
+ cache = Cache::ActiveSupportCache.new(Rails.cache) if cache == :rails
27
+ @cache = cache
28
+ end
29
+ end
30
+
31
+ # Exception raised when there is a circular reference serializing a model dependent on itself.
32
+ class CircularReferenceError < StandardError
33
+ def initialize(model)
34
+ super("Circular refernce on #{model.inspect}")
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,92 @@
1
+ module FastSerializer
2
+ # Serializer implementation for serializing an array of objects.
3
+ # This class allows taking advantage of a single SerializationContext
4
+ # for caching duplicate serializers.
5
+ class ArraySerializer
6
+ include Serializer
7
+
8
+ serialize :array
9
+
10
+ def initialize(object, options = nil)
11
+ super(Array(object), options)
12
+ end
13
+
14
+ def cache_key
15
+ if option(:serializer)
16
+ array.collect(&:cache_key)
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def cacheable?
23
+ if option(:cacheable) || self.class.cacheable?
24
+ true
25
+ elsif option(:serializer)
26
+ option(:serializer).cacheable?
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def cache_ttl
33
+ if option(:cache_ttl)
34
+ true
35
+ elsif option(:serializer)
36
+ option(:serializer).cache_ttl
37
+ else
38
+ super
39
+ end
40
+ end
41
+
42
+ def cache
43
+ if option(:cache)
44
+ true
45
+ elsif option(:serializer)
46
+ option(:serializer).cache
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ def as_json(*args)
53
+ if array.nil?
54
+ nil
55
+ elsif array.empty?
56
+ []
57
+ else
58
+ super[:array]
59
+ end
60
+ end
61
+
62
+ undef :to_hash
63
+ undef :to_h
64
+ alias :to_a :as_json
65
+
66
+ protected
67
+
68
+ def load_from_cache
69
+ if cache
70
+ values = cache.fetch_all(array, cache_ttl){|serializer| serializer.as_json}
71
+ {:array => values}
72
+ else
73
+ load_hash
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def array
80
+ unless defined?(@_array)
81
+ serializer = option(:serializer)
82
+ if serializer
83
+ serializer_options = option(:serializer_options)
84
+ @_array = object.collect{|obj| serializer.new(obj, serializer_options)}
85
+ else
86
+ @_array = object
87
+ end
88
+ end
89
+ @_array
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,22 @@
1
+ module FastSerializer
2
+ # Base class for cache implementations for storing cacheable serializers.
3
+ # Implementations must implement the +fetch+ method.
4
+ class Cache
5
+ def fetch(serializer, ttl, &block)
6
+ raise NotImplementedError
7
+ end
8
+
9
+ # Fetch multiple serializers from the cache. The default behavior is just
10
+ # to call +fetch+ with each serializer. Implementations may optimize this
11
+ # if the cache can return multiple values at once.
12
+ #
13
+ # The block to this method will be yielded to with each uncached serializer.
14
+ def fetch_all(serializers, ttl)
15
+ serializers.collect do |serializer|
16
+ fetch(serializer, ttl) do
17
+ yield(serializer)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module FastSerializer
2
+ # ActiveSupport compatible cache implementation.
3
+ class Cache::ActiveSupportCache < Cache
4
+ attr_reader :cache
5
+
6
+ def initialize(cache)
7
+ @cache = cache
8
+ end
9
+
10
+ def fetch(serializer, ttl, &block)
11
+ exists = !!@cache.read(serializer.cache_key)
12
+ @cache.fetch(serializer.cache_key, :expires_in => ttl, &block)
13
+ end
14
+
15
+ def fetch_all(serializers, ttl)
16
+ @cache.fetch_multi(*serializers) do |serializer|
17
+ yield(serializer)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,70 @@
1
+ module FastSerializer
2
+ # This class provides a context for creating serializers that allows
3
+ # duplicate serializers to be re-used within the context. This then
4
+ # short circuits the serialization process on the duplicates.
5
+ class SerializationContext
6
+ class << self
7
+ # Use a context or create one for use within a block. Any serializers
8
+ # based on the same object with the same options within the block will be
9
+ # re-used instead of creating duplicates.
10
+ def use
11
+ if Thread.current[:fast_serializer_context]
12
+ yield
13
+ else
14
+ begin
15
+ Thread.current[:fast_serializer_context] = new
16
+ yield
17
+ ensure
18
+ Thread.current[:fast_serializer_context] = nil
19
+ end
20
+ end
21
+ end
22
+
23
+ # Return the current context or nil if none is in use.
24
+ def current
25
+ Thread.current[:fast_serializer_context]
26
+ end
27
+ end
28
+
29
+ def initialize
30
+ @cache = nil
31
+ @references = nil
32
+ end
33
+
34
+ # Returns a serializer from the context cache if a duplicate has already
35
+ # been created. Otherwise creates the serializer and adds it to the
36
+ # cache.
37
+ def load(serializer_class, object, options = nil)
38
+ key = [serializer_class, object, options]
39
+ serializer = nil
40
+ if @cache
41
+ serializer = @cache[key]
42
+ end
43
+
44
+ unless serializer
45
+ serializer = serializer_class.allocate
46
+ serializer.send(:initialize, object, options)
47
+ @cache ||= {}
48
+ @cache[key] = serializer
49
+ end
50
+
51
+ serializer
52
+ end
53
+
54
+ # Maintain reference stack to avoid circular references.
55
+ def with_reference(object)
56
+ if @references
57
+ raise CircularReferenceError.new(object) if @references.include?(object)
58
+ else
59
+ @references = []
60
+ end
61
+
62
+ begin
63
+ @references.push(object)
64
+ yield
65
+ ensure
66
+ @references.pop
67
+ end
68
+ end
69
+ end
70
+ end