simple_ams 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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