tiny_serializer 2.0.2

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b3f2930aab86eaf43af0326633d4698fdd4f1e51
4
+ data.tar.gz: 297b7d9fb72d0075853448ec056e7ea596c2e4fd
5
+ SHA512:
6
+ metadata.gz: 6c3ab366f4a4e5bcabc111d78ad10eae27ce23c23f54d2f009963b09005adae9dfcbb534755352ba7750923db7e099cdf297f6f1e8c9177df98ffc9cc4ae86bf
7
+ data.tar.gz: 959b2a9e76f0303615dab63a52cb52a76b3861362832530139e196e583b91bdc9c1110bd90b1fc9e76c99c18da0fb6dc182296b3059d865dc7d6c44cfcfebbda
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ .idea
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /html/
11
+
12
+ .DS_Store
13
+ Thumbs.db
14
+ ._*
15
+ ~*
16
+ *~
17
+ *.swp
18
+ .vscode
data/.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,23 @@
1
+ ---
2
+ AllCops:
3
+ TargetRubyVersion: 2.4
4
+
5
+ Style/StringLiterals:
6
+ EnforcedStyle: double_quotes
7
+ Exclude:
8
+ - "**/*_spec.rb"
9
+
10
+ # We might want to make this work on an old Ruby sometime.
11
+ Style/SafeNavigation:
12
+ Enabled: false
13
+
14
+ Metrics/BlockLength:
15
+ Exclude:
16
+ - "**/*_spec.rb"
17
+
18
+ Layout/EmptyLineAfterMagicComment:
19
+ Enabled: false
20
+
21
+ Layout/EmptyLines:
22
+ Exclude:
23
+ - "**/*_spec.rb"
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.4.5
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.5
5
+ - 2.5.3
6
+ before_install: gem install bundler -v 1.17.1
7
+ bundler_args:
8
+ script:
9
+ - bundle exec rake spec
10
+ - bundle exec rake build
data/CHANGELOG.md ADDED
@@ -0,0 +1,73 @@
1
+ # SimpleSerializer Change Log
2
+
3
+ ## 2.0.2
4
+
5
+ * Fix major incorrect require statement.
6
+
7
+ ## 2.0.1
8
+
9
+ * Bump version so I can publish under the correct rubygems account.
10
+
11
+ ## 2.0.0
12
+
13
+ * Rename to "tiny_serializer" and push to rubygems.org.
14
+ * Update .travis.yml ruby versions to 2.4.5 and 2.5.3
15
+ * Minor rubocop config tweaks and add frozen\_string_literal to specs.
16
+
17
+ ## 1.0.0
18
+
19
+ * Remove netflix/fast_jsonapi NOOP compatibily methods.
20
+ * Rubocop style improvements.
21
+
22
+ ## 0.5.3
23
+
24
+ * Raise ArgumentError if serialize_each called without a collection.
25
+ (Helps find unexpected values earlier in testing.)
26
+
27
+ ## 0.5.2
28
+
29
+ * Fix place I forgot to create a new serializer instance.
30
+
31
+ ## 0.5.1
32
+
33
+ * Fix `key` arguments not being passed around correctly.
34
+ * Minor code clarity improvements.
35
+
36
+ ## 0.5.0
37
+
38
+ * Pass options to has_many definition block.
39
+ * Use a new serializer instance to serialize each item in a collection.
40
+
41
+ ## 0.4.0
42
+
43
+ * Implement options parameter (from AMS).
44
+ * Raises ArgumentError if a specified serializer is not actually a
45
+ SimpleSerializer subclass.
46
+ * Add note to README.md about attribute inheritance.
47
+
48
+ ## 0.3.1
49
+
50
+ * Fix bug where collections would be checked for existence before serialization,
51
+ causing unnecessary database queries.
52
+
53
+ ## 0.3.0
54
+
55
+ * Fix blocks not being passed on to the #sub_record method.
56
+ * Major documentation improvements.
57
+ * Code cleanup and simplification.
58
+ * Travis CI config improvements.
59
+ * Allow #as_json and #to_json to take arguments.
60
+
61
+ ## 0.2.1
62
+
63
+ * Remove lack of inheritance as a downside!
64
+
65
+ ## 0.2.0
66
+
67
+ * Implement serialized attribute inheritance.
68
+ * README improvements.
69
+ * Add this CHANGELOG.
70
+
71
+ ## 0.1.0
72
+
73
+ * Initial post to Reddit.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in simple_serializer.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tiny_serializer (2.0.2)
5
+ activesupport (>= 4.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (5.2.1)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (>= 0.7, < 2)
13
+ minitest (~> 5.1)
14
+ tzinfo (~> 1.1)
15
+ concurrent-ruby (1.0.5)
16
+ diff-lcs (1.3)
17
+ i18n (1.1.1)
18
+ concurrent-ruby (~> 1.0)
19
+ minitest (5.11.3)
20
+ rake (10.5.0)
21
+ rspec (3.7.0)
22
+ rspec-core (~> 3.7.0)
23
+ rspec-expectations (~> 3.7.0)
24
+ rspec-mocks (~> 3.7.0)
25
+ rspec-core (3.7.1)
26
+ rspec-support (~> 3.7.0)
27
+ rspec-expectations (3.7.0)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.7.0)
30
+ rspec-mocks (3.7.0)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.7.0)
33
+ rspec-support (3.7.1)
34
+ thread_safe (0.3.6)
35
+ tzinfo (1.2.5)
36
+ thread_safe (~> 0.1)
37
+
38
+ PLATFORMS
39
+ ruby
40
+
41
+ DEPENDENCIES
42
+ bundler (~> 1.0)
43
+ rake (~> 10.0)
44
+ rspec (~> 3.5)
45
+ tiny_serializer!
46
+
47
+ BUNDLED WITH
48
+ 1.17.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 William Makley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # TinySerializer
2
+
3
+ Simple DSL for converting objects to Hashes, which is mostly API-compatible with
4
+ [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers).
5
+ Not quite a drop-in replacement, but facilitates migrating away from it.
6
+ The resulting Hashes can be rendered as JSON by Rails using the ActiveSupport::JSON
7
+ module.
8
+
9
+ Unlike AMS, it does **not** integrate with Rails for automatic usage with the `render` function,
10
+ which has proven to be one of the biggest sources of confusion and complexity for me.
11
+ `render json: @object` will continue to work exactly as it does in a base Rails setup.
12
+
13
+ Use if you have heavily invested in [active_model_serializers](https://github.com/rails-api/active_model_serializers), but have started experiencing the same frustrations I had with it and can't transition to [jsonapi-rb](http://jsonapi-rb.org/) or [fast_jsonapi](https://github.com/Netflix/fast_jsonapi).
14
+
15
+ ![Travis CI Build Status](https://travis-ci.org/wmakley/tiny_serializer.svg?branch=master)
16
+
17
+ **Benefits:**
18
+
19
+ * Extremely simple and deterministic behavior; no adapter classes and other complications. Just turns Objects into Hashes.
20
+ * Easy to understand; uses `#as_json` to serialize attributes.
21
+ * Simple and explicit invocation.
22
+ * Does not leak memory in development.
23
+ * ~200 lines of code, give or take.
24
+ * Seems pretty darn fast, at least as fast as `public_send`, `Hash#[]=`, and `Hash#to_json` can be.
25
+
26
+ **Downsides:**
27
+
28
+ * Uses `#as_json` to serialize attributes (maybe unintended consequences, especially with complex objects).
29
+ * Requires ActiveSupport.
30
+
31
+ ## Usage
32
+
33
+ ### Serializer definition:
34
+
35
+ ```ruby
36
+ MyObject = Struct.new(:id, :first_name, :last_name, :date) do
37
+ def parent; nil; end
38
+ def sub_record; nil; end
39
+ def related_items; []; end
40
+ end
41
+
42
+ class MyObjectSerializer < TinySerializer
43
+ attributes :id,
44
+ :first_name,
45
+ :last_name,
46
+ :date
47
+
48
+ belongs_to :parent, serializer: ParentSerializer
49
+ has_one :sub_record # serializer will be inferred to be SubRecordSerializer
50
+ has_many :related_items, serializer: RelatedItemSerializer
51
+
52
+ attribute :full_name do |object|
53
+ "#{object.first_name} #{object.last_name}"
54
+ end
55
+ end
56
+ ```
57
+
58
+ **Notes on blocks:**
59
+
60
+ The `object` parameter for blocks is optional, as blocks are executed
61
+ in the context of the serializer instance. It just makes it easier
62
+ to use the [fast_jsonapi](https://github.com/Netflix/fast_jsonapi) gem later if you want.
63
+
64
+ ### Usage:
65
+
66
+ ```ruby
67
+ object = MyObject.new(...)
68
+ # Several ways to invoke the serializer are available:
69
+ MyObjectSerializer.new(object).serialize
70
+ MyObjectSerializer.serialize(object)
71
+ ```
72
+
73
+ Produces:
74
+
75
+ ```json
76
+ {
77
+ "id": 1,
78
+ "first_name": "Fred",
79
+ "last_name": "Flintstone",
80
+ "date": "2000-01-01",
81
+ "full_name": "Fred Flintstone",
82
+ "parent": null,
83
+ "sub_record": null,
84
+ "related_items": []
85
+ }
86
+ ```
87
+
88
+ ### Usage in Rails:
89
+
90
+ In Rails, calling `.serialize` is optional because **TinySerializer** implements all the 'magic' methods (`as_json`, `to_json`, and `serializable_hash`), although I'm not the biggest fan (it loses some explicitness):
91
+
92
+ ```ruby
93
+ @model = MyObject.new(1, "Fred", "Flintstone", Date.new(2000, 1, 1))
94
+ render json: MyObjectSerializer.new(@model)
95
+ ```
96
+
97
+ ### Under the Hood
98
+
99
+ Takes every attribute you define, and uses it to call `#public_send` on the object to serialize,
100
+ then uses `#as_json` to serialize the resulting value. If you define the attribute with a block, it will call the block to get the value instead.
101
+
102
+ ### Usage with Collections:
103
+
104
+ Collections are handled automatically:
105
+
106
+ ```ruby
107
+ class MyObjectSerializer < TinySerializer
108
+ attributes :id, :first_name
109
+ end
110
+
111
+ items = [
112
+ MyObject.new(1, "Fred"),
113
+ MyObject.new(2, "Wilma")
114
+ ]
115
+ MyObjectSerializer.new(items).serialize
116
+ ```
117
+
118
+ Produces:
119
+
120
+ ```json
121
+ [
122
+ {"id": 1, "name": "Fred"},
123
+ {"id": 2, "name": "Wilma"}
124
+ ]
125
+ ```
126
+
127
+ ### Defining Associations and Serializing Sub-Objects
128
+
129
+ The DSL methods `belongs_to`, `has_one`, and `has_many` are all synonyms for "use this serializer to serialize this property". They are basically syntax sugar for:
130
+
131
+ ```ruby
132
+ class MySerializer < TinySerializer
133
+ attribute :parent do |object|
134
+ ParentSerializer.new(object.parent).serialize
135
+ end
136
+ end
137
+ ```
138
+
139
+ These methods all optionally take a block, which you can use to customize the
140
+ object or collection. For example, to avoid loading every single item in a
141
+ `has_many` relation:
142
+
143
+ ```ruby
144
+ class MySerializer < TinySerializer
145
+ has_many :items, serializer: ItemSerializer do |object|
146
+ object.items.limit(100)
147
+ end
148
+ end
149
+ ```
150
+
151
+ ### Links and URL's
152
+
153
+ When used with Rails, serializer instances have the `#url_helpers`
154
+ method available.
155
+
156
+
157
+ ```ruby
158
+ class ItemSerializer < TinySerializer
159
+ attribute :link do
160
+ url_helpers.item_path(object)
161
+ end
162
+ end
163
+ ```
164
+
165
+ ### Notes on ID's.
166
+
167
+ If you don't want ID's to be serialized as numeric types,
168
+ you can have them automatically detected and coerced to Strings.
169
+
170
+ ```ruby
171
+ class MyObjectSerializer < TinySerializer
172
+ self.coerce_ids_to_string = true
173
+ end
174
+ ```
175
+
176
+ To enable globally, but opt out for a specific attribute:
177
+
178
+ ```ruby
179
+ class MyObjectSerializer < TinySerializer
180
+ self.coerce_ids_to_string = true
181
+
182
+ attribute :id, is_id: false
183
+ end
184
+
185
+ ### Notes on Options
186
+
187
+ If you want to pass in options to the serializer you can access via the second argument of the block
188
+
189
+
190
+ class MyObjectSerializer < TinySerializer
191
+ attribute :id do |object, options|
192
+ options[:prefix] + object.id
193
+ end
194
+ end
195
+ ```
196
+ MyObjectSerializer.new(object, { prefix: "prefix_" }).serialize
197
+
198
+ Produces:
199
+
200
+ {
201
+ id: "prefix_id"
202
+ }
203
+
204
+ ### Attribute Inheritance
205
+
206
+ Attributes are inherited from parent classes, but can be extended.
207
+ This example works as you would expect from [active_model_serializers](https://github.com/rails-api/active_model_serializers)
208
+ (the `name` attribute will only appear if you use `MyObjectSerializer::WithName.serialize(my_object)`):
209
+
210
+ ```ruby
211
+ class MyObjectSerializer < TinySerializer
212
+ attribute :id
213
+
214
+ class WithName < MyObjectSerializer
215
+ attribute :name
216
+ end
217
+ end
218
+ ```
219
+
220
+
221
+ ## Installation
222
+
223
+ Add this line to your application's Gemfile:
224
+
225
+ ```ruby
226
+ gem 'tiny_serializer', '~> 2.0.0'
227
+ ```
228
+
229
+ And then execute:
230
+
231
+ $ bundle
232
+
233
+ ## Contributing
234
+
235
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wmakley/tiny_serializer.
236
+
237
+ ## License
238
+
239
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ begin
9
+ require "rdoc/task"
10
+ RDoc::Task.new do |rdoc|
11
+ rdoc.main = "README.md"
12
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
13
+ end
14
+ rescue LoadError
15
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "tiny_serializer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/inflections"
4
+
5
+ class TinySerializer
6
+ # The TinySerializer class macros.
7
+ module DSL
8
+ # Get all the sub-record attribute definitions (created by #belongs_to,
9
+ # #has_one, etc.)
10
+ def sub_records
11
+ @sub_records ||=
12
+ if superclass.respond_to?(:sub_records)
13
+ superclass.sub_records.dup
14
+ else
15
+ []
16
+ end
17
+ end
18
+
19
+ # Get only the names of the sub-record attribute definitions (created by
20
+ # #belongs_to, #has_one, etc.)
21
+ def sub_record_names
22
+ sub_records.map(&:first)
23
+ end
24
+
25
+ # Get all the collection definitions (created by #has_many).
26
+ def collections
27
+ @collections ||=
28
+ if superclass.respond_to?(:collections)
29
+ superclass.collections.dup
30
+ else
31
+ []
32
+ end
33
+ end
34
+
35
+ # Get only the names of the collections definitions (created by #has_many).
36
+ def collection_names
37
+ collections.map(&:first)
38
+ end
39
+
40
+ # Definite a new attribute to serialize. The value to serialize is retrieved
41
+ # in one of two ways:
42
+ #
43
+ # 1. *Default:* Calls #public_send(name) on TinySerializer#object.
44
+ # 2. *Block:* The return value of the block is used.
45
+ #
46
+ # name::
47
+ # The name of the attribute
48
+ # key::
49
+ # Optional. Defaults to *name*. The Hash key to assign the attribute's
50
+ # value to.
51
+ # is_id::
52
+ # Optional. Whether the attribute is a database ID. Guessed from its name
53
+ # by default.
54
+ #
55
+ def attribute(name, key: name, is_id: _is_id?(name), &block)
56
+ _initialize_attributes
57
+ name = name.to_sym
58
+ attribute = [name, key, is_id, block]
59
+ @attributes << attribute
60
+ return attribute
61
+ end
62
+
63
+ # Define multiple attributes at once, using the defaults.
64
+ def attributes(*names)
65
+ if names && !names.empty?
66
+ names.each do |name|
67
+ attribute(name)
68
+ end
69
+ else
70
+ _initialize_attributes
71
+ end
72
+ return @attributes
73
+ end
74
+
75
+ # Get the names of all attributes defined using #attribute.
76
+ def attribute_names
77
+ attributes.map(&:first)
78
+ end
79
+
80
+ # Alias of #sub_record
81
+ def has_one(association_name, key: association_name, serializer: nil, &block)
82
+ sub_record(association_name, key: key, serializer: serializer, &block)
83
+ end
84
+
85
+ # Alias of #sub_record
86
+ def belongs_to(association_name, key: association_name, serializer: nil, &block)
87
+ sub_record(association_name, key: key, serializer: serializer, &block)
88
+ end
89
+
90
+ # Define a serializer to use for a sub-object of TinySerializer#object.
91
+ # <b>If given a block:</b> Will use the block to retrieve the
92
+ # object, instead of public_send(name).
93
+ #
94
+ # name::
95
+ # The method name of the sub-object.
96
+ # serializer::
97
+ # Optional. The serializer class to use. Inferred from name if blank.
98
+ # Must be a subclass of TinySerializer.
99
+ # key::
100
+ # Optional. Defaults to *name*. The Hash key to assign the sub-record's
101
+ # JSON to.
102
+ #
103
+ def sub_record(name, key: name, serializer: nil, &block)
104
+ if serializer.nil?
105
+ serializer = "#{name.to_s.camelize}Serializer".constantize
106
+ elsif !_is_serializer?(serializer)
107
+ raise ArgumentError, "#{serializer} does not appear to be a TinySerializer"
108
+ end
109
+
110
+ sub_records << [name, key, serializer, block]
111
+ end
112
+
113
+ # Alias of #collection
114
+ def has_many(name, key: name, serializer: nil, &block)
115
+ collection(name, key: key, serializer: serializer, &block)
116
+ end
117
+
118
+ # Define a serializer to use to serialize a collection of objects
119
+ # as an Array. Will call #public_send(name) on TinySerializer#object
120
+ # to get the items in the collection, or use the return value of
121
+ # the block.
122
+ #
123
+ # name::
124
+ # The name of the collection.
125
+ # key::
126
+ # Optional. Defaults to *name*. The Hash key to assign the serialized
127
+ # Array to.
128
+ # serializer::
129
+ # Optional, inferred from *collection_name*. The serializer class to use
130
+ # to serialize each item in the collection. Must be a subclass of
131
+ # TinySerializer.
132
+ #
133
+ def collection(name, key: name, serializer: nil, &block)
134
+ if serializer.nil?
135
+ serializer = "#{name.to_s.singularize.camelize}Serializer".constantize
136
+ elsif !_is_serializer?(serializer)
137
+ raise ArgumentError, "#{serializer} does not appear to be a TinySerializer"
138
+ end
139
+ collections << [name, key, serializer, block]
140
+ end
141
+
142
+ # Private method to initialize inherited attributes.
143
+ def _initialize_attributes # :nodoc:
144
+ @attributes ||=
145
+ if superclass.respond_to?(:attributes)
146
+ superclass.attributes.dup
147
+ else
148
+ []
149
+ end
150
+ end
151
+
152
+ # Private method to check if an attribute name is an ID.
153
+ def _is_id?(name) # :nodoc:
154
+ name == :id || name.to_s.end_with?("_id")
155
+ end
156
+
157
+ # Private method to check if a class is a TinySerializer subclass
158
+ def _is_serializer?(klass)
159
+ klass <= TinySerializer
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TinySerializer
4
+ # Small convenience improvements to TinySerializer
5
+ # that are automatically included as serializer instance methods
6
+ # when used in a Rails app.
7
+ module RailsExtensions
8
+ # Alias of +Rails.application.routes.url_helpers+.
9
+ def url_helpers
10
+ Rails.application.routes.url_helpers
11
+ end
12
+
13
+ # Return +Rails.logger+ if TinySerializer#logger is not set.
14
+ def logger
15
+ super || Rails.logger
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class TinySerializer
4
+ VERSION = "2.0.2"
5
+ end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tiny_serializer/version"
4
+ require "tiny_serializer/dsl"
5
+ require "active_support/json"
6
+ require "active_support/core_ext/class/attribute"
7
+
8
+ # Simple DSL for converting objects to Hashes, which is mostly API-compatible
9
+ # with ActiveModel::Serializer. The Hashes can be rendered as JSON by Rails.
10
+ #
11
+ # I have also added some fast_jsonapi[https://github.com/Netflix/fast_jsonapi]
12
+ # API parameters that do nothing, to ease later transition to that library.
13
+ #
14
+ # == Usage
15
+ #
16
+ # # my_object.rb
17
+ # MyObject = Struct.new(:id, :first_name, :last_name, :date) do
18
+ # def parent; nil; end
19
+ # def sub_record; nil; end
20
+ # def related_items; []; end
21
+ # end
22
+ #
23
+ # # my_object_serializer.rb
24
+ # class MyObjectSerializer < TinySerializer
25
+ # attributes :id,
26
+ # :first_name,
27
+ # :last_name,
28
+ # :date
29
+ #
30
+ # belongs_to :parent, serializer: ParentSerializer
31
+ # has_one :sub_record # serializer will be inferred to be SubRecordSerializer
32
+ # has_many :related_items, serializer: RelatedItemSerializer
33
+ #
34
+ # attribute :full_name do
35
+ # "#{object.first_name} #{object.last_name}"
36
+ # end
37
+ # end
38
+ #
39
+ # # my_objects_controller.rb
40
+ # def show
41
+ # @my_object = MyObject.new(1, "Fred", "Flintstone", Date.new(2000, 1, 1))
42
+ # render json: MyObjectSerializer.new(@my_object).serialize
43
+ # end
44
+ #
45
+ # == RailsExtensions
46
+ #
47
+ # The RailsExtensions module is automatically prepended when TinySerializer
48
+ # is used in a Rails app. It defines some small convenience instance methods.
49
+ #
50
+ class TinySerializer
51
+ extend DSL
52
+
53
+ if defined?(Rails)
54
+ require "tiny_serializer/rails_extensions"
55
+ prepend RailsExtensions
56
+ end
57
+
58
+ # Whether to automatically convert "\*_id" properties to String.
59
+ # *default:* _false_
60
+ class_attribute :coerce_ids_to_string, default: false
61
+
62
+ # The object to serialize as a Hash
63
+ attr_accessor :object
64
+
65
+ # Hash of data to be passed to blocks
66
+ attr_accessor :options
67
+
68
+ # Optional logger object. Defaults to Rails.logger in a Rails app.
69
+ attr_accessor :logger
70
+
71
+ # Create a new serializer instance.
72
+ #
73
+ # object::
74
+ # The object to serialize. Can be a single object or a collection of
75
+ # objects.
76
+ def initialize(object, options = {})
77
+ @object = object
78
+ @options = options
79
+ end
80
+
81
+ # Serialize #object as a Hash with Symbol keys.
82
+ # Returns nil if #object is nil.
83
+ def serializable_hash(_ = nil)
84
+ return serialize_single_object_to_hash unless collection?
85
+
86
+ json = []
87
+ @object.each do |object|
88
+ json << self.class.new(object, @options).serializable_hash
89
+ end
90
+ json
91
+ end
92
+
93
+ alias to_hash serializable_hash
94
+ alias serialize serializable_hash
95
+
96
+ # Serialize #object as a Hash, then call #as_json on the Hash,
97
+ # which will convert keys to Strings.
98
+ #
99
+ # <b>There shouldn't be a need to call this, but we implement it to fully
100
+ # support ActiveSupport's magic JSON protocols.</b>
101
+ def as_json(_ = nil)
102
+ serializable_hash.as_json
103
+ end
104
+
105
+ # Serialize #object to a JSON String.
106
+ #
107
+ # Calls #serializable_hash, then call #to_json on the resulting Hash,
108
+ # converting it to a String using the automatic facilities for doing so
109
+ # from ActiveSupport.
110
+ def to_json(_ = nil)
111
+ serializable_hash.to_json
112
+ end
113
+
114
+ # Return true if #object is a collection.
115
+ def collection?
116
+ self.class.is_collection?(@object)
117
+ end
118
+
119
+ # = Class Methods
120
+ class << self
121
+ # The same as:
122
+ #
123
+ # TinySerializer.new(object).serializable_hash
124
+ def serialize(object)
125
+ new(object).serializable_hash
126
+ end
127
+
128
+ # Same as #serialize, but raises ArgumentError if `collection`
129
+ # doesn't appear to be a collection.
130
+ def serialize_each(collection)
131
+ raise ArgumentError, "collection does not appear to be a collection" unless is_collection?(collection)
132
+ new(collection).serializable_hash
133
+ end
134
+
135
+ # Check if #object is a collection.
136
+ def is_collection?(object)
137
+ object.respond_to?(:each) && !object.respond_to?(:each_pair)
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ # Private serialization implementation for a single object.
144
+ # @object must be a single object.
145
+ def serialize_single_object_to_hash
146
+ return nil if @object.nil?
147
+
148
+ hash = {}
149
+ serialize_attributes(hash)
150
+ serialize_sub_records(hash)
151
+ serialize_collections(hash)
152
+ hash
153
+ end
154
+
155
+ def serialize_attributes(hash)
156
+ self.class.attributes.each do |name, key, is_id, block|
157
+ raw_value = get_attribute(name, block)
158
+ hash[key] = serialize_attribute(raw_value, is_id)
159
+ end
160
+ end
161
+
162
+ def get_attribute(name, block)
163
+ if block
164
+ instance_exec(@object, @options, &block)
165
+ else
166
+ @object.public_send(name)
167
+ end
168
+ end
169
+
170
+ # Internal algorithm to convert any object to a valid JSON string, scalar,
171
+ # object, array, etc. All objects are passed through this function after they
172
+ # are retrieved from #object. Currently just calls #as_json.
173
+ def serialize_attribute(raw_value, is_id = false)
174
+ if is_id && coerce_ids_to_string?
175
+ serialize_id(raw_value)
176
+ else
177
+ raw_value.as_json
178
+ end
179
+ end
180
+
181
+ def serialize_id(id)
182
+ id && id.to_s
183
+ end
184
+
185
+ def serialize_sub_records(hash)
186
+ self.class.sub_records.each do |name, key, serializer, block|
187
+ record = get_attribute(name, block)
188
+ hash[key] = serializer.new(record, @options).serializable_hash
189
+ end
190
+ end
191
+
192
+ def serialize_collections(hash)
193
+ self.class.collections.each do |collection_name, key, serializer, block|
194
+ collection = get_collection(collection_name, block)
195
+
196
+ json_array = []
197
+ collection.each do |object|
198
+ json_array << serializer.new(object, @options).serializable_hash
199
+ end
200
+
201
+ hash[key] = json_array
202
+ end
203
+ end
204
+
205
+ # Get the collection from #object or block, or [] if nil.
206
+ def get_collection(name, block)
207
+ if block
208
+ instance_exec(@object, @options, &block)
209
+ else
210
+ @object.public_send(name)
211
+ end || []
212
+ end
213
+ end
@@ -0,0 +1,31 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "tiny_serializer/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tiny_serializer"
8
+ spec.version = TinySerializer::VERSION
9
+ spec.authors = ["William Makley"]
10
+ spec.email = ["william@pioneerstreet.com"]
11
+
12
+ spec.summary = %q{Tiny Ruby JSON Serialization DSL}
13
+ # spec.description = %q{TODO: Write a longer description or delete this line.}
14
+ spec.homepage = "https://github.com/wmakley/simple_serializer.git"
15
+ spec.license = "MIT"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "activesupport", ">= 4.2"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.0"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec", "~> 3.5"
31
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tiny_serializer
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.2
5
+ platform: ruby
6
+ authors:
7
+ - William Makley
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-11-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.5'
69
+ description:
70
+ email:
71
+ - william@pioneerstreet.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".idea/vcs.xml"
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - ".ruby-version"
81
+ - ".travis.yml"
82
+ - CHANGELOG.md
83
+ - Gemfile
84
+ - Gemfile.lock
85
+ - LICENSE.txt
86
+ - README.md
87
+ - Rakefile
88
+ - bin/console
89
+ - bin/setup
90
+ - lib/tiny_serializer.rb
91
+ - lib/tiny_serializer/dsl.rb
92
+ - lib/tiny_serializer/rails_extensions.rb
93
+ - lib/tiny_serializer/version.rb
94
+ - tiny_serializer.gemspec
95
+ homepage: https://github.com/wmakley/simple_serializer.git
96
+ licenses:
97
+ - MIT
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.6.14.3
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: Tiny Ruby JSON Serialization DSL
119
+ test_files: []