supa 0.2.0 → 0.2.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.
data/.travis.yml CHANGED
@@ -2,19 +2,11 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.3.1
5
-
6
- notifications:
7
- email: false
8
-
5
+ before_install: gem install bundler -v 1.13.1
9
6
  addons:
10
7
  code_climate:
11
- repo_token: ad743cd1c267d4a84bfa620869f906a68efba51baa3aadc0638b88330ce3ab3a
12
-
13
- cache:
14
- - bundler
15
-
16
- before_install: gem install bundler -v 1.13.7
17
- script:
18
- - bundle exec rake
8
+ repo_token: eb758a7a8b04369f929aa1b962bc9764b6517539971b785600065cf5ad93e282
19
9
  after_success:
20
10
  - bundle exec codeclimate-test-reporter
11
+ notifications:
12
+ email: false
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ # Specify your gem's dependencies in supa.gemspec
3
4
  gemspec
data/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
3
  Ruby object → JSON serialization.
4
4
 
5
- [![Build Status](https://travis-ci.org/distribusion/supa.svg?branch=master)](https://travis-ci.org/distribusion/supa)
6
- [![Code Climate](https://codeclimate.com/repos/587387071c36ea7203000e0d/badges/19b714c64bf6f028a58c/gpa.svg)](https://codeclimate.com/repos/587387071c36ea7203000e0d/feed)
7
- [![Test Coverage](https://codeclimate.com/repos/587387071c36ea7203000e0d/badges/19b714c64bf6f028a58c/coverage.svg)](https://codeclimate.com/repos/587387071c36ea7203000e0d/coverage)
8
- [![Issue Count](https://codeclimate.com/repos/587387071c36ea7203000e0d/badges/19b714c64bf6f028a58c/issue_count.svg)](https://codeclimate.com/repos/587387071c36ea7203000e0d/feed)
5
+ [![Build Status](https://travis-ci.org/dasnotme/supa.svg?branch=master)](https://travis-ci.org/dasnotme/supa)
6
+ [![Code Climate](https://codeclimate.com/github/dasnotme/supa/badges/gpa.svg)](https://codeclimate.com/github/dasnotme/supa)
7
+ [![Test Coverage](https://codeclimate.com/github/dasnotme/supa/badges/coverage.svg)](https://codeclimate.com/github/dasnotme/supa/coverage)
8
+ [![Issue Count](https://codeclimate.com/github/dasnotme/supa/badges/issue_count.svg)](https://codeclimate.com/github/dasnotme/supa)
9
9
 
10
10
  ## Introduction
11
11
 
@@ -49,60 +49,75 @@ end
49
49
  ```
50
50
 
51
51
  ```ruby
52
- class ArticleRepresenter
53
- include Supa::Representable
52
+ module Supa
53
+ class ArticleRepresenter
54
+ include Supa::Representable
54
55
 
55
- define do
56
- namespace :jsonapi do
57
- attribute :version, getter: proc { 1.1 }
58
- end
59
-
60
- namespace :data do
61
- attribute :id
62
- attribute :type, getter: proc { 'articles' }
56
+ define do
57
+ namespace :jsonapi do
58
+ virtual :version, 1.1, modifier: :to_s
59
+ end
63
60
 
64
- namespace :attributes do
65
- attribute :title
66
- attribute :text
61
+ namespace :meta do
62
+ attribute :locale, :language, exec_context: :representer
67
63
  end
68
64
 
69
- namespace :relationships do
70
- object :author do
71
- namespace :data do
72
- attribute :id
73
- attribute :type, getter: proc { 'authors' }
74
- end
65
+ namespace :data do
66
+ attribute :id
67
+ virtual :type, 'articles'
68
+
69
+ namespace :attributes do
70
+ attribute :title
71
+ attribute :text
75
72
  end
76
73
 
77
- namespace :comments do
78
- collection :data, getter: proc { self.comments } do
79
- attribute :id
80
- attribute :type, getter: proc { 'comments' }
74
+ namespace :relationships do
75
+ object :author do
76
+ namespace :data do
77
+ attribute :id
78
+ virtual :type, 'authors'
79
+ end
80
+ end
81
+
82
+ namespace :comments do
83
+ collection :data, :comments do
84
+ attribute :id
85
+ virtual :type, 'comments'
86
+ end
81
87
  end
82
88
  end
83
89
  end
84
- end
85
90
 
86
- collection :included, getter: proc { [self.author] } do
87
- attribute :id
88
- attribute :type, getter: proc { 'authors' }
91
+ collection :included, :author do
92
+ attribute :id
93
+ virtual :type, 'authors'
89
94
 
90
- namespace :attributes do
91
- attribute :first_name
92
- attribute :last_name
95
+ namespace :attributes do
96
+ attribute :first_name
97
+ attribute :last_name
98
+ end
93
99
  end
94
- end
95
100
 
96
- collection :included, getter: proc { self.comments }, squash: true do
97
- attribute :id
98
- attribute :type, getter: proc { 'comments' }
101
+ append :included, :comments do
102
+ attribute :id
103
+ virtual :type, 'comments'
99
104
 
100
- namespace :attributes do
101
- attribute :text
105
+ namespace :attributes do
106
+ attribute :text
107
+ end
102
108
  end
103
109
  end
110
+
111
+ def to_s(value)
112
+ value.to_s
113
+ end
114
+
115
+ def language
116
+ 'en'
117
+ end
104
118
  end
105
119
  end
120
+
106
121
  ```
107
122
 
108
123
  ```ruby
@@ -112,7 +127,10 @@ ArticleRepresenter.new(Article.new).to_json
112
127
  ```json
113
128
  {
114
129
  "jsonapi": {
115
- "version": 1.1
130
+ "version": "1.1"
131
+ },
132
+ "meta": {
133
+ "locale": "en"
116
134
  },
117
135
  "data": {
118
136
  "id": "7aa15512-1f9d-4a86-98ad-4bb0aae487a2",
@@ -171,58 +189,15 @@ ArticleRepresenter.new(Article.new).to_json
171
189
 
172
190
  ### `attribute`
173
191
 
192
+ ### `virtual`
193
+
174
194
  ### `namespace`
175
195
 
176
196
  ### `object`
177
197
 
178
198
  ### `collection`
179
199
 
180
- #### `:squash` option
181
-
182
- Passing `true` to `:squash` option results in merging collection with the previous one
183
-
184
- ```ruby
185
- class AnimalsRepresenter
186
- include Supa::Representable
187
-
188
- define do
189
- collection :animals, getter: -> { [{name: 'Rex', type: 'dogs'}] } do
190
- attribute :name
191
- attribute :type
192
- end
193
-
194
- collection :animals, getter: -> { [{name: 'Tom', type: 'cats'}] }, squash: true do
195
- attribute :name
196
- attribute :type
197
- end
198
- end
199
- end
200
- ```
201
-
202
- ```ruby
203
- AnimalsRepresenter.new(nil).to_hash
204
- ```
205
-
206
- ```ruby
207
- {
208
- animals: [
209
- {name: 'Rex', type: 'dogs'},
210
- {name: 'Tom', type: 'cats'}
211
- ]
212
- }
213
- ```
214
-
215
- ### `:getter` option
216
-
217
- Avoid passing Proc objects to `:getter` option because this is little slower than method name passing
218
-
219
- ```ruby
220
- # Bad
221
- attribute :name, getter: -> { fetch_name }
222
-
223
- # Good
224
- attribute :name, getter: :fetch_name
225
- ```
200
+ ### `append`
226
201
 
227
202
  ## Development
228
203
 
@@ -248,9 +223,10 @@ bin/console
248
223
 
249
224
  ## Contributing
250
225
 
251
- Bug reports and pull requests are welcome on GitHub at https://github.com/distribusion/supa.
252
- This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
226
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dasnotme/supa. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
227
+
253
228
 
254
229
  ## License
255
230
 
256
231
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
232
+
data/Rakefile CHANGED
@@ -1,6 +1,14 @@
1
1
  require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
2
+ require 'rake/testtask'
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << %w(spec lib)
6
+ t.test_files = FileList['spec/**/*_spec.rb']
7
+ end
5
8
 
6
- task default: :spec
9
+ Rake::TestTask.new(:bench) do |t|
10
+ t.libs << %w(spec lib)
11
+ t.test_files = FileList['spec/benchmarks/**/*_bench.rb']
12
+ end
13
+
14
+ task :default => :test
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bundler/setup'
4
- require 'supa'
3
+ require "bundler/setup"
4
+ require "supa"
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,5 @@ require 'supa'
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require 'pry-byebug'
14
- Pry.start
13
+ require "irb"
14
+ IRB.start
data/lib/supa.rb CHANGED
@@ -1,12 +1,6 @@
1
1
  require 'supa/version'
2
- require 'supa/representable'
3
- require 'supa/command'
4
- require 'supa/commands/traits/collectionable'
5
- require 'supa/commands/attribute'
6
- require 'supa/commands/object'
7
- require 'supa/commands/namespace'
8
- require 'supa/commands/collection'
9
2
  require 'supa/builder'
3
+ require 'supa/representable'
10
4
 
11
5
  module Supa
12
6
  end
data/lib/supa/builder.rb CHANGED
@@ -1,34 +1,44 @@
1
+ require 'supa/commands/attribute'
2
+ require 'supa/commands/virtual'
3
+ require 'supa/commands/object'
4
+ require 'supa/commands/namespace'
5
+ require 'supa/commands/collection'
6
+ require 'supa/commands/append'
7
+
1
8
  module Supa
2
9
  class Builder
3
- COMMANDS = %w(attribute object namespace collection).freeze
10
+ COMMANDS = %w(attribute virtual object namespace collection append).freeze
4
11
 
5
12
  COMMANDS.each do |command|
6
13
  klass = Supa::Commands.const_get(command.capitalize)
7
14
 
8
- define_method command do |name, options = {}, &block|
9
- klass.new(
10
- @object,
11
- tree: @tree,
12
- representer: @representer,
13
- name: name,
14
- options: options,
15
- &block
16
- ).represent
15
+ define_method command do |name, getter = nil, options = {}, &block|
16
+ klass.new(representer: representer,
17
+ context: context,
18
+ tree: tree,
19
+ name: name,
20
+ getter: getter,
21
+ options: options,
22
+ &block).represent
17
23
  end
18
24
  end
19
25
 
20
- def initialize(object, tree:, representer:)
21
- @object = object
22
- @tree = tree
26
+ def initialize(representer:, context:, tree:)
23
27
  @representer = representer
28
+ @context = context
29
+ @tree = tree
24
30
  end
25
31
 
26
32
  def to_hash
27
- @tree.to_hash
33
+ tree.to_hash
28
34
  end
29
35
 
30
36
  def to_json
31
37
  to_hash.to_json
32
38
  end
39
+
40
+ private
41
+
42
+ attr_reader :representer, :context, :tree
33
43
  end
34
44
  end
data/lib/supa/command.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  module Supa
2
2
  class Command
3
- def initialize(object, tree:, representer:, name:, options: {}, &block)
4
- @object = object
5
- @tree = tree
3
+ def initialize(representer:, context:, tree:, name:, getter:, options: {}, &block)
6
4
  @representer = representer
5
+ @context = context
6
+ @tree = tree
7
7
  @name = name
8
+ @getter = getter
8
9
  @options = options
9
10
  @block = block
10
11
  end
@@ -14,43 +15,50 @@ module Supa
14
15
  end
15
16
 
16
17
  private
18
+ attr_reader :representer, :context, :tree, :name, :options, :block
17
19
 
18
- def value
19
- return instance_exec(&value_accessor) if value_accessor.respond_to?(:call)
20
+ def apply_modifier(value)
21
+ with_modifier? ? representer.send(modifier, value) : value
22
+ end
20
23
 
21
- extracted_value = derived_value_from_object(@object) || derived_value_from_object(@representer)
22
- extracted_value ||= literal_value
24
+ def modifier
25
+ options[:modifier]
26
+ end
23
27
 
24
- raise_no_method_error(value_accessor) if extracted_value.nil?
25
- extracted_value
28
+ def with_modifier?
29
+ !!options[:modifier]
26
30
  end
27
31
 
28
- def value_accessor
29
- @value_accessor ||= @options.fetch(:getter, @name)
32
+ def static_value
33
+ value = getter
34
+
35
+ apply_modifier(value)
30
36
  end
31
37
 
32
- def derived_value_from_object(object)
33
- if value_accessor.respond_to?(:to_sym) && object.respond_to?(value_accessor)
34
- object.send(value_accessor.to_sym)
35
- elsif object.is_a?(Hash)
36
- object.dig(value_accessor)
38
+ def dynamic_value
39
+ value = if exec_on_object?
40
+ value_from_object
41
+ else
42
+ value_from_representer
37
43
  end
44
+
45
+ apply_modifier(value)
38
46
  end
39
47
 
40
- def literal_value
41
- return if value_accessor.respond_to?(:call)
42
- value_accessor unless value_accessor.is_a?(Symbol) || value_accessor.is_a?(Enumerable)
48
+ def exec_on_object?
49
+ options[:exec_context] != :representer
43
50
  end
44
51
 
45
- def raise_no_method_error(method_sym)
46
- raise NoMethodError, "undefined method `#{method_sym}' for #{@object} or #{@representer}"
52
+ def value_from_object
53
+ context.is_a?(Hash) ? context[getter] : context.send(getter)
47
54
  end
48
55
 
49
- def method_missing(method_sym, *args, &block)
50
- return @representer.send(method_sym, *args, &block) if @representer.respond_to?(method_sym)
51
- return @object.send(method_sym, *args, &block) if @object.respond_to?(method_sym)
56
+ def value_from_representer
57
+ representer.send(getter)
58
+ end
52
59
 
53
- raise_no_method_error(method_sym)
60
+ def getter
61
+ @getter || @name
54
62
  end
55
63
  end
56
64
  end
@@ -0,0 +1,17 @@
1
+ require 'supa/command'
2
+
3
+ module Supa
4
+ module Commands
5
+ class Append < Supa::Command
6
+ def represent
7
+ tree[name] ||= []
8
+
9
+ Array(dynamic_value).each do |element|
10
+ tree[name] << {}
11
+
12
+ Supa::Builder.new(representer: representer, context: element, tree: tree[name][-1]).instance_exec(&block)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end