ruby-serializer 0.0.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76d0570d6c6888182f54fad8d93447115da9b15d
4
- data.tar.gz: fadc5ff494af8792ba60f1c76b90cc822ed90f4c
3
+ metadata.gz: 64873bd0b35de8defd662d6bd92bb9b52100937e
4
+ data.tar.gz: a735bbb0a3e92c063bbef5be1c1b58dac515cfd2
5
5
  SHA512:
6
- metadata.gz: 0d35417352d902d4e46cd7de29e7151fe6024b784f6b46a0029bf38cd4db8545c8e559f6f71a82e9d75dbcefadf3411eab517a7b60ab4dc39baa23aeaae2313a
7
- data.tar.gz: 5a0ea330adc5f8ad4cdac778524677342547f30888fadadae3f527b2a685070e6f32557a8854d6a759647747638a6f60593a9224967241cda736521663ed6c35
6
+ metadata.gz: da5bf86ac16016adec3d8951000c0846f488408e67826afc85395bf273456854319e35289bb083377404c4863c276fa9e650ba716b34cae6e5090206ec7d6fce
7
+ data.tar.gz: d26a542f36175f9638a29091fe03250ae7272cacc1cff5e5a52f8ba7bf91a052ccf875921fd47f0971443f57f290ad07475e8a482563e86fb31a8c8df29538f7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-serializer (0.0.1)
4
+ ruby-serializer (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
@@ -0,0 +1,24 @@
1
+ module RubySerializer
2
+ class Association < Field
3
+
4
+ #----------------------------------------------------------------------------------------------
5
+
6
+ def serialize(resource, serializer)
7
+ includes = serializer.send(:includes)[field]
8
+ association = resource.send(field)
9
+ if association
10
+ RubySerializer.as_json(association, include: includes)
11
+ end
12
+ end
13
+
14
+ #----------------------------------------------------------------------------------------------
15
+
16
+ def present?(resource, serializer)
17
+ super &&
18
+ serializer.send(:include?, field)
19
+ end
20
+
21
+ #----------------------------------------------------------------------------------------------
22
+
23
+ end
24
+ end
@@ -3,8 +3,7 @@ module RubySerializer
3
3
 
4
4
  extend RubySerializer::Dsl
5
5
 
6
- attr_reader :options, :include # only populated during #serialize and (temporarily) stores the #serialize options to make
7
- # them available to any potential :value, :only and :unless lambdas in the derived serializer
6
+ attr_reader :options
8
7
 
9
8
  def initialize(resource)
10
9
  @resource = resource
@@ -23,6 +22,11 @@ module RubySerializer
23
22
  private
24
23
 
25
24
  attr_reader :resource
25
+ attr_reader :includes
26
+
27
+ def include?(association)
28
+ includes.key?(association.to_sym)
29
+ end
26
30
 
27
31
  def serialize(options = {})
28
32
  if resource.respond_to? :to_ary
@@ -40,7 +44,8 @@ module RubySerializer
40
44
  end
41
45
 
42
46
  def serialize_object(options)
43
- @options = options
47
+ @options = options
48
+ @includes = expand_includes(options[:include])
44
49
  json = {}
45
50
  json[:errors] = resource.errors if resource.respond_to?(:valid?) && !resource.valid?
46
51
  self.class.fields.each do |field|
@@ -58,5 +63,28 @@ module RubySerializer
58
63
 
59
64
  #----------------------------------------------------------------------------------------------
60
65
 
66
+ def expand_includes(includes, options = {})
67
+
68
+ return includes if includes.is_a?(Hash) # already expanded
69
+
70
+ includes = case includes
71
+ when nil then []
72
+ when Array then includes
73
+ when Symbol then [ includes ]
74
+ when String then includes.split(',')
75
+ else
76
+ raise ArgumentError, "unexpected includes #{includes.class}"
77
+ end
78
+
79
+ includes.each_with_object({}) do |i, hash|
80
+ i.to_s.downcase.strip.split('.').reduce(hash) do |h, part|
81
+ h[part.to_sym] ||= {}
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ #----------------------------------------------------------------------------------------------
88
+
61
89
  end
62
90
  end
@@ -1,6 +1,8 @@
1
1
  module RubySerializer
2
2
  module Dsl
3
3
 
4
+ #----------------------------------------------------------------------------------------------
5
+
4
6
  def serializes(name)
5
7
  define_method(name) do # provide a friendly-name accessor to the underlying resource
6
8
  resource
@@ -11,8 +13,17 @@ module RubySerializer
11
13
  fields << Field.new(field, namespace, options)
12
14
  end
13
15
 
14
- def fields
15
- @fields ||= []
16
+ def belongs_to(field, options = {})
17
+ fields << Field.new("#{field}_id", namespace, options)
18
+ fields << Association.new(field, namespace, options)
19
+ end
20
+
21
+ def has_many(field, options = {})
22
+ fields << Association.new(field, namespace, options)
23
+ end
24
+
25
+ def has_one(field, options = {})
26
+ fields << Association.new(field, namespace, options)
16
27
  end
17
28
 
18
29
  def namespace(ns = nil, &block)
@@ -23,5 +34,11 @@ module RubySerializer
23
34
  @namespace.pop
24
35
  end
25
36
 
37
+ def fields
38
+ @fields ||= []
39
+ end
40
+
41
+ #----------------------------------------------------------------------------------------------
42
+
26
43
  end
27
44
  end
@@ -5,8 +5,8 @@ module RubySerializer
5
5
 
6
6
  def initialize(field, namespace, options)
7
7
  @field = field.to_sym
8
- @as = options[:as] || field
9
- @from = options[:from] || field
8
+ @as = options[:as] || @field
9
+ @from = options[:from] || @field
10
10
  @value = options[:value]
11
11
  @only = options[:only]
12
12
  @unless = options[:unless]
@@ -2,16 +2,17 @@ module RubySerializer
2
2
 
3
3
  #------------------------------------------------------------------------------------------------
4
4
 
5
- VERSION = '0.0.1'
5
+ VERSION = '1.0.0'
6
6
  SUMMARY = 'Serialize POROs to JSON'
7
7
  DESCRIPTION = 'A general purpose library for serializing plain old ruby objects (POROs) into JSON using a declarative DSL'
8
8
  LIB = File.dirname(__FILE__)
9
9
 
10
10
  #------------------------------------------------------------------------------------------------
11
11
 
12
- autoload :Dsl, File.join(LIB, 'ruby_serializer/dsl')
13
- autoload :Base, File.join(LIB, 'ruby_serializer/base')
14
- autoload :Field, File.join(LIB, 'ruby_serializer/field')
12
+ autoload :Dsl, File.join(LIB, 'ruby_serializer/dsl')
13
+ autoload :Base, File.join(LIB, 'ruby_serializer/base')
14
+ autoload :Field, File.join(LIB, 'ruby_serializer/field')
15
+ autoload :Association, File.join(LIB ,'ruby_serializer/association')
15
16
 
16
17
  #------------------------------------------------------------------------------------------------
17
18
 
data/readme.md CHANGED
@@ -6,7 +6,7 @@ into JSON using a declarative DSL.
6
6
  ## Installation
7
7
 
8
8
  ```bash
9
- $ gem install ruby-serializer # ... or add to your Gemfile as appropriate
9
+ $ gem install ruby-serializer # ... or add to your Gemfile as appropriate
10
10
  ```
11
11
 
12
12
  ## Usage
@@ -38,6 +38,21 @@ And serialize using...
38
38
  # { size: 'large', color: 'red', shape: 'square' }
39
39
  ```
40
40
 
41
+ ### Serializer naming convention
42
+
43
+ By default, `ruby-serializer` will look for a serializer with the following naming convention
44
+
45
+ ```ruby
46
+ "#{resource.class.name}Serializer"
47
+ ```
48
+
49
+ But you can always specify the serializer class explicitly:
50
+
51
+ ```ruby
52
+ shape = Shape.new(...)
53
+ json = RubySerializer.as_json shape, with: MyCustomSerializer
54
+ ```
55
+
41
56
  ## Beyond the basics
42
57
 
43
58
  Ok, that was very basic, but with RubySerializer you can also:
@@ -50,54 +65,153 @@ Ok, that was very basic, but with RubySerializer you can also:
50
65
  * `:include` nested `:belongs_to` associated models
51
66
  * `:include` nested `:has_one` associated models
52
67
  * `:include` nested `:has_many` associated models
53
- * serialize an array of models
54
- * add `ActionController` integration for easy API serialization
55
68
 
56
69
  ### Exposing attributes with another name
57
70
 
58
- >> _TODO_
71
+ You can expose an attribute with a different name using `:as`
72
+
73
+ ```ruby
74
+ class UserSerializer < RubySerializer::Base
75
+ expose :id
76
+ expose :name, as: :user_name
77
+ expose :email, as: :user_email
78
+ end
79
+
80
+ user = User.new(id: 42, name: 'Jake', email: 'jake@codeincomplete.com')
81
+ json = RubySerializer.as_json user
82
+
83
+ # { id: 42, user_name: 'Jake', user_email: 'jake@codeincomplete.com' }
84
+ ```
59
85
 
60
86
  ### Exposing attributes with custom values
61
87
 
62
- >> _TODO_
88
+ You can expose an attribute with a different value using `:value`
89
+
90
+ ```ruby
91
+ class UserSerializer < RubySerializer::Base
92
+ expose :id
93
+ expose :static, value: 'static' # expose a static attribute
94
+ expose :dynamic, value: -> { resource.name.reverse } # expose a dynamic attribute using a lambda
95
+ expose :method, value: :name # expose a dynamic attribute using a symbol (calls a method on the underlying resource automatically)
96
+ end
97
+
98
+ user = User.new(id: 42, name: 'Jake')
99
+ json = RubySerializer.as_json user
100
+
101
+ # { id: 42, static: 'static', dynamic: 'ekaJ', method: 'Jake' }
102
+ ```
63
103
 
64
104
  ### Exposing attributes conditionally
65
105
 
66
- >> _TODO_
106
+ You can expose attributes conditionally using `:only` and `:unless`
107
+
108
+ ```ruby
109
+ class UserSerializer < RubySerializer::Base
110
+ expose :id
111
+ expose :admin, only: :admin? # expose this field only when resource.admin?
112
+ expose :name, only: -> { resource.name.present? } # expose this field only when resource.name.present?
113
+ expose :email, unless: -> { resource.email.blank? } # expose this field unless resource.email.blank?
114
+ end
115
+
116
+ user = User.new(id: 1)
117
+ json = RubySerializer.as_json user
118
+
119
+ # { id: 1 }
120
+
121
+ user = User.new(id: 2, name: 'Joe')
122
+ json = RubySerializer.as_json user
123
+
124
+ # { id: 2, name: 'Joe' }
125
+
126
+ user = User.new(id: 3, name: 'Bob', admin: true)
127
+ json = RubySerializer.as_json user
128
+
129
+ # { id: 3, name: 'Bob', admin: true }
130
+
131
+ ```
67
132
 
68
133
  ### Exposing attributes within a namespace
69
134
 
70
- >> _TODO_
135
+ You can nest serialized attributes using a `:namespace`:
71
136
 
72
- ### Exposing model validation errors
137
+ ```ruby
138
+ class UserSerializer < RubySerializer::Base
139
+ expose :id
140
+ namespace :i18n do
141
+ expose :locale
142
+ expose :time_zone
143
+ end
144
+ end
145
+
146
+ user = User.new(id: 42, locale: 'en-us', time_zone: 'UTC')
147
+ json = RubySerializer.as_json user
73
148
 
74
- >> _TODO_
149
+ # { id: 42, i18n: { locale: 'en-us', time_zone: 'UTC' } }
150
+ ```
75
151
 
76
152
  ### Exposing associations
77
153
 
78
- >> _TODO_
154
+ If your models have ActiveRecord like associations you can declare them in your serializers and
155
+ then use the optional `:include` mechanism to choose when to include them as nested resources
156
+ during serialization:
157
+
158
+ ```ruby
159
+ class Publisher
160
+ attr :id, :name
161
+ has_many :books
162
+ end
163
+
164
+ class Book
165
+ attr :isbn, :name
166
+ belongs_to :publisher
167
+ end
168
+
169
+ class PublisherSerializer
170
+ expose :id
171
+ expose :name
172
+ has_many :books
173
+ end
174
+
175
+ class BookSerializer
176
+ expose :isbn
177
+ expose :name
178
+ belongs_to :publisher
179
+ end
180
+
181
+ publisher = Publisher.new(id: 42, name: 'Addison Wesley')
182
+
183
+ RubySerializer.as_json publisher # WITHOUT :include
184
+
185
+ # { id: 42, name: 'Addison Wesley' }
186
+
187
+ RubySerializer.as_json publisher, include: :books # WITH :include
79
188
 
80
- ### Serializing arrays
189
+ # { id: 42, name: 'Addison Wesley', books: [
190
+ # { isbn: '020161622X', name: 'Pragmatic Programmer' },
191
+ # { isbn: '0201633612', name: 'Design Patterns '},
192
+ # ...
193
+ # ] }
194
+ ```
81
195
 
82
- >> _TODO_
196
+ `ruby-serializer` supports `belongs_to`, `has_one`, and `has_many` associations.
83
197
 
84
- ### ActionController integration
198
+ You can include multiple associations using an array of includes:
85
199
 
86
- >> _TODO_
200
+ ```ruby
201
+ RubySerializer.as_json publisher, include: [ :books, :address, :websites ]
202
+ ```
87
203
 
88
- # TODO
204
+ You can also include nested associations:
89
205
 
90
- * :belongs_to
91
- * :has_one
92
- * :has_many
93
- * :includes
94
- * ActionController integration
95
- * documentation
206
+ ```ruby
207
+ RubySerializer.as_json publisher, include: { books: [ :authors ] }
208
+ ```
209
+
210
+ ### Exposing model validation errors
96
211
 
97
- # Roadmap
212
+ By default, if your underlying model responds to `:valid?` and returns `false` then the
213
+ serialized response will automatically include a serialized `:errors` attribute.
98
214
 
99
- * Extensibility with custom Field types
100
-
101
215
  # License
102
216
 
103
217
  See [LICENSE](https://github.com/jakesgordon/ruby-serializer/blob/master/LICENSE) file.
@@ -0,0 +1,288 @@
1
+ require_relative 'test_case'
2
+
3
+ module RubySerializer
4
+ class AssociationTest < TestCase
5
+
6
+ #----------------------------------------------------------------------------------------------
7
+
8
+ PORTLAND = { id: :portland, city: 'Portland', state: 'OR' }
9
+ SEATTLE = { id: :seattle, city: 'Seattle', state: 'WA' }
10
+ BOSTON = { id: :boston, city: 'Boston', state: 'MA' }
11
+ NYC = { id: :nyc, city: 'New York', state: 'NY' }
12
+ SF = { id: :sf, city: 'San Francisco', state: 'CA' }
13
+ LA = { id: :la, city: 'Los Angeles', state: 'LA' }
14
+ CHICAGO = { id: :chicago, city: 'Chicago', state: 'IL' }
15
+ DALLAS = { id: :dallas, city: 'Dallas', state: 'TX' }
16
+ REDMOND = { id: :redmond, city: 'Redmond', state: 'WA' }
17
+
18
+ MCCONNEL = { id: :mcconnel, name: 'Steve McConnel', address: SEATTLE }
19
+ HUNT = { id: :hunt, name: 'Andrew Hunt', address: PORTLAND }
20
+ THOMAS = { id: :thomas, name: 'David Thomas', address: BOSTON }
21
+ GAMMA = { id: :gamma, name: 'Erich Gamma', address: NYC }
22
+ HELM = { id: :helm, name: 'Richard Helm', address: SF }
23
+ JOHNSON = { id: :johnson, name: 'Ralph Johnson', address: CHICAGO }
24
+ VLISSIDES = { id: :vlissides, name: 'John Vlissides', address: DALLAS }
25
+
26
+ PP = { id: :pp, isbn: '020161622X', name: 'Pragmatic Programmer', publisher_id: :aw, authors: [ HUNT, THOMAS ] }
27
+ DP = { id: :dp, isbn: '0201633612', name: 'Design Patterns', publisher_id: :aw, authors: [ GAMMA, HELM, JOHNSON, VLISSIDES ] }
28
+ CC = { id: :cc, isbn: '0735619670', name: 'Code Complete', publisher_id: :ms, authors: [ MCCONNEL ] }
29
+
30
+ AW = { id: :aw, name: 'Addison-Wesley', address: LA, books: [ PP, DP ] }
31
+ MS = { id: :ms, name: 'Microsoft Press', address: REDMOND, books: [ CC ] }
32
+
33
+ PROGRAMMING = { id: :programming, name: 'Programming', books: [ PP, DP, CC ] }
34
+
35
+ #----------------------------------------------------------------------------------------------
36
+
37
+ class Publisher < Model
38
+ attr :id, :name
39
+ has_one :address
40
+ has_many :books
41
+ end
42
+
43
+ class Book < Model
44
+ attr :isbn, :name
45
+ belongs_to :publisher
46
+ has_many :authors
47
+ end
48
+
49
+ class Author < Model
50
+ attr :id, :name
51
+ has_one :address
52
+ end
53
+
54
+ class Address < Model
55
+ attr :id, :city, :state
56
+ end
57
+
58
+ class Category < Model
59
+ attr :id, :name
60
+ has_many :books
61
+ end
62
+
63
+ #----------------------------------------------------------------------------------------------
64
+
65
+ class PublisherSerializer < RubySerializer::Base
66
+ expose :id
67
+ expose :name
68
+ has_one :address
69
+ has_many :books
70
+ end
71
+
72
+ class BookSerializer < RubySerializer::Base
73
+ expose :isbn
74
+ expose :name
75
+ belongs_to :publisher
76
+ has_many :authors
77
+ end
78
+
79
+ class AuthorSerializer < RubySerializer::Base
80
+ expose :id
81
+ expose :name
82
+ has_one :address
83
+ end
84
+
85
+ class AddressSerializer < RubySerializer::Base
86
+ expose :id
87
+ expose :city
88
+ expose :state
89
+ end
90
+
91
+ class CategorySerializer < RubySerializer::Base
92
+ expose :id
93
+ expose :name
94
+ has_many :books
95
+ end
96
+
97
+ #----------------------------------------------------------------------------------------------
98
+
99
+ def test_belongs_to
100
+
101
+ book = books(PP)
102
+ with = serialize book, include: :publisher
103
+ without = serialize book
104
+
105
+ assert_book PP, with, with: :publisher
106
+ assert_publisher AW, with[:publisher]
107
+ assert_book PP, without
108
+
109
+ end
110
+
111
+ #----------------------------------------------------------------------------------------------
112
+
113
+ def test_has_one
114
+
115
+ author = authors(HUNT)
116
+ with = serialize author, include: :address
117
+ without = serialize author
118
+
119
+ assert_author HUNT, with, with: :address
120
+ assert_address PORTLAND, with[:address]
121
+ assert_author HUNT, without
122
+
123
+ end
124
+
125
+ #----------------------------------------------------------------------------------------------
126
+
127
+ def test_has_many
128
+
129
+ publisher = publishers(AW)
130
+ with = serialize publisher, include: :books
131
+ without = serialize publisher
132
+
133
+ assert_publisher AW, with, with: :books
134
+ assert_equal 2, with[:books].length
135
+ assert_book PP, with[:books][0]
136
+ assert_book DP, with[:books][1]
137
+ assert_publisher AW, without
138
+
139
+ end
140
+
141
+ #----------------------------------------------------------------------------------------------
142
+
143
+ def test_multiple_includes
144
+ variations = [
145
+ [ :books, :address ], # verify using array of symbols
146
+ [ 'books', 'address' ], # verify using array of strings
147
+ "books,address", # verify using a comma separated string
148
+ " books , address " # verify using a comma separated string (with whitespace)
149
+ ]
150
+ variations.each do |includes|
151
+ publisher = publishers(AW)
152
+ json = serialize publisher, include: includes
153
+ assert_publisher AW, json, with: [ :address, :books ]
154
+ assert_address LA, json[:address]
155
+ assert_equal 2, json[:books].length
156
+ assert_book PP, json[:books][0]
157
+ assert_book DP, json[:books][1]
158
+ end
159
+ end
160
+
161
+ #----------------------------------------------------------------------------------------------
162
+
163
+ def test_nested_includes
164
+ publisher = publishers(AW)
165
+ json = serialize publisher, include: "books.authors.address"
166
+ assert_publisher AW, json, with: :books
167
+ assert_equal 2, json[:books].length
168
+ assert_book PP, json[:books][0], with: :authors
169
+ assert_equal 2, json[:books][0][:authors].length
170
+ assert_author HUNT, json[:books][0][:authors][0], with: :address
171
+ assert_address PORTLAND, json[:books][0][:authors][0][:address]
172
+ assert_author THOMAS, json[:books][0][:authors][1], with: :address
173
+ assert_address BOSTON, json[:books][0][:authors][1][:address]
174
+ assert_book DP, json[:books][1], with: :authors
175
+ assert_equal 4, json[:books][1][:authors].length
176
+ assert_author GAMMA, json[:books][1][:authors][0], with: :address
177
+ assert_address NYC, json[:books][1][:authors][0][:address]
178
+ assert_author HELM, json[:books][1][:authors][1], with: :address
179
+ assert_address SF, json[:books][1][:authors][1][:address]
180
+ assert_author JOHNSON, json[:books][1][:authors][2], with: :address
181
+ assert_address CHICAGO, json[:books][1][:authors][2][:address]
182
+ assert_author VLISSIDES, json[:books][1][:authors][3], with: :address
183
+ assert_address DALLAS, json[:books][1][:authors][3][:address]
184
+ end
185
+
186
+ #----------------------------------------------------------------------------------------------
187
+
188
+ def test_multiple_nested_includes
189
+ category = categories(PROGRAMMING)
190
+ json = serialize category, include: "books.publisher, books.authors"
191
+ assert_category PROGRAMMING, json, with: :books
192
+ assert_equal 3, json[:books].length
193
+ assert_book PP, json[:books][0], with: [ :publisher, :authors ]
194
+ assert_publisher AW, json[:books][0][:publisher]
195
+ assert_equal 2, json[:books][0][:authors].length
196
+ assert_author HUNT, json[:books][0][:authors][0]
197
+ assert_author THOMAS, json[:books][0][:authors][1]
198
+ assert_book DP, json[:books][1], with: [ :publisher, :authors ]
199
+ assert_publisher AW, json[:books][1][:publisher]
200
+ assert_equal 4, json[:books][1][:authors].length
201
+ assert_author GAMMA, json[:books][1][:authors][0]
202
+ assert_author HELM, json[:books][1][:authors][1]
203
+ assert_author JOHNSON, json[:books][1][:authors][2]
204
+ assert_author VLISSIDES, json[:books][1][:authors][3]
205
+ assert_book CC, json[:books][2], with: [ :publisher, :authors ]
206
+ assert_publisher MS, json[:books][2][:publisher]
207
+ assert_equal 1, json[:books][2][:authors].length
208
+ assert_author MCCONNEL, json[:books][2][:authors][0]
209
+ end
210
+
211
+ #----------------------------------------------------------------------------------------------
212
+
213
+ private
214
+
215
+ def addresses(fixture)
216
+ Address.new(fixture)
217
+ end
218
+
219
+ def authors(fixture)
220
+ author = Author.new(fixture)
221
+ author.address = addresses(fixture[:address]) if fixture.key?(:address)
222
+ author
223
+ end
224
+
225
+ def books(fixture)
226
+ book = Book.new(fixture)
227
+ book.publisher = publishers(fixture[:publisher_id], false)
228
+ book.authors = Array(fixture[:authors]).map { |a| authors(a) } if fixture.key?(:authors)
229
+ book
230
+ end
231
+
232
+ def publishers(fixture, include_books = true)
233
+ fixture = AW if fixture == :aw
234
+ fixture = MS if fixture == :ms
235
+ publisher = Publisher.new(fixture)
236
+ publisher.address = addresses(fixture[:address]) if fixture.key?(:address)
237
+ publisher.books = Array(fixture[:books]).map { |b| books(b) } if include_books && fixture[:books]
238
+ publisher
239
+ end
240
+
241
+ def categories(fixture)
242
+ category = Category.new(fixture)
243
+ category.books = Array(fixture[:books]).map { |b| books(b) } if fixture.key?(:books)
244
+ category
245
+ end
246
+
247
+ #----------------------------------------------------------------------------------------------
248
+
249
+ def assert_publisher(expected, actual, options = {})
250
+ expected_keys = [ :id, :name ] + Array(options[:with])
251
+ assert_set expected_keys, actual.keys
252
+ assert_equal expected[:id], actual[:id]
253
+ assert_equal expected[:name], actual[:name]
254
+ end
255
+
256
+ def assert_book(expected, actual, options = {})
257
+ expected_keys = [ :isbn, :name, :publisher_id ] + Array(options[:with])
258
+ assert_set expected_keys, actual.keys
259
+ assert_equal expected[:isbn], actual[:isbn]
260
+ assert_equal expected[:name], actual[:name]
261
+ assert_equal expected[:publisher_id], actual[:publisher_id]
262
+ end
263
+
264
+ def assert_author(expected, actual, options = {})
265
+ expected_keys = [ :id, :name ] + Array(options[:with])
266
+ assert_set expected_keys, actual.keys
267
+ assert_equal expected[:id], actual[:id]
268
+ assert_equal expected[:name], actual[:name]
269
+ end
270
+
271
+ def assert_address(expected, actual)
272
+ assert_set [ :id, :city, :state ], actual.keys
273
+ assert_equal expected[:id], actual[:id]
274
+ assert_equal expected[:city], actual[:city]
275
+ assert_equal expected[:state], actual[:state]
276
+ end
277
+
278
+ def assert_category(expected, actual, options = {})
279
+ expected_keys = [ :id, :name ] + Array(options[:with])
280
+ assert_equal expected_keys, actual.keys
281
+ assert_equal expected[:id], actual[:id]
282
+ assert_equal expected[:name], actual[:name]
283
+ end
284
+
285
+ #----------------------------------------------------------------------------------------------
286
+
287
+ end
288
+ end
data/test/basic_test.rb CHANGED
@@ -6,15 +6,20 @@ module RubySerializer
6
6
  #----------------------------------------------------------------------------------------------
7
7
 
8
8
  def test_gem_information
9
- assert_equal '0.0.1', VERSION
9
+ assert_equal '1.0.0', VERSION
10
10
  assert_equal 'Serialize POROs to JSON', SUMMARY
11
11
  assert_match 'A general purpose library for', DESCRIPTION
12
12
  end
13
13
 
14
14
  #----------------------------------------------------------------------------------------------
15
15
 
16
- Shape = Struct.new(:color, :shape)
17
- Car = Struct.new(:make, :model)
16
+ class Shape < Model
17
+ attr :color, :shape
18
+ end
19
+
20
+ class Car < Model
21
+ attr :make, :model
22
+ end
18
23
 
19
24
  class ShapeSerializer < RubySerializer::Base
20
25
  expose :color
@@ -30,12 +35,12 @@ module RubySerializer
30
35
 
31
36
  def test_basic_serialization
32
37
 
33
- json = RubySerializer.as_json Shape.new(:red, :square)
38
+ json = serialize Shape.new(color: :red, shape: :square)
34
39
  assert_set [ :color, :shape ], json.keys
35
40
  assert_equal :red, json[:color]
36
41
  assert_equal :square, json[:shape]
37
42
 
38
- json = RubySerializer.as_json Car.new(:honda, :civic)
43
+ json = serialize Car.new(make: :honda, model: :civic)
39
44
  assert_set [ :make, :model ], json.keys
40
45
  assert_equal :honda, json[:make]
41
46
  assert_equal :civic, json[:model]
@@ -46,12 +51,12 @@ module RubySerializer
46
51
 
47
52
  def test_array_serialization
48
53
  resources = [
49
- Shape.new(:red, :square),
50
- Shape.new(:blue, :circle),
51
- Car.new(:honda, :civic),
52
- Car.new(:jeep, :wrangler)
54
+ Shape.new(color: :red, shape: :square),
55
+ Shape.new(color: :blue, shape: :circle),
56
+ Car.new(make: :honda, model: :civic),
57
+ Car.new(make: :jeep, model: :wrangler)
53
58
  ]
54
- json = RubySerializer.as_json resources
59
+ json = serialize resources
55
60
  assert_equal resources.length, json.length
56
61
  assert_set [ :color, :shape ], json[0].keys
57
62
  assert_equal :red, json[0][:color]
@@ -70,7 +75,7 @@ module RubySerializer
70
75
  #----------------------------------------------------------------------------------------------
71
76
 
72
77
  def test_serialize_with_root
73
- json = RubySerializer.as_json Car.new(:honda, :civic), root: :car
78
+ json = serialize Car.new(make: :honda, model: :civic), root: :car
74
79
  assert_set [ :car ], json.keys
75
80
  assert_equal :honda, json[:car][:make]
76
81
  assert_equal :civic, json[:car][:model]
@@ -79,8 +84,8 @@ module RubySerializer
79
84
  #----------------------------------------------------------------------------------------------
80
85
 
81
86
  def test_serialize_with_errors
82
- resource = OpenStruct.new(color: :red, shape: :sausages, valid?: false, errors: { shape: 'is invalid' })
83
- json = RubySerializer.as_json resource, with: ShapeSerializer
87
+ resource = Shape.new(color: :red, shape: :sausages, valid: false, errors: { shape: 'is invalid' })
88
+ json = serialize resource, with: ShapeSerializer
84
89
  expected = [ :color, :shape, :errors ]
85
90
  assert_set expected, json.keys
86
91
  assert_equal :red, json[:color]
data/test/expose_test.rb CHANGED
@@ -10,7 +10,14 @@ module RubySerializer
10
10
  EMAIL = 'Email'
11
11
  SECRET = 'Secret'
12
12
 
13
- Resource = OpenStruct
13
+ class Resource < Model
14
+ attr :id
15
+ attr :name
16
+ attr :email
17
+ attr :secret # this attribute will not be exposed in any serializer
18
+ attr :value # this attribute might be used as a custom :value in some serializers
19
+ attr :show # this attribute might be used to conditionally show :only or :unless
20
+ end
14
21
 
15
22
  #==============================================================================================
16
23
  # Sample Serializers
@@ -45,9 +52,8 @@ module RubySerializer
45
52
  class CustomValueSerializer < RubySerializer::Base
46
53
  expose :id
47
54
  expose :static, value: 'static value'
48
- expose :method, value: :method_value
49
55
  expose :dynamic, value: -> { "dynamic value (#{resource.name})" }
50
- expose :empty, value: nil
56
+ expose :method, value: :value
51
57
  end
52
58
 
53
59
  #----------------------------------------------------------------------------------------------
@@ -89,7 +95,7 @@ module RubySerializer
89
95
 
90
96
  def test_expose_attributes_unchanged
91
97
  resource = Resource.new(id: ID, name: NAME, email: EMAIL, secret: SECRET)
92
- json = RubySerializer.as_json resource, with: BasicSerializer
98
+ json = serialize resource, with: BasicSerializer
93
99
  expected = [ :id, :name, :email ]
94
100
  assert_set expected, json.keys
95
101
  assert_equal ID, json[:id]
@@ -101,7 +107,7 @@ module RubySerializer
101
107
 
102
108
  def test_expose_renamed_attributes
103
109
  resource = Resource.new(id: ID, name: NAME, email: EMAIL, secret: SECRET)
104
- json = RubySerializer.as_json resource, with: RenamingSerializer
110
+ json = serialize resource, with: RenamingSerializer
105
111
  expected = [ :id, :user_name, :user_email ]
106
112
  assert_set expected, json.keys
107
113
  assert_equal ID, json[:id]
@@ -113,7 +119,7 @@ module RubySerializer
113
119
 
114
120
  def test_expose_namespaced_attributes
115
121
  resource = Resource.new(id: ID, name: NAME, email: EMAIL, secret: SECRET)
116
- json = RubySerializer.as_json resource, with: NamespaceSerializer
122
+ json = serialize resource, with: NamespaceSerializer
117
123
  expected = [ :id, :user ]
118
124
  assert_set expected, json.keys
119
125
  assert_equal ID, json[:id]
@@ -125,23 +131,22 @@ module RubySerializer
125
131
  #----------------------------------------------------------------------------------------------
126
132
 
127
133
  def test_expose_attributes_with_custom_values
128
- resource = Resource.new(id: ID, name: NAME, method_value: 'method value')
129
- json = RubySerializer.as_json resource, with: CustomValueSerializer
130
- expected = [ :id, :static, :dynamic, :method, :empty ]
134
+ resource = Resource.new(id: ID, name: NAME, value: 'method value')
135
+ json = serialize resource, with: CustomValueSerializer
136
+ expected = [ :id, :static, :dynamic, :method ]
131
137
  assert_set expected, json.keys
132
138
  assert_equal ID, json[:id]
133
139
  assert_equal 'static value', json[:static]
134
140
  assert_equal 'dynamic value (Name)', json[:dynamic]
135
141
  assert_equal 'method value', json[:method]
136
- assert_equal nil, json[:empty]
137
142
  end
138
143
 
139
144
  #----------------------------------------------------------------------------------------------
140
145
 
141
146
  def test_expose_attributes_conditionally
142
- show = RubySerializer.as_json Resource.new(id: ID, show: true), with: ConditionalSerializer
143
- noshow = RubySerializer.as_json Resource.new(id: ID, show: false), with: ConditionalSerializer
144
- unspecified = RubySerializer.as_json Resource.new(id: ID), with: ConditionalSerializer
147
+ show = serialize Resource.new(id: ID, show: true), with: ConditionalSerializer
148
+ noshow = serialize Resource.new(id: ID, show: false), with: ConditionalSerializer
149
+ unspecified = serialize Resource.new(id: ID), with: ConditionalSerializer
145
150
  assert_set [ :id, :only_true, :unless_false, :only_method, :only_dynamic ], show.keys
146
151
  assert_set [ :id, :only_true, :unless_false, :unless_method, :unless_dynamic ], noshow.keys
147
152
  assert_set [ :id, :only_true, :unless_false, :unless_method, :unless_dynamic ], unspecified.keys
@@ -152,7 +157,7 @@ module RubySerializer
152
157
  def test_expose_attributes_using_custom_resource_name
153
158
 
154
159
  resource = Resource.new(id: ID, value: 42, show: false)
155
- json = RubySerializer.as_json resource, with: CustomResourceNameSerializer
160
+ json = serialize resource, with: CustomResourceNameSerializer
156
161
  expected = [ :id, :value, :show_unless ]
157
162
  assert_set expected, json.keys
158
163
  assert_equal ID, json[:id]
@@ -160,7 +165,7 @@ module RubySerializer
160
165
  assert_equal "show unless", json[:show_unless]
161
166
 
162
167
  resource = Resource.new(id: ID, value: 99, show: true)
163
- json = RubySerializer.as_json resource, with: CustomResourceNameSerializer
168
+ json = serialize resource, with: CustomResourceNameSerializer
164
169
  expected = [ :id, :value, :show_only ]
165
170
  assert_set expected, json.keys
166
171
  assert_equal ID, json[:id]
@@ -175,14 +180,14 @@ module RubySerializer
175
180
 
176
181
  resource = Resource.new(id: ID)
177
182
 
178
- json = RubySerializer.as_json resource, value: 42, show: false, with: CustomOptionsSerializer
183
+ json = serialize resource, value: 42, show: false, with: CustomOptionsSerializer
179
184
  expected = [ :id, :value, :show_unless ]
180
185
  assert_set expected, json.keys
181
186
  assert_equal ID, json[:id]
182
187
  assert_equal "value is 42", json[:value]
183
188
  assert_equal "show unless", json[:show_unless]
184
189
 
185
- json = RubySerializer.as_json resource, value: 99, show: true, with: CustomOptionsSerializer
190
+ json = serialize resource, value: 99, show: true, with: CustomOptionsSerializer
186
191
  expected = [ :id, :value, :show_only ]
187
192
  assert_set expected, json.keys
188
193
  assert_equal ID, json[:id]
data/test/model.rb ADDED
@@ -0,0 +1,66 @@
1
+ module RubySerializer
2
+ class Model # simple model with ActiveRecord like associations
3
+
4
+ def initialize(attributes = {})
5
+ @valid = true
6
+ @errors = []
7
+ attributes.each do |key, value|
8
+ instance_variable_set "@#{key}", value
9
+ end
10
+ end
11
+
12
+ def valid?
13
+ @valid
14
+ end
15
+
16
+ def errors
17
+ @errors
18
+ end
19
+
20
+ #----------------------------------------------------------------------------------------------
21
+
22
+ def self.belongs_to(key)
23
+ idkey = "#{key}_id"
24
+ instvar = "@#{key}"
25
+ idinstvar = "@#{idkey}"
26
+ define_method key do
27
+ instance_variable_get instvar
28
+ end
29
+ define_method idkey do
30
+ instance_variable_get idinstvar
31
+ end
32
+ define_method "#{key}=" do |value|
33
+ instance_variable_set instvar, value
34
+ instance_variable_set idinstvar, value ? value.id : nil
35
+ end
36
+ end
37
+
38
+ #----------------------------------------------------------------------------------------------
39
+
40
+ def self.has_one(key)
41
+ instvar = "@#{key}"
42
+ define_method key do
43
+ instance_variable_get instvar
44
+ end
45
+ define_method "#{key}=" do |value|
46
+ instance_variable_set instvar, value
47
+ end
48
+ end
49
+
50
+ #----------------------------------------------------------------------------------------------
51
+
52
+ def self.has_many(key)
53
+ instvar = "@#{key}"
54
+ define_method key do
55
+ instance_variable_set instvar, [] unless instance_variable_defined?(instvar)
56
+ instance_variable_get instvar
57
+ end
58
+ define_method "#{key}=" do |value|
59
+ instance_variable_set instvar, Array(value)
60
+ end
61
+ end
62
+
63
+ #----------------------------------------------------------------------------------------------
64
+
65
+ end
66
+ end
data/test/test_case.rb CHANGED
@@ -2,13 +2,22 @@ require_relative '../lib/ruby_serializer'
2
2
 
3
3
  require 'minitest/autorun'
4
4
  require 'minitest/pride'
5
+ require 'ostruct'
5
6
  require 'byebug'
6
7
 
8
+ require_relative 'model'
9
+
7
10
  module RubySerializer
8
11
  class TestCase < Minitest::Test
9
12
 
10
13
  #----------------------------------------------------------------------------------------------
11
14
 
15
+ def serialize(resource, options = {})
16
+ RubySerializer.as_json resource, options
17
+ end
18
+
19
+ #----------------------------------------------------------------------------------------------
20
+
12
21
  def assert_set(expected, actual, message = nil)
13
22
  assert_equal Set.new(expected), Set.new(actual), message
14
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Gordon
@@ -81,13 +81,16 @@ files:
81
81
  - LICENSE
82
82
  - Rakefile
83
83
  - lib/ruby_serializer.rb
84
+ - lib/ruby_serializer/association.rb
84
85
  - lib/ruby_serializer/base.rb
85
86
  - lib/ruby_serializer/dsl.rb
86
87
  - lib/ruby_serializer/field.rb
87
88
  - readme.md
88
89
  - ruby-serializer.gemspec
90
+ - test/association_test.rb
89
91
  - test/basic_test.rb
90
92
  - test/expose_test.rb
93
+ - test/model.rb
91
94
  - test/test_case.rb
92
95
  homepage: https://github.com/jakesgordon/ruby-serializer
93
96
  licenses: