shreddies 0.1.0 → 0.2.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
  SHA256:
3
- metadata.gz: 6770e333d8c5c90f6a7f1d56fd4d3e27fa32103c13473ed500d151ee20cd8826
4
- data.tar.gz: 387cc3e15bbaa1eb8ca3c1775fbc80d22df8c7edf79547dd1518149f07d70e68
3
+ metadata.gz: 1f3a9878d5d25ca7eb02bc3d9f3e4e4dc6f71ab6f970312331ed01fe864923b4
4
+ data.tar.gz: bfba9356df3012c94527f1214d1862c5a507f91aab243f43fc85c8a30b0d50e3
5
5
  SHA512:
6
- metadata.gz: 8a7ef85a2a7e0cf2bac9353ce5998f3b7b897c36da844e7169e13dfea62c377cf7a994b4e0bf27a763721a3853aa503303ff2248b65a6ade68b9db4748b027ec
7
- data.tar.gz: c2b54d864565890b793b63bdbdf6160d85ff7dd3a8de974034499139aedb7c2fb0974cb34ae05acda31d7d444caa480522f4c9408c6284709a6a1168f552f221
6
+ metadata.gz: '069be646751138acce14f15960e417c18dcb2f57981a49b77d7d1a3a71ef4c2c079c49be4535c8ae193e752cb90298ddca0a72d5f51d12a9f2f2860c940a1e84'
7
+ data.tar.gz: 8cf4dd148fe6ab7a6989e52462bc3fd3b14cddf592d17b45bd6470ec60f7482fe21e64a12af1909181bf789981b64a964b30f241e746ff3150de7bb4edfa3245
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /test/internal/db/*.sqlite
data/README.md CHANGED
@@ -20,7 +20,7 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- Serializers should be named after your models and located in "`app/serializers`". Any public methods you define will be serialized, and you can quickly expose methods from the serialized subject using the `delegate` class method.
23
+ Serializers should be named after your models and located in "`app/serializers`". Any public methods you define will be serialized, and you can quickly expose methods from the serialized subject using the `delegate` class method, which simply delegates to the subject.
24
24
 
25
25
  ```ruby
26
26
  # app/serializers/user_serializer.rb
@@ -65,12 +65,64 @@ Model collections and array's are also supported:
65
65
  User.all.as_json
66
66
  ```
67
67
 
68
+ You may find that you don't want or need to return as much data in collections of objects, or may want to include differtent data. So if a serializer defines a `Collection` module, and a collection or array is being rendered, then that Collection module will automatically be included:
69
+
70
+ ```ruby
71
+ ArticleSerializer < Shreddies::Json
72
+ module Collection
73
+ def url
74
+ "https://blah.com/#{subject.slug}"
75
+ end
76
+ end
77
+ end
78
+ ```
79
+
80
+ Conversely, you can define a `Single` module, and that will be included when rendering a single object.
81
+
82
+ ```ruby
83
+ ArticleSerializer < Shreddies::Json
84
+ module Single
85
+ def body
86
+ 'this body is really, really long, and I do not want it returned in lists.'
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
68
92
  ### Options
69
93
 
70
94
  Both `#as_json` and `.render` accepts an `options` hash, which will be forwarded to the serializer class, and available as `options`. This allows you to pass arbitrary options and use them in your serializer.
71
95
 
72
96
  The following standard options are supported, and provide additional built-in functionality:
73
97
 
98
+ #### `serializer`
99
+
100
+ By default `#as_json` will look for a serializer named after your model. So a `User` model will automatically use the `UserSerializer`. Sometimes you want to use a different serializer class, in which case you can use the `serializer` option:
101
+
102
+ ```ruby
103
+ User.all.as_json serializer: User::AdminSerializer
104
+ ```
105
+
106
+ #### `module`
107
+
108
+ You can pass one or module names in the `module` option, and these modules will be included into the serializer. This is great for selectively including attributes and methods.
109
+
110
+ ```ruby
111
+ Article.all.as_json module: :WithBody
112
+ ```
113
+
114
+ ```ruby
115
+ ArticleSerializer < Shreddies::Json
116
+ module WithBody
117
+ def body
118
+ 'This article body is really, really long'
119
+ end
120
+ end
121
+ end
122
+ ```
123
+
124
+ The `Collection` and `Single` modules can be defined and they will be automatically included. The Collection module will be included when rendering an array or ActiveRecord collection (`ActiveRecord::Relation`), and the Single module will be included when rendering a single obejct.
125
+
74
126
  #### `index_by`
75
127
 
76
128
  Give this option a property of your serialized subject as a Symbol, and the returned collection will be a Hash keyed by that property.
@@ -99,6 +151,33 @@ User.all.as_json index_by: :id
99
151
  }
100
152
  ```
101
153
 
154
+ ### Serializer Inheritance
155
+
156
+ A serializer can inherit from any other serializer, which is a great way to create custom views:
157
+
158
+ ```ruby
159
+ # app/serializers/user_serializer.rb
160
+ class UserSerializer < Shreddies::Json
161
+ delegate :id, :first_name, :last_name, :email
162
+
163
+ def name
164
+ "#{first_name} #{last_name}"
165
+ end
166
+ end
167
+
168
+ class User::AdministratorSerializer < UserSerializer
169
+ def type
170
+ 'administrator'
171
+ end
172
+ end
173
+ ```
174
+
175
+ Then call it like any other serializer:
176
+
177
+ ```ruby
178
+ User::AdministratorSerializer.render(user)
179
+ ```
180
+
102
181
  ## Development
103
182
 
104
183
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -7,12 +7,10 @@ module Shreddies
7
7
  serializer = options.delete(:serializer) || "#{model_name}Serializer"
8
8
 
9
9
  if serializer.is_a?(String) || serializer.is_a?(Symbol)
10
- serializer.to_s.constantize.render_as_json self, options
11
- else
12
- serializer.render self, options
10
+ serializer = serializer.to_s.safe_constantize
13
11
  end
14
- rescue NameError
15
- super
12
+
13
+ serializer ? serializer.render_as_json(self, options) : super
16
14
  end
17
15
  end
18
16
 
@@ -21,12 +19,10 @@ module Shreddies
21
19
  serializer = options.delete(:serializer) || "#{model_name}Serializer"
22
20
 
23
21
  if serializer.is_a?(String) || serializer.is_a?(Symbol)
24
- serializer.to_s.constantize.render_as_json self, options
25
- else
26
- serializer.render self, options
22
+ serializer = serializer.to_s.safe_constantize
27
23
  end
28
- rescue NameError
29
- super
24
+
25
+ serializer ? serializer.render_as_json(self, options) : super
30
26
  end
31
27
  end
32
28
  end
@@ -6,20 +6,28 @@ module Shreddies
6
6
  # Render a subject as json, where `subject` is a single object (usually a Rails model), or an
7
7
  # array/collection of objects.
8
8
  #
9
+ # If subject is an array/collection then it will look for a `Collection` module prepend it to
10
+ # the `module` option.
11
+ #
9
12
  # A Hash of options can be given as the second argument:
10
13
  # - index_by - Key the returned array by the value, transforming it from an array to a hash.
14
+ # - module - A Symbol or String of a local module to include. Or an array of several
15
+ # modules, where each will be mixed in in order. Use this to mix in groups of
16
+ # attributes. Eg. `ArticleSerializer.render(data, module: :WithBody)`.
11
17
  #
12
18
  def render(subject, options = {})
13
19
  index_by = options.delete(:index_by)
14
20
 
15
21
  if subject.is_a?(Array) || subject.is_a?(ActiveRecord::Relation)
22
+ collection_options = options.merge(from_collection: true)
23
+
16
24
  if index_by
17
25
  mapped = {}
18
26
  subject.each do |x|
19
- mapped[x[index_by]] = new(x, options)
27
+ mapped[x[index_by]] = new(x, collection_options)
20
28
  end
21
29
  else
22
- mapped = subject.map { |x| new(x, options) }
30
+ mapped = subject.map { |x| new(x, collection_options) }
23
31
  end
24
32
 
25
33
  mapped.as_json
@@ -31,31 +39,62 @@ module Shreddies
31
39
  alias render_as_json render
32
40
  end
33
41
 
34
- # Monkey patches Rails Module#delegate so that the `:to` argument defaults tio `:subject`.
42
+ # Monkey patches Rails Module#delegate so that the `:to` argument defaults to `:subject`.
35
43
  def self.delegate(*methods, to: :subject, prefix: nil, allow_nil: nil, private: nil)
36
44
  super(*methods, to: to, prefix: prefix, allow_nil: allow_nil, private: private)
37
45
  end
38
46
 
39
- attr_reader :subject, :options
47
+ attr_reader :subject, :options, :from_collection
40
48
 
41
49
  def initialize(subject, options)
42
50
  @subject = subject
43
51
  @options = options
52
+
53
+ extend_with_modules
44
54
  end
45
55
 
56
+ # Travel through the ancestors that are serializers (class name ends with "Serializer"), and
57
+ # call all public instance methods, returning a hash.
46
58
  def as_json
47
59
  json = {}
60
+ methods = Set.new(public_methods(false))
48
61
 
49
- methods = public_methods(false)
50
- if self.class.superclass.to_s.end_with?('Serializer')
51
- methods.concat self.class.superclass.public_instance_methods(false)
62
+ self.class.ancestors.each do |ancestor|
63
+ if ancestor.to_s.end_with?('Serializer')
64
+ methods.merge ancestor.public_instance_methods(false)
65
+ end
52
66
  end
53
67
 
54
- methods.uniq.excluding(:subject, :options, :as_json).map do |attr|
55
- json[attr] = public_send(attr)
68
+ methods.map do |attr|
69
+ json[attr.to_s.camelize :lower] = public_send(attr)
56
70
  end
57
71
 
58
72
  json.deep_transform_keys { |key| key.to_s.camelize :lower }
59
73
  end
74
+
75
+ private
76
+
77
+ def extend_with_modules
78
+ self.class.ancestors.reverse.each do |ancestor|
79
+ next unless ancestor.to_s.end_with?('Serializer')
80
+
81
+ # Extend with Collection module if it exists, and a collection is being rendered. Otherwise,
82
+ # extend with the Single module if that exists.
83
+ if options[:from_collection]
84
+ (collection_mod = "#{ancestor}::Collection".safe_constantize) && extend(collection_mod)
85
+ else
86
+ (single_mod = "#{ancestor}::Single".safe_constantize) && extend(single_mod)
87
+ end
88
+ end
89
+
90
+ # Extend with the :module option if given.
91
+ if options[:module]
92
+ Array(options[:module]).each do |m|
93
+ mod = m.is_a?(Module) ? m : "#{self.class}::#{m}".constantize
94
+
95
+ extend mod
96
+ end
97
+ end
98
+ end
60
99
  end
61
100
  end
@@ -1,3 +1,3 @@
1
1
  module Shreddies
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shreddies
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Moss
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-30 00:00:00.000000000 Z
11
+ date: 2020-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord