datasource 0.0.1 → 0.0.2
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 +4 -4
- data/README.md +211 -0
- data/lib/datasource/adapters/active_record.rb +188 -89
- data/lib/datasource/adapters/sequel.rb +200 -0
- data/lib/datasource/attributes/computed_attribute.rb +11 -15
- data/lib/datasource/attributes/loader.rb +65 -0
- data/lib/datasource/attributes/query_attribute.rb +8 -3
- data/lib/datasource/base.rb +144 -51
- data/lib/datasource/consumer_adapters/active_model_serializers.rb +66 -0
- data/lib/datasource/serializer.rb +117 -0
- data/lib/datasource.rb +21 -3
- data/lib/generators/datasource/install_generator.rb +8 -0
- data/lib/generators/datasource/templates/initializer.rb +11 -0
- data/test/active_record_helper.rb +13 -0
- data/test/schema.rb +1 -15
- data/test/test_helper.rb +4 -2
- data/test/test_loader.rb +79 -0
- data/test/test_scope.rb +50 -0
- data/test/test_serializer.rb +52 -0
- metadata +44 -8
- data/lib/datasource/serializer/composite.rb +0 -119
- data/test/test_datasource.rb +0 -47
- data/test/test_serializer_composite.rb +0 -48
@@ -0,0 +1,117 @@
|
|
1
|
+
module Datasource
|
2
|
+
class Serializer
|
3
|
+
TemplatePart = Struct.new(:parent, :type, :value, :select) do
|
4
|
+
def initialize(parent, type = nil, value = nil)
|
5
|
+
super(parent, type, get_default_value(type, value), [])
|
6
|
+
end
|
7
|
+
|
8
|
+
def set_default_value(value = nil)
|
9
|
+
self.value = get_default_value(type, value)
|
10
|
+
end
|
11
|
+
private
|
12
|
+
def get_default_value(type, value)
|
13
|
+
case type
|
14
|
+
when :hash then {}
|
15
|
+
when :array then []
|
16
|
+
when :datasource then value
|
17
|
+
when nil then nil
|
18
|
+
else
|
19
|
+
fail "Unknown type #{type}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_accessor :datasource_count, :template
|
26
|
+
|
27
|
+
def inherited(base)
|
28
|
+
base.datasource_count = 0
|
29
|
+
@cursor = base.template = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_new_cursor(type, value = nil, &block)
|
33
|
+
result = nil
|
34
|
+
new_cursor = TemplatePart.new(@cursor, type, value)
|
35
|
+
@cursor = @cursor.tap do
|
36
|
+
if template.nil?
|
37
|
+
self.template = @cursor = new_cursor
|
38
|
+
elsif @cursor.type == :datasource
|
39
|
+
@cursor = @cursor.parent
|
40
|
+
return with_new_cursor(type, value, &block)
|
41
|
+
elsif @cursor.type == :array
|
42
|
+
@cursor = new_cursor
|
43
|
+
@cursor.parent.push(@cursor)
|
44
|
+
elsif @cursor.type.nil?
|
45
|
+
# replace cursor
|
46
|
+
@cursor.type = type
|
47
|
+
@cursor.set_default_value(value)
|
48
|
+
else
|
49
|
+
fail "Invalid use of #{type}."
|
50
|
+
end
|
51
|
+
result = block.call
|
52
|
+
end
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def hash(&block)
|
57
|
+
with_new_cursor(:hash, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def key(name)
|
61
|
+
fail "Cannot use key outside hash." unless template && @cursor.type == :hash
|
62
|
+
@cursor = @cursor.tap do
|
63
|
+
@cursor = @cursor.value[name.to_s] = TemplatePart.new(@cursor)
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def array(&block)
|
69
|
+
with_new_cursor(:array, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def datasource(ds)
|
73
|
+
self.datasource_count += 1
|
74
|
+
@cursor = with_new_cursor(:datasource, ds) { @cursor }
|
75
|
+
end
|
76
|
+
|
77
|
+
def attributes(*attributes)
|
78
|
+
attributes.each { |name| attribute name }
|
79
|
+
end
|
80
|
+
|
81
|
+
def attribute(name)
|
82
|
+
fail "No datasource selected - use \"select_datasource Klass\" first." unless template && @cursor.type == :datasource
|
83
|
+
@cursor.select << name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize(*scopes)
|
88
|
+
@scopes = scopes
|
89
|
+
if @scopes.size != self.class.datasource_count
|
90
|
+
fail ArgumentError, "#{self.class.name} needs #{self.class.datasource_count} scopes, you provided #{@scopes.size}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def as_json
|
95
|
+
parse_template_part(self.class.template)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def parse_template_part(part)
|
100
|
+
return nil unless part
|
101
|
+
case part.type
|
102
|
+
when :hash
|
103
|
+
{}.tap do |result|
|
104
|
+
part.value.each_pair do |k, v|
|
105
|
+
result[k] = parse_template_part(v)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
when :array
|
109
|
+
part.value.map { |v| parse_template_part(v) }
|
110
|
+
when :datasource
|
111
|
+
part.value.new(@scopes.shift).select(*part.select).results
|
112
|
+
else
|
113
|
+
fail "Unknown type #{type}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/datasource.rb
CHANGED
@@ -1,8 +1,26 @@
|
|
1
|
+
module Datasource
|
2
|
+
Error = Class.new(StandardError)
|
3
|
+
RecursionError = Class.new(StandardError)
|
4
|
+
|
5
|
+
def self.load(adapter = nil)
|
6
|
+
unless adapter
|
7
|
+
adapter = if defined? ActiveRecord
|
8
|
+
:activerecord
|
9
|
+
elsif defined? Sequel
|
10
|
+
:sequel
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'datasource/adapters/active_record' if [:activerecord, :active_record].include?(adapter)
|
15
|
+
require 'datasource/adapters/sequel' if adapter == :sequel
|
16
|
+
require 'datasource/consumer_adapters/active_model_serializers' if defined? ActiveModel::Serializer
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
1
20
|
require 'datasource/base'
|
2
21
|
|
3
22
|
require 'datasource/attributes/computed_attribute'
|
4
23
|
require 'datasource/attributes/query_attribute'
|
24
|
+
require 'datasource/attributes/loader'
|
5
25
|
|
6
|
-
require 'datasource/
|
7
|
-
|
8
|
-
require 'datasource/serializer/composite'
|
26
|
+
require 'datasource/serializer'
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class Datasource::InstallGenerator < ::Rails::Generators::Base
|
2
|
+
source_root File.expand_path("../templates", __FILE__)
|
3
|
+
desc 'Creates a Datasource Rails initializer'
|
4
|
+
|
5
|
+
def create_serializer_file
|
6
|
+
template 'initializer.rb', 'config/initializers/datasource.rb'
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
Datasource.load(:activerecord)
|
2
|
+
|
3
|
+
if defined? ActiveModel::Serializer
|
4
|
+
if ActiveModel::Serializer.respond_to?(:config)
|
5
|
+
ActiveModel::Serializer.config.array_serializer = Datasource::ArrayAMS
|
6
|
+
else
|
7
|
+
ActiveModel::Serializer.setup do |config|
|
8
|
+
config.array_serializer = Datasource::ArrayAMS
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -8,3 +8,16 @@ def clean_db
|
|
8
8
|
ActiveRecord::Base.connection.execute("DELETE FROM blogs")
|
9
9
|
ActiveRecord::Base.connection.execute("DELETE FROM sqlite_sequence")
|
10
10
|
end
|
11
|
+
|
12
|
+
def assert_query_count(count)
|
13
|
+
old_logger = ActiveRecord::Base.logger
|
14
|
+
logger = StringIO.new
|
15
|
+
ActiveRecord::Base.logger = Logger.new(logger)
|
16
|
+
begin
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
ActiveRecord::Base.logger = old_logger
|
20
|
+
# puts logger.string
|
21
|
+
end
|
22
|
+
assert_equal count, logger.string.lines.count
|
23
|
+
end
|
data/test/schema.rb
CHANGED
@@ -16,18 +16,4 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
self.table_name = "blogs"
|
21
|
-
has_many :posts
|
22
|
-
end
|
23
|
-
|
24
|
-
class Post < ActiveRecord::Base
|
25
|
-
self.table_name = "posts"
|
26
|
-
belongs_to :blog
|
27
|
-
has_many :comments
|
28
|
-
end
|
29
|
-
|
30
|
-
class Comment < ActiveRecord::Base
|
31
|
-
self.table_name = "comments"
|
32
|
-
belongs_to :post
|
33
|
-
end
|
19
|
+
ActiveRecord::Base.send :include, Datasource::Adapters::ActiveRecord::Model
|
data/test/test_helper.rb
CHANGED
@@ -5,8 +5,10 @@ require 'minitest/autorun'
|
|
5
5
|
require 'active_support/all'
|
6
6
|
require 'active_record'
|
7
7
|
require 'datasource'
|
8
|
+
require 'active_model_serializers'
|
9
|
+
require 'pry'
|
10
|
+
|
11
|
+
Datasource.load(:activerecord)
|
8
12
|
|
9
13
|
# Load support files
|
10
14
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
11
|
-
|
12
|
-
# ActiveRecord::Base.logger = Logger.new STDOUT
|
data/test/test_loader.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_record_helper'
|
3
|
+
|
4
|
+
class LoaderTest < ActiveSupport::TestCase
|
5
|
+
class Comment < ActiveRecord::Base
|
6
|
+
self.table_name = "comments"
|
7
|
+
belongs_to :post
|
8
|
+
end
|
9
|
+
|
10
|
+
class Post < ActiveRecord::Base
|
11
|
+
self.table_name = "posts"
|
12
|
+
has_many :comments
|
13
|
+
|
14
|
+
datasource_module do
|
15
|
+
loader :newest_comment, group_by: :post_id, one: true do |post_ids|
|
16
|
+
Comment.for_serializer.where(post_id: post_ids)
|
17
|
+
.group("post_id")
|
18
|
+
.having("id = MAX(id)")
|
19
|
+
.datasource_select(:post_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
loader :newest_comment_text, array_to_hash: true do |post_ids|
|
23
|
+
Comment.where(post_id: post_ids)
|
24
|
+
.group("post_id")
|
25
|
+
.having("id = MAX(id)")
|
26
|
+
.pluck("post_id, comment")
|
27
|
+
end
|
28
|
+
|
29
|
+
loader :ordered_comments, group_by: :post_id do |post_ids|
|
30
|
+
Comment.for_serializer(CommentSerializer).where(post_id: post_ids)
|
31
|
+
.order("post_id, id desc")
|
32
|
+
.datasource_select(:post_id)
|
33
|
+
end
|
34
|
+
|
35
|
+
computed :newest_comment, loaders: :newest_comment
|
36
|
+
computed :newest_comment_text, loaders: :newest_comment_text
|
37
|
+
computed :ordered_comments, loaders: :ordered_comments
|
38
|
+
end
|
39
|
+
|
40
|
+
def name_initials
|
41
|
+
return unless author_first_name && author_last_name
|
42
|
+
author_first_name[0].upcase + author_last_name[0].upcase
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class CommentSerializer < ActiveModel::Serializer
|
47
|
+
attributes :id, :comment
|
48
|
+
end
|
49
|
+
|
50
|
+
class PostSerializer < ActiveModel::Serializer
|
51
|
+
attributes :id, :title, :newest_comment, :newest_comment_text, :ordered_comments
|
52
|
+
|
53
|
+
def newest_comment
|
54
|
+
CommentSerializer.new(object.loaded_values[:newest_comment]).as_json
|
55
|
+
end
|
56
|
+
|
57
|
+
def newest_comment_text
|
58
|
+
object.loaded_values[:newest_comment_text]
|
59
|
+
end
|
60
|
+
|
61
|
+
def ordered_comments
|
62
|
+
Datasource::ArrayAMS.new(object.loaded_values[:ordered_comments]).as_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_loader
|
67
|
+
post = Post.create! title: "First Post"
|
68
|
+
2.times { |i| post.comments.create! comment: "Comment #{i+1}" }
|
69
|
+
|
70
|
+
assert_query_count(4) do
|
71
|
+
assert_equal Datasource::ArrayAMS.new(Post.all).as_json,
|
72
|
+
[{:id=>1, :title=>"First Post", :newest_comment=>{"comment"=>{:id=>2, :comment=>"Comment 2"}}, :newest_comment_text=>"Comment 2", :ordered_comments=>[{:id=>2, :comment=>"Comment 2"}, {:id=>1, :comment=>"Comment 1"}]}]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def teardown
|
77
|
+
clean_db
|
78
|
+
end
|
79
|
+
end
|
data/test/test_scope.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_record_helper'
|
3
|
+
|
4
|
+
class ScopeTest < ActiveSupport::TestCase
|
5
|
+
class Post < ActiveRecord::Base
|
6
|
+
belongs_to :blog
|
7
|
+
|
8
|
+
datasource_module do
|
9
|
+
query :author_name do
|
10
|
+
"posts.author_first_name || ' ' || posts.author_last_name"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class PostSerializer < ActiveModel::Serializer
|
16
|
+
attributes :id, :title, :author_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_first
|
20
|
+
Post.create! title: "The Post", author_first_name: "John", author_last_name: "Doe", blog_id: 10
|
21
|
+
post = Post.for_serializer.first
|
22
|
+
|
23
|
+
assert_equal("The Post", post.title)
|
24
|
+
assert_equal("John Doe", post.author_name)
|
25
|
+
assert_raises(ActiveModel::MissingAttributeError) { post.blog_id }
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_find
|
29
|
+
post = Post.create! title: "The Post", author_first_name: "John", author_last_name: "Doe", blog_id: 10
|
30
|
+
post = Post.for_serializer.find(post.id)
|
31
|
+
|
32
|
+
assert_equal("The Post", post.title)
|
33
|
+
assert_equal("John Doe", post.author_name)
|
34
|
+
assert_raises(ActiveModel::MissingAttributeError) { post.blog_id }
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_each
|
38
|
+
post = Post.create! title: "The Post", author_first_name: "John", author_last_name: "Doe", blog_id: 10
|
39
|
+
|
40
|
+
Post.for_serializer.each do |post|
|
41
|
+
assert_equal("The Post", post.title)
|
42
|
+
assert_equal("John Doe", post.author_name)
|
43
|
+
assert_raises(ActiveModel::MissingAttributeError) { post.blog_id }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def teardown
|
48
|
+
clean_db
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'active_record_helper'
|
3
|
+
|
4
|
+
class SerializerTest < ActiveSupport::TestCase
|
5
|
+
class Post < ActiveRecord::Base
|
6
|
+
belongs_to :blog
|
7
|
+
|
8
|
+
datasource_module do
|
9
|
+
query :author_name do
|
10
|
+
"posts.author_first_name || ' ' || posts.author_last_name"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Blog < ActiveRecord::Base
|
16
|
+
has_many :posts
|
17
|
+
end
|
18
|
+
|
19
|
+
class BlogSerializer < ActiveModel::Serializer
|
20
|
+
attributes :id, :title
|
21
|
+
|
22
|
+
has_many :posts
|
23
|
+
end
|
24
|
+
|
25
|
+
class PostSerializer < ActiveModel::Serializer
|
26
|
+
attributes :id, :title, :author_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_blogs_and_posts_serializer
|
30
|
+
blog = Blog.create! title: "Blog 1"
|
31
|
+
blog.posts.create! title: "Post 1", author_first_name: "John", author_last_name: "Doe"
|
32
|
+
blog.posts.create! title: "Post 2", author_first_name: "Maria", author_last_name: "Doe"
|
33
|
+
blog = Blog.create! title: "Blog 2"
|
34
|
+
|
35
|
+
expected_result = [
|
36
|
+
{:id =>1, :title =>"Blog 1", :posts =>[
|
37
|
+
{:id =>1, :title =>"Post 1", :author_name =>"John Doe"},
|
38
|
+
{:id =>2, :title =>"Post 2", :author_name =>"Maria Doe"}
|
39
|
+
]},
|
40
|
+
{:id =>2, :title =>"Blog 2", :posts =>[]}
|
41
|
+
]
|
42
|
+
|
43
|
+
assert_query_count(2) do
|
44
|
+
serializer = Datasource::ArrayAMS.new(Blog.all)
|
45
|
+
assert_equal(expected_result, serializer.as_json)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def teardown
|
50
|
+
clean_db
|
51
|
+
end
|
52
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: datasource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jan Berdajs
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-12-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: active_model_serializers
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: activerecord
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -17,7 +31,7 @@ dependencies:
|
|
17
31
|
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
33
|
version: '4'
|
20
|
-
type: :
|
34
|
+
type: :development
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0.9'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4'
|
41
69
|
description:
|
42
70
|
email:
|
43
71
|
- mrbrdo@gmail.com
|
@@ -50,15 +78,21 @@ files:
|
|
50
78
|
- Rakefile
|
51
79
|
- lib/datasource.rb
|
52
80
|
- lib/datasource/adapters/active_record.rb
|
81
|
+
- lib/datasource/adapters/sequel.rb
|
53
82
|
- lib/datasource/attributes/computed_attribute.rb
|
83
|
+
- lib/datasource/attributes/loader.rb
|
54
84
|
- lib/datasource/attributes/query_attribute.rb
|
55
85
|
- lib/datasource/base.rb
|
56
|
-
- lib/datasource/
|
86
|
+
- lib/datasource/consumer_adapters/active_model_serializers.rb
|
87
|
+
- lib/datasource/serializer.rb
|
88
|
+
- lib/generators/datasource/install_generator.rb
|
89
|
+
- lib/generators/datasource/templates/initializer.rb
|
57
90
|
- test/active_record_helper.rb
|
58
91
|
- test/schema.rb
|
59
|
-
- test/test_datasource.rb
|
60
92
|
- test/test_helper.rb
|
61
|
-
- test/
|
93
|
+
- test/test_loader.rb
|
94
|
+
- test/test_scope.rb
|
95
|
+
- test/test_serializer.rb
|
62
96
|
homepage: https://github.com/mrbrdo/datasource
|
63
97
|
licenses:
|
64
98
|
- MIT
|
@@ -86,6 +120,8 @@ summary: Ruby library for creating data source objects from database data
|
|
86
120
|
test_files:
|
87
121
|
- test/active_record_helper.rb
|
88
122
|
- test/schema.rb
|
89
|
-
- test/test_datasource.rb
|
90
123
|
- test/test_helper.rb
|
91
|
-
- test/
|
124
|
+
- test/test_loader.rb
|
125
|
+
- test/test_scope.rb
|
126
|
+
- test/test_serializer.rb
|
127
|
+
has_rdoc:
|
@@ -1,119 +0,0 @@
|
|
1
|
-
module Datasource
|
2
|
-
module Serializer
|
3
|
-
class Composite
|
4
|
-
TemplatePart = Struct.new(:parent, :type, :value, :select) do
|
5
|
-
def initialize(parent, type = nil, value = nil)
|
6
|
-
super(parent, type, get_default_value(type, value), [])
|
7
|
-
end
|
8
|
-
|
9
|
-
def set_default_value(value = nil)
|
10
|
-
self.value = get_default_value(type, value)
|
11
|
-
end
|
12
|
-
private
|
13
|
-
def get_default_value(type, value)
|
14
|
-
case type
|
15
|
-
when :hash then {}
|
16
|
-
when :array then []
|
17
|
-
when :datasource then value
|
18
|
-
when nil then nil
|
19
|
-
else
|
20
|
-
fail "Unknown type #{type}"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
class << self
|
26
|
-
attr_accessor :datasource_count, :template
|
27
|
-
|
28
|
-
def inherited(base)
|
29
|
-
base.datasource_count = 0
|
30
|
-
@cursor = base.template = nil
|
31
|
-
end
|
32
|
-
|
33
|
-
def with_new_cursor(type, value = nil, &block)
|
34
|
-
result = nil
|
35
|
-
new_cursor = TemplatePart.new(@cursor, type, value)
|
36
|
-
@cursor = @cursor.tap do
|
37
|
-
if template.nil?
|
38
|
-
self.template = @cursor = new_cursor
|
39
|
-
elsif @cursor.type == :datasource
|
40
|
-
@cursor = @cursor.parent
|
41
|
-
return with_new_cursor(type, value, &block)
|
42
|
-
elsif @cursor.type == :array
|
43
|
-
@cursor = new_cursor
|
44
|
-
@cursor.parent.push(@cursor)
|
45
|
-
elsif @cursor.type.nil?
|
46
|
-
# replace cursor
|
47
|
-
@cursor.type = type
|
48
|
-
@cursor.set_default_value(value)
|
49
|
-
else
|
50
|
-
fail "Invalid use of #{type}."
|
51
|
-
end
|
52
|
-
result = block.call
|
53
|
-
end
|
54
|
-
result
|
55
|
-
end
|
56
|
-
|
57
|
-
def hash(&block)
|
58
|
-
with_new_cursor(:hash, &block)
|
59
|
-
end
|
60
|
-
|
61
|
-
def key(name)
|
62
|
-
fail "Cannot use key outside hash." unless @cursor.type == :hash
|
63
|
-
@cursor = @cursor.tap do
|
64
|
-
@cursor = @cursor.value[name.to_s] = TemplatePart.new(@cursor)
|
65
|
-
yield
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def array(&block)
|
70
|
-
with_new_cursor(:array, &block)
|
71
|
-
end
|
72
|
-
|
73
|
-
def datasource(ds)
|
74
|
-
self.datasource_count += 1
|
75
|
-
@cursor = with_new_cursor(:datasource, ds) { @cursor }
|
76
|
-
end
|
77
|
-
|
78
|
-
def attributes(*attributes)
|
79
|
-
attributes.each { |name| attribute name }
|
80
|
-
end
|
81
|
-
|
82
|
-
def attribute(name)
|
83
|
-
fail "No datasource selected - use \"select_datasource Klass\" first." unless @cursor.type == :datasource
|
84
|
-
@cursor.select << name
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def initialize(*scopes)
|
89
|
-
@scopes = scopes
|
90
|
-
if @scopes.size != self.class.datasource_count
|
91
|
-
fail ArgumentError, "#{self.class.name} needs #{@scopes.size} scopes, you provided #{self.class.datasource_count}"
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def as_json
|
96
|
-
parse_template_part(self.class.template)
|
97
|
-
end
|
98
|
-
|
99
|
-
private
|
100
|
-
def parse_template_part(part)
|
101
|
-
return nil unless part
|
102
|
-
case part.type
|
103
|
-
when :hash
|
104
|
-
{}.tap do |result|
|
105
|
-
part.value.each_pair do |k, v|
|
106
|
-
result[k] = parse_template_part(v)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
when :array
|
110
|
-
part.value.map { |v| parse_template_part(v) }
|
111
|
-
when :datasource
|
112
|
-
part.value.new(@scopes.shift).select(*part.select).results
|
113
|
-
else
|
114
|
-
fail "Unknown type #{type}"
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
data/test/test_datasource.rb
DELETED
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'active_record_helper'
|
3
|
-
require 'pry'
|
4
|
-
|
5
|
-
class PostsDatasource < Datasource::Base
|
6
|
-
attributes :id, :title, :blog_id
|
7
|
-
|
8
|
-
computed_attribute :author, posts: [ :author_first_name, :author_last_name ] do
|
9
|
-
{ "name" => "#{author_first_name} #{author_last_name}" }
|
10
|
-
end
|
11
|
-
|
12
|
-
query_attribute :author_name, :posts do
|
13
|
-
"posts.author_first_name || ' ' || posts.author_last_name"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class BlogsDatasource < Datasource::Base
|
18
|
-
attributes :id
|
19
|
-
|
20
|
-
includes_many :posts, PostsDatasource, :blog_id
|
21
|
-
end
|
22
|
-
|
23
|
-
class DatasourceTest < ActiveSupport::TestCase
|
24
|
-
def test_basic
|
25
|
-
blog = Blog.create! title: "Blog 1"
|
26
|
-
blog.posts.create! title: "Post 1", author_first_name: "John", author_last_name: "Doe"
|
27
|
-
|
28
|
-
ds = PostsDatasource.new(Post.all)
|
29
|
-
|
30
|
-
assert_equal [{"id"=>1, "title"=>"Post 1", "author_name"=>"John Doe", "author"=>{"name"=>"John Doe"}}],
|
31
|
-
ds.select(:id, :title, :author, :author_name).results
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_assoc
|
35
|
-
blog = Blog.create! title: "Blog 1"
|
36
|
-
blog.posts.create! title: "Post 1", author_first_name: "John", author_last_name: "Doe"
|
37
|
-
|
38
|
-
ds = BlogsDatasource.new(Blog.all)
|
39
|
-
|
40
|
-
assert_equal [{"id"=>1, "posts"=>[{"id"=>1, "author_name"=>"John Doe"}]}],
|
41
|
-
ds.select(:id, posts: { scope: Post.all, select: [:id, :author_name] }).results
|
42
|
-
end
|
43
|
-
|
44
|
-
def teardown
|
45
|
-
clean_db
|
46
|
-
end
|
47
|
-
end
|