simple_ams 0.1.0 → 0.1.1

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: 5193c4f1997cfc84c8fdf5fa90abfde7ac2a03a2
4
- data.tar.gz: 13c6c665417899d0a73d9900144d58e8f848e370
3
+ metadata.gz: 402b318569b32a747756164ace5901543dfae101
4
+ data.tar.gz: 9cad887a5be00d87011f12a55bb779084d51e147
5
5
  SHA512:
6
- metadata.gz: b6affb011516c6e9b69b829c176966305b38f7d102b874248bac63fb089c8b84b27cec6190e067364943935b26164bcc066d12ebad77c88d412edaf7db6724aa
7
- data.tar.gz: c44a84ff86b1a0d2eb5b67670b508a3cfa6da2ded1cc0353fd0709b73a352a7a2d1bf735860cba76ae03f696e4eefab50c4704b67229be8fb1285f43376c8ea2
6
+ metadata.gz: 5ad0ca8cfb422fedbf1ddc2b8d2d3f3150b9c704a20720c83f7aa81ccb03e913edf2c2e52f15a13307e0eb2b8c3ded26e620ffc2fc5650f0c195f6b95c385431
7
+ data.tar.gz: d35104e20592ff6b54235a7d990875737c454079ece2dd267530d9831eecc6c1d6a470152b7bc0daf36fd3cebafecf2d2e6d46d564a32253c332a243dd605fe1
data/.gitignore CHANGED
@@ -12,3 +12,4 @@
12
12
  # rspec failure tracking
13
13
  .rspec_status
14
14
  .todo.md
15
+ *.gem
data/README.md CHANGED
@@ -41,9 +41,17 @@ class UserSerializer
41
41
  attributes :id, :name, :email, :birth_date
42
42
 
43
43
  #specify available relations
44
- has_many :videos, :comments, :posts
45
- belongs_to :organization
46
- has_one :profile
44
+ has_one :profile, serializer: ProfileSerializer
45
+ #belongs_to is just an alias to has_one
46
+ belongs_to :organization, serializer: OrganizationSerializer
47
+ has_many :videos, serializer: VideosSerializer
48
+ #rarely used: if you need more options, you can pas a block
49
+ #which adheres to the same DSL as described here
50
+ #it goes to an option called `embedded`
51
+ #essentially these options here should be used for linking current resource
52
+ #with the relation (useful for JSONAPI for instance)
53
+ generic :include_data, false
54
+ end
47
55
 
48
56
  #specify some links
49
57
  link :feed, '/api/v1/me/feed'
@@ -57,16 +65,26 @@ class UserSerializer
57
65
  #same with metas: can be static, dynamic and accept arbitrary options
58
66
  meta :environment, ->(obj) { Rails.env.to_s }
59
67
 
60
- #collection accepts exactly the same aforementioned interface
61
- #although you will rarely use it to full extend
62
- #here we use only links and meta
68
+ #same with form: can be static, dynamic and accept arbitrary options
69
+ form :create, ->(obj) { User::CreateForm.for(obj) }
70
+
71
+ #or if you need something quite generic (and probably adapter-related)
72
+ #again it follows the same patterns as link
73
+ generic :include_embedded_data, true, {only: :collection}
74
+
75
+ #these are properties to the collection resource itself
76
+ #AND NOT to each resource separately, when applied inside a collection..
77
+ #It's a rarely used feature but definitely nice to have..
63
78
  collection do
79
+ #collection accepts exactly the same aforementioned interface
80
+ #here we use only links and meta
64
81
  link :root, '/api/v1/', rel: :user
65
82
  type :users
66
83
  meta :count, ->(collection) { collection.count }
67
84
  end
68
85
 
69
- #note that there is a shortcut if you just need to specify the collection name/type:
86
+ #note that most probably the only thing that you will need here is the `type`,
87
+ #so there is a shortcut if you just need to specify the collection name/type:
70
88
  #collection :users
71
89
 
72
90
  #override an attribute
@@ -146,6 +164,11 @@ In any case, we have the following options:
146
164
  #meta can take arbitrary options as well
147
165
  authorization: :oauth, type: :bearer_token
148
166
  },
167
+ #the form data, same as the links/metas data (available in adapters even for single records)
168
+ forms: {
169
+ update: ->(obj){ User::UpdateForm.for(obj)}
170
+ follow: ->(obj){ User::FollowForm.for(obj)}
171
+ },
149
172
  #collection parameters, used only in ArrayRenderer
150
173
  collection: {
151
174
  links: {
@@ -158,6 +181,10 @@ In any case, we have the following options:
158
181
  next_page: ->(obj) { [obj.next_page, collection: true] },
159
182
  max_per_page: 50,
160
183
  },
184
+ #creating a resource goes in the collection route (users/), hence inside collection options ;)
185
+ forms: {
186
+ create: ->(obj){ User::CreateForm.for(obj)}
187
+ },
161
188
  }
162
189
  #exposing helpers that will be available inside the seriralizer
163
190
  expose: {
@@ -206,8 +233,8 @@ SimpleAMS::Renderer.new(user, {
206
233
  # ...
207
234
  # ...
208
235
  }).to_json
209
-
210
236
  ```
237
+
211
238
  ## Development
212
239
 
213
240
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -11,45 +11,52 @@ class SimpleAMS::Adapters::AMS
11
11
  def as_json
12
12
  hash = {}
13
13
 
14
- #TODO: I think bang method for merging is way faster ?
15
- hash = hash.merge(fields)
16
- hash = hash.merge(relations)
17
- hash = hash.merge(links: links) unless links.empty?
18
- hash = hash.merge(metas: metas) unless metas.empty?
14
+ hash.merge!(fields)
15
+ hash.merge!(relations) unless relations.empty?
16
+ hash.merge!(links: links) unless links.empty?
17
+ hash.merge!(metas: metas) unless metas.empty?
18
+ hash.merge!(forms: forms) unless forms.empty?
19
19
 
20
+ return {document.name => hash} if options[:root]
20
21
  return hash
21
22
  end
22
23
 
23
24
  def fields
24
25
  @fields ||= document.fields.inject({}){ |hash, field|
25
- _value = field.value
26
- hash[field.key] = _value.respond_to?(:as_json) ? _value.as_json : _value
26
+ hash[field.key] = field.value
27
27
  hash
28
28
  }
29
29
  end
30
30
 
31
31
  def links
32
+ return @links ||= {} if document.links.empty?
33
+
32
34
  @links ||= document.links.inject({}){ |hash, link|
33
- _value = link.value
34
- hash[link.name] = _value.respond_to?(:as_json) ? _value.as_json : _value
35
+ hash[link.name] = link.value
35
36
  hash
36
37
  }
37
38
  end
38
39
 
39
40
  def metas
40
41
  @metas ||= document.metas.inject({}){ |hash, meta|
41
- _value = meta.value
42
- hash[meta.name] = _value.respond_to?(:as_json) ? _value.as_json : _value
42
+ hash[meta.name] = meta.value
43
+ hash
44
+ }
45
+ end
46
+
47
+ def forms
48
+ @forms ||= document.forms.inject({}){ |hash, form|
49
+ hash[form.name] = form.value
43
50
  hash
44
51
  }
45
52
  end
46
53
 
47
54
  def relations
48
- return {} if document.relations.empty?
55
+ return @relations = {} if document.relations.available.empty?
49
56
 
50
- @relations ||= document.relations.inject({}){ |hash, relation|
57
+ @relations ||= document.relations.available.inject({}){ |hash, relation|
51
58
  if relation.folder?
52
- value = relation.documents.map{|doc| self.class.new(doc).as_json}
59
+ value = relation.map{|doc| self.class.new(doc).as_json}
53
60
  else
54
61
  value = self.class.new(relation).as_json
55
62
  end
@@ -70,16 +77,26 @@ class SimpleAMS::Adapters::AMS
70
77
 
71
78
  def as_json
72
79
  if options[:root]
73
- {folder.name => documents}
80
+ {
81
+ folder.name => documents,
82
+ meta: metas
83
+ }
74
84
  else
75
85
  documents
76
86
  end
77
87
  end
78
88
 
79
89
  def documents
80
- return folder.documents.map{|document|
90
+ return folder.map{|document|
81
91
  adapter.new(document).as_json
82
92
  } || []
83
93
  end
94
+
95
+ def metas
96
+ @metas ||= folder.metas.inject({}){ |hash, meta|
97
+ hash[meta.name] = meta.value
98
+ hash
99
+ }
100
+ end
84
101
  end
85
102
  end
@@ -0,0 +1,182 @@
1
+ require "simple_ams"
2
+
3
+ class SimpleAMS::Adapters::JSONAPI
4
+ DEFAULT_OPTIONS = {
5
+ skip_id_in_attributes: true
6
+ }
7
+
8
+ attr_reader :document, :options
9
+ def initialize(document, options = {})
10
+ @document = document
11
+ @options = DEFAULT_OPTIONS.merge(options)
12
+ end
13
+
14
+ def as_json
15
+ hash = {
16
+ data: data,
17
+ }
18
+
19
+ hash.merge!(links: links) unless links.empty?
20
+ hash.merge!(metas: metas) unless metas.empty?
21
+ hash.merge!(included: included) unless included.empty?
22
+
23
+ return hash
24
+ end
25
+
26
+ def data
27
+ data = {
28
+ transform_key(document.primary_id.name) => document.primary_id.value.to_s,
29
+ type: document.type.value,
30
+ attributes: fields,
31
+ }
32
+
33
+ data.merge!(relationships: relationships) unless relationships.empty?
34
+
35
+ data
36
+ end
37
+
38
+ def fields
39
+ @fields ||= document.fields.inject({}){ |hash, field|
40
+ unless options[:skip_id_in_attributes] && field_is_primary_id?(field)
41
+ hash[transform_key(field.key)] = field.value
42
+ end
43
+ hash
44
+ }
45
+ end
46
+
47
+ def transform_key(key)
48
+ key.to_s.gsub('_', '-')
49
+ end
50
+
51
+ def relationships
52
+ return @relationships ||= {} if document.relations.empty?
53
+
54
+ @relationships ||= document.relations.inject({}){ |hash, relation|
55
+ _hash = {}
56
+ embedded_relation_data = embedded_relation_data_for(relation)
57
+ unless embedded_relation_data.empty?
58
+ _hash.merge!(data: embedded_relation_data_for(relation))
59
+ end
60
+
61
+ embedded_relation_links = embedded_relation_links_for(relation)
62
+ unless embedded_relation_links.empty?
63
+ _hash.merge!(links: embedded_relation_links_for(relation))
64
+ end
65
+
66
+ hash.merge!(relation.name => _hash) unless _hash.empty?
67
+ hash
68
+ }
69
+ end
70
+
71
+ def embedded_relation_data_for(relation)
72
+ return {} if relation.embedded.generics[:skip_data]&.value
73
+
74
+ if relation.folder?
75
+ value = relation.documents.map{|doc|
76
+ {
77
+ doc.primary_id.name => doc.primary_id.value.to_s,
78
+ type: doc.type.name
79
+ }
80
+ }
81
+ else
82
+ value = {
83
+ relation.primary_id.name => relation.primary_id.value.to_s,
84
+ type: relation.type.name
85
+ }
86
+ end
87
+ end
88
+
89
+ def embedded_relation_links_for(relation)
90
+ return {} if relation.embedded.links.empty?
91
+
92
+ relation.embedded.links.inject({}){ |hash, link|
93
+ hash[link.name] = link.value
94
+ hash
95
+ }
96
+ end
97
+
98
+
99
+ def links
100
+ return @links ||= {} if document.links.empty?
101
+
102
+ @links ||= document.links.inject({}){ |hash, link|
103
+ hash[link.name] = link.value
104
+ hash
105
+ }
106
+ end
107
+
108
+ def metas
109
+ @metas ||= document.metas.inject({}){ |hash, meta|
110
+ hash[meta.name] = meta.value
111
+ hash
112
+ }
113
+ end
114
+
115
+ def forms
116
+ @forms ||= document.forms.inject({}){ |hash, form|
117
+ hash[form.name] = form.value
118
+ hash
119
+ }
120
+ end
121
+
122
+ def included
123
+ return @included ||= [] if document.relations.available.empty?
124
+
125
+ @included ||= document.relations.available.inject([]){ |array, relation|
126
+ if relation.folder?
127
+ array << relation.map{|doc| self.class.new(doc).as_json[:data]}
128
+ else
129
+ array << self.class.new(relation).as_json[:data]
130
+ end
131
+
132
+ array
133
+ }.flatten
134
+ end
135
+
136
+ class Collection < self
137
+ attr_reader :folder, :adapter, :options
138
+
139
+ def initialize(folder, options = {})
140
+ @folder = folder
141
+ @adapter = folder.adapter.value
142
+ @options = options
143
+ end
144
+
145
+ def as_json
146
+ hash = {
147
+ data: documents
148
+ }
149
+ hash.merge!(meta: metas) unless metas.empty?
150
+ hash.merge!(links: links) unless links.empty?
151
+
152
+ return hash
153
+ end
154
+
155
+ def documents
156
+ return folder.map{|document|
157
+ adapter.new(document).as_json[:data]
158
+ } || []
159
+ end
160
+
161
+ def metas
162
+ @metas ||= folder.metas.inject({}){ |hash, meta|
163
+ hash[transform_key(meta.name)] = meta.value
164
+ hash
165
+ }
166
+ end
167
+
168
+ def links
169
+ @links ||= folder.links.inject({}){ |hash, link|
170
+ hash[transform_key(link.name)] = link.value
171
+ hash
172
+ }
173
+ end
174
+ end
175
+
176
+ private
177
+ def field_is_primary_id?(field)
178
+ field.key == document.primary_id.name
179
+ end
180
+
181
+ end
182
+
@@ -4,7 +4,7 @@ module SimpleAMS
4
4
  class Document::Fields
5
5
  include Enumerable
6
6
 
7
- attr_reader :members
7
+ Field = Struct.new(:key, :value)
8
8
 
9
9
  def initialize(options)
10
10
  @options = options
@@ -15,52 +15,37 @@ module SimpleAMS
15
15
  found = members.find{|field| field == key}
16
16
  return nil unless found
17
17
 
18
- return with_decorator(found)
18
+ value_of(found)
19
19
  end
20
20
 
21
+ #TODO: Can we make this faster?
21
22
  def each(&block)
22
23
  return enum_for(:each) unless block_given?
23
24
 
24
25
  members.each{ |key|
25
- yield with_decorator(key)
26
+ yield value_of(key)
26
27
  }
27
28
 
28
29
  self
29
30
  end
30
31
 
31
- private
32
- attr_reader :options
33
-
34
- def with_decorator(key)
35
- Field.new(
36
- options.resource,
37
- options.serializer,
38
- key,
39
- options
40
- )
41
- end
42
-
43
- class Field
44
- attr_reader :key
32
+ def any?
33
+ members.any?
34
+ end
45
35
 
46
- #do we need to inject the whole options object?
47
- def initialize(resource, serializer, key, options)
48
- @resource = resource
49
- @serializer = serializer
50
- @key = key
51
- @options = options
52
- end
36
+ def empty?
37
+ members.empty?
38
+ end
53
39
 
54
- def value
55
- return @value if defined?(@value)
40
+ private
41
+ attr_reader :members, :options
56
42
 
57
- return @value = serializer.send(key) if serializer.respond_to? key
58
- binding.pry if resource.is_a?(Array)
59
- return resource.send(key)
43
+ def value_of(key)
44
+ if options.serializer.respond_to?(key)
45
+ Field.new(key, options.serializer.send(key))
46
+ else
47
+ Field.new(key, options.resource.send(key))
60
48
  end
61
-
62
- private
63
- attr_reader :resource, :serializer
64
49
  end
65
50
  end
66
51
  end
@@ -0,0 +1,13 @@
1
+ require "simple_ams"
2
+
3
+ module SimpleAMS
4
+ class Document::Forms < Document::Links
5
+ def initialize(options)
6
+ @options = options
7
+ @members = options.forms
8
+ end
9
+
10
+ class Form < Document::Links::Link
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ require "simple_ams"
2
+
3
+ module SimpleAMS
4
+ class Document::Generics < Document::Links
5
+ def initialize(options)
6
+ @options = options
7
+ @members = options.generics
8
+ end
9
+
10
+ class Option < Document::Links::Link
11
+ end
12
+ end
13
+ end
14
+
@@ -4,7 +4,7 @@ module SimpleAMS
4
4
  class Document::Links
5
5
  include Enumerable
6
6
 
7
- attr_reader :members
7
+ Link = Struct.new(:name, :value, :options)
8
8
 
9
9
  def initialize(options)
10
10
  @options = options
@@ -28,33 +28,23 @@ module SimpleAMS
28
28
  self
29
29
  end
30
30
 
31
- private
32
- attr_reader :options
33
-
34
- def with_decorator(link)
35
- Link.new(link)
36
- end
37
-
38
- #memoization maybe ?
39
- class Link
40
- def initialize(link)
41
- @link = link
42
- end
31
+ def any?
32
+ members.any?
33
+ end
43
34
 
44
- def name
45
- link.name
46
- end
35
+ def empty?
36
+ members.empty?
37
+ end
47
38
 
48
- def value
49
- link.respond_to?(:call) ? link.value.call : link.value
50
- end
39
+ private
40
+ attr_reader :members, :options
51
41
 
52
- def options
42
+ def with_decorator(link)
43
+ Link.new(
44
+ link.name,
45
+ link.respond_to?(:call) ? link.value.call : link.value,
53
46
  link.options
54
- end
55
-
56
- private
57
- attr_reader :link
47
+ )
58
48
  end
59
49
  end
60
50
  end
@@ -3,7 +3,7 @@ require "simple_ams"
3
3
  module SimpleAMS
4
4
  class Document::Metas < Document::Links
5
5
  def initialize(options)
6
- super
6
+ @options = options
7
7
  @members = options.metas
8
8
  end
9
9
 
@@ -0,0 +1,23 @@
1
+ module SimpleAMS
2
+ class Document::PrimaryId
3
+ attr_reader :name
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ @name = options.primary_id.name
8
+ end
9
+
10
+ def value
11
+ if @options.serializer.respond_to?(name)
12
+ @options.serializer.send(name)
13
+ else
14
+ @options.resource.send(name)
15
+ end
16
+ end
17
+
18
+ def options
19
+ @options.primary_id.options
20
+ end
21
+ end
22
+ end
23
+
@@ -5,9 +5,9 @@ module SimpleAMS
5
5
  class Document::Relations
6
6
  include Enumerable
7
7
 
8
- def initialize(options)
8
+ def initialize(options, relations)
9
9
  @options = options
10
- @relations = options.relations
10
+ @relations = relations
11
11
  @serializer = options.serializer
12
12
  @resource = options.resource
13
13
  end
@@ -33,13 +33,29 @@ module SimpleAMS
33
33
  count == 0
34
34
  end
35
35
 
36
+ def available
37
+ return @available ||= [] if relations.available.empty?
38
+
39
+ @available ||= self.class.new(options, relations.available)
40
+ end
41
+
36
42
  private
37
43
  attr_reader :options, :relations, :serializer, :resource
38
44
 
39
45
  def relation_for(relation)
40
- renderer_klass_for(relation).new(
46
+ relation_value = relation_value_for(relation.name)
47
+
48
+ renderer_klass_for(relation, relation_value).new(
41
49
  SimpleAMS::Options.new(
42
- relation_value_for(relation.name), relation_options_for(relation)
50
+ relation_value, relation_options_for(relation, relation_value)
51
+ ),
52
+ SimpleAMS::Options.new(
53
+ resource, {
54
+ injected_options: {
55
+ serializer: relation.embedded
56
+ },
57
+ allowed_options: relation.embedded.options
58
+ }
43
59
  )
44
60
  )
45
61
  end
@@ -57,12 +73,12 @@ module SimpleAMS
57
73
  # *user injected when instantiating the SimpleAMS class
58
74
  # *relation options injected from parent serializer
59
75
  # *serializer class options
60
- def relation_options_for(relation)
76
+ def relation_options_for(relation, relation_value)
61
77
  _relation_options = {
62
78
  injected_options: (relation.options || {}).merge(
63
79
  options.relation_options_for(
64
80
  relation.name
65
- ).merge(
81
+ ).select{|k, v| !v.nil?}.merge(
66
82
  expose: options.expose
67
83
  )
68
84
  ).merge(
@@ -71,19 +87,33 @@ module SimpleAMS
71
87
  }
72
88
  )
73
89
  }
90
+
91
+ if relation.collection? || relation_value.respond_to?(:each)
74
92
  #TODO: deep merge, can we automate this somehow ?
75
- _relation_options[:injected_options][:collection] = (_relation_options[:collection] || {}).merge(
76
- name: relation.name
77
- )
93
+ _relation_options[:injected_options][:collection] = {
94
+ name: relation.name
95
+ }.merge(_relation_options[:injected_options][:collection] || {})
96
+ else
97
+ _relation_options[:injected_options][:name] = relation.name
98
+ end
78
99
 
79
100
  return _relation_options
80
101
  end
102
+ =begin
103
+ def embedded_relation_options_for(relation)
104
+ _relation_options = relation_options_for(relation).merge(
105
+ allowed_options: relation.embedded.options
106
+ )
107
+ _relation_options[:injected_options][:serializer] = relation.embedded
81
108
 
82
- def renderer_klass_for(relation)
83
- renderer = SimpleAMS::Document
84
- collection_renderer = renderer::Folder
109
+ return _relation_options
110
+ end
111
+ =end
85
112
 
86
- relation.collection? ? collection_renderer : renderer
113
+ def renderer_klass_for(relation, relation_value)
114
+ return SimpleAMS::Document::Folder if relation.collection?
115
+ return SimpleAMS::Document::Folder if relation_value.respond_to?(:each)
116
+ return SimpleAMS::Document
87
117
  end
88
118
 
89
119
  =begin TODO: Add that as public method, should help performance in edge cases