datasource 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 617cef0cb48d4953a9da29c7476ef583c5d7487e
4
+ data.tar.gz: bf76cef2be532f75c71729dbf3fa11fa4022ea37
5
+ SHA512:
6
+ metadata.gz: 3d4738bb6e26eae45f63df27fdfe52a7725d49d1e20569eace2af7d71314e20529f3a29fb913979b1f88f960f337418319b2f2dd88ef978ed57d8d72cda5688e
7
+ data.tar.gz: 3079481e54532538dd4aeb9df57ec2abe0f412f62369caf91ec3ebe16ab5106036321f3070d02be015972a81da9f6bb087de997f38610ba94c843e9cd4425e95
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) <year> <copyright holders>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1 @@
1
+ # Datasource
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
@@ -0,0 +1,126 @@
1
+ require 'set'
2
+
3
+ ActiveRecord::Calculations
4
+ module ActiveRecord
5
+ module Calculations
6
+ def pluck_hash(*column_names)
7
+ column_names.map! do |column_name|
8
+ if column_name.is_a?(Symbol) && attribute_alias?(column_name)
9
+ attribute_alias(column_name)
10
+ else
11
+ column_name.to_s
12
+ end
13
+ end
14
+
15
+ if has_include?(column_names.first)
16
+ construct_relation_for_association_calculations.pluck(*column_names)
17
+ else
18
+ relation = spawn
19
+ relation.select_values = column_names.map { |cn|
20
+ columns_hash.key?(cn) ? arel_table[cn] : cn
21
+ }
22
+ result = klass.connection.select_all(relation.arel, nil, bind_values)
23
+ columns = result.columns.map do |key|
24
+ klass.column_types.fetch(key) {
25
+ result.column_types.fetch(key) { result.identity_type }
26
+ }
27
+ end
28
+
29
+ result.rows.map do |values|
30
+ {}.tap do |hash|
31
+ values.zip(columns, result.columns).each do |v|
32
+ single_attr_hash = { v[2] => v[0] }
33
+ hash[v[2]] = v[1].type_cast klass.initialize_attributes(single_attr_hash).values.first
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ module Datasource
43
+ module Adapters
44
+ module ActiveRecord
45
+ ID_KEY = "id"
46
+
47
+ def to_query(scope)
48
+ ActiveRecord::Base.uncached do
49
+ scope.select(*get_select_values(scope)).to_sql
50
+ end
51
+ end
52
+
53
+ def get_rows(scope)
54
+ scope.pluck_hash(*get_select_values(scope))
55
+ end
56
+
57
+ def included_datasource_rows(att, datasource_data, rows)
58
+ ds_select = datasource_data[:select]
59
+ unless ds_select.include?(att[:foreign_key])
60
+ ds_select += [att[:foreign_key]]
61
+ end
62
+ ds_scope = datasource_data[:scope]
63
+ column = "#{ds_scope.klass.table_name}.#{att[:foreign_key]}"
64
+ ds_scope = ds_scope.where("#{column} IN (?)",
65
+ rows.map { |row| row[att[:id_key]] })
66
+ grouped_results = att[:klass].new(ds_scope)
67
+ .select(ds_select)
68
+ .results.group_by do |row|
69
+ row[att[:foreign_key]]
70
+ end
71
+ unless datasource_data[:select].include?(att[:foreign_key])
72
+ grouped_results.each_pair do |k, rows|
73
+ rows.each do |row|
74
+ row.delete(att[:foreign_key])
75
+ end
76
+ end
77
+ end
78
+ grouped_results
79
+ end
80
+
81
+ def get_select_values(scope)
82
+ select_values = Set.new
83
+ select_values.add("#{scope.klass.table_name}.#{self.class.adapter::ID_KEY}")
84
+
85
+ self.class._attributes.each do |att|
86
+ if attribute_exposed?(att[:name])
87
+ if att[:klass] == nil
88
+ select_values.add("#{scope.klass.table_name}.#{att[:name]}")
89
+ elsif att[:klass].ancestors.include?(Attributes::ComputedAttribute)
90
+ att[:klass]._depends.keys.map(&:to_s).each do |name|
91
+ next if name == scope.klass.table_name
92
+ ensure_table_join!(scope, name, att)
93
+ end
94
+ att[:klass]._depends.each_pair do |table, names|
95
+ Array(names).each do |name|
96
+ select_values.add("#{table}.#{name}")
97
+ end
98
+ # TODO: handle depends on virtual attribute
99
+ end
100
+ elsif att[:klass].ancestors.include?(Attributes::QueryAttribute)
101
+ select_values.add("(#{att[:klass].new.select_value}) as #{att[:name]}")
102
+ att[:klass]._depends.each do |name|
103
+ next if name == scope.klass.table_name
104
+ ensure_table_join!(scope, name, att)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ select_values.to_a
110
+ end
111
+
112
+ def ensure_table_join!(scope, name, att)
113
+ join_value = scope.joins_values.find do |value|
114
+ if value.is_a?(Symbol)
115
+ value.to_s == att[:name]
116
+ elsif value.is_a?(String)
117
+ if value =~ /join (\w+)/i
118
+ $1 == att[:name]
119
+ end
120
+ end
121
+ end
122
+ raise "Given scope does not join on #{name}, but it is required by #{att[:name]}" unless join_value
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,41 @@
1
+ module Datasource
2
+ module Attributes
3
+ class ComputedAttribute
4
+ class << self
5
+ attr_accessor :_depends
6
+
7
+ def inherited(base)
8
+ base._depends = (_depends || {}).dup # TODO: deep dup?
9
+ end
10
+
11
+ def depends(*args)
12
+ args.each do |dep|
13
+ _depends.deep_merge!(dep)
14
+ dep.values.each do |names|
15
+ Array(names).each do |name|
16
+ define_method(name) do
17
+ @depend_values[name.to_s]
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(depend_values)
26
+ @depend_values = depend_values
27
+ end
28
+ end
29
+ end
30
+
31
+ class Datasource::Base
32
+ def self.computed_attribute(name, deps, &block)
33
+ klass = Class.new(Attributes::ComputedAttribute) do
34
+ depends deps
35
+
36
+ define_method(:value, &block)
37
+ end
38
+ attribute name, klass
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,28 @@
1
+ module Datasource
2
+ module Attributes
3
+ class QueryAttribute
4
+ class << self
5
+ attr_accessor :_depends
6
+
7
+ def inherited(base)
8
+ base._depends = (_depends || []).dup
9
+ end
10
+
11
+ def depends(*args)
12
+ self._depends += args.map(&:to_s)
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ class Datasource::Base
19
+ def self.query_attribute(name, deps, &block)
20
+ klass = Class.new(Attributes::QueryAttribute) do
21
+ depends deps
22
+
23
+ define_method(:select_value, &block)
24
+ end
25
+ attribute name, klass
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,99 @@
1
+ module Datasource
2
+ class Base
3
+ class << self
4
+ attr_accessor :_attributes, :_virtual_attributes, :_associations
5
+ attr_accessor :adapter
6
+
7
+ def inherited(base)
8
+ base._attributes = (_attributes || []).dup
9
+ @adapter ||= Datasource::Adapters::ActiveRecord
10
+ self.send :include, @adapter
11
+ end
12
+
13
+ def attributes(*attrs)
14
+ attrs.each { |name| attribute(name) }
15
+ end
16
+
17
+ def attribute(name, klass = nil)
18
+ @_attributes.push name: name.to_s, klass: klass
19
+ end
20
+
21
+ def includes_many(name, klass, foreign_key)
22
+ @_attributes.push name: name.to_s, klass: klass, foreign_key: foreign_key.to_s, id_key: self::ID_KEY
23
+ end
24
+ end
25
+
26
+ def initialize(scope)
27
+ @scope = scope
28
+ @expose_attributes = []
29
+ @datasource_data = {}
30
+ end
31
+
32
+ def select(*names)
33
+ names = names.flat_map do |name|
34
+ if name.kind_of?(Hash)
35
+ # datasource data
36
+ name.each_pair do |k, v|
37
+ @datasource_data[k.to_s] = v
38
+ end
39
+ name.keys
40
+ else
41
+ name
42
+ end
43
+ end
44
+ @expose_attributes = (@expose_attributes + names.map(&:to_s)).uniq
45
+ self
46
+ end
47
+
48
+ def attribute_exposed?(name)
49
+ @expose_attributes.include?(name)
50
+ end
51
+
52
+ def to_query
53
+ to_query(@scope)
54
+ end
55
+
56
+ def results
57
+ rows = get_rows(@scope)
58
+
59
+ attribute_map = self.class._attributes.inject({}) do |hash, att|
60
+ hash[att[:name]] = att
61
+ hash
62
+ end
63
+
64
+ computed_expose_attributes = []
65
+ datasources = {}
66
+
67
+ @expose_attributes.each do |name|
68
+ att = attribute_map[name]
69
+ klass = att[:klass]
70
+ next unless klass
71
+
72
+ if klass.ancestors.include?(Attributes::ComputedAttribute)
73
+ computed_expose_attributes.push(att)
74
+ elsif klass.ancestors.include?(Base)
75
+ datasources[att] =
76
+ included_datasource_rows(att, @datasource_data[att[:name]], rows)
77
+ end
78
+ end
79
+
80
+ # TODO: field names...
81
+ rows.each do |row|
82
+ computed_expose_attributes.each do |att|
83
+ klass = att[:klass]
84
+ if klass
85
+ row[att[:name]] = klass.new(row).value
86
+ end
87
+ end
88
+ datasources.each_pair do |att, rows|
89
+ row[att[:name]] = Array(rows[row[att[:id_key]]])
90
+ end
91
+ row.delete_if do |key, value|
92
+ !attribute_exposed?(key)
93
+ end
94
+ end
95
+
96
+ rows
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,119 @@
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/lib/datasource.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'datasource/base'
2
+
3
+ require 'datasource/attributes/computed_attribute'
4
+ require 'datasource/attributes/query_attribute'
5
+
6
+ require 'datasource/adapters/active_record'
7
+
8
+ require 'datasource/serializer/composite'
@@ -0,0 +1,10 @@
1
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
2
+ ActiveRecord::Migration.verbose = false
3
+ load "schema.rb"
4
+
5
+ def clean_db
6
+ ActiveRecord::Base.connection.execute("DELETE FROM comments")
7
+ ActiveRecord::Base.connection.execute("DELETE FROM posts")
8
+ ActiveRecord::Base.connection.execute("DELETE FROM blogs")
9
+ ActiveRecord::Base.connection.execute("DELETE FROM sqlite_sequence")
10
+ end
data/test/schema.rb ADDED
@@ -0,0 +1,33 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+ create_table :blogs, :force => true do |t|
3
+ t.string :title
4
+ end
5
+
6
+ create_table :posts, :force => true do |t|
7
+ t.integer :blog_id
8
+ t.string :title
9
+ t.string :author_first_name
10
+ t.string :author_last_name
11
+ end
12
+
13
+ create_table :comments, :force => true do |t|
14
+ t.integer :post_id
15
+ t.text :comment
16
+ end
17
+ end
18
+
19
+ class Blog < ActiveRecord::Base
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
@@ -0,0 +1,47 @@
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
@@ -0,0 +1,12 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require 'minitest/autorun'
5
+ require 'active_support/all'
6
+ require 'active_record'
7
+ require 'datasource'
8
+
9
+ # Load support files
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
+
12
+ # ActiveRecord::Base.logger = Logger.new STDOUT
@@ -0,0 +1,48 @@
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
+ end
8
+
9
+ class BlogsDatasource < Datasource::Base
10
+ attributes :id, :title
11
+ includes_many :posts, PostsDatasource, :blog_id
12
+ end
13
+
14
+ class BlogsAndPostsSerializer < Datasource::Serializer::Composite
15
+ hash do
16
+ key :blogs do
17
+ datasource BlogsDatasource
18
+ attributes :id, :title,
19
+ posts: { select: [ :id ], scope: Post.all }
20
+ end
21
+
22
+ key :posts do
23
+ datasource PostsDatasource
24
+ attributes :id, :title
25
+ end
26
+ end
27
+ end
28
+
29
+ class SerializerCompositeTest < ActiveSupport::TestCase
30
+ # SELECT blogs.id, blogs.title FROM "blogs"
31
+ # SELECT posts.id, posts.blog_id FROM "posts" WHERE (posts.blog_id IN (1,2))
32
+ # SELECT posts.id, posts.title FROM "posts"
33
+ def test_blogs_and_posts_serializer
34
+ blog = Blog.create! title: "Blog 1"
35
+ blog.posts.create! title: "Post 1", author_first_name: "John", author_last_name: "Doe"
36
+ blog.posts.create! title: "Post 2", author_first_name: "Maria", author_last_name: "Doe"
37
+ blog = Blog.create! title: "Blog 2"
38
+
39
+ serializer = BlogsAndPostsSerializer.new(Blog.all, Post.all)
40
+
41
+ assert_equal({"blogs"=>[{"id"=>1, "title"=>"Blog 1", "posts"=>[{"id"=>1}, {"id"=>2}]}, {"id"=>2, "title"=>"Blog 2", "posts"=>[]}], "posts"=>[{"id"=>1, "title"=>"Post 1"}, {"id"=>2, "title"=>"Post 2"}]},
42
+ serializer.as_json)
43
+ end
44
+
45
+ def teardown
46
+ clean_db
47
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: datasource
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jan Berdajs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ description:
42
+ email:
43
+ - mrbrdo@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - lib/datasource.rb
52
+ - lib/datasource/adapters/active_record.rb
53
+ - lib/datasource/attributes/computed_attribute.rb
54
+ - lib/datasource/attributes/query_attribute.rb
55
+ - lib/datasource/base.rb
56
+ - lib/datasource/serializer/composite.rb
57
+ - test/active_record_helper.rb
58
+ - test/schema.rb
59
+ - test/test_datasource.rb
60
+ - test/test_helper.rb
61
+ - test/test_serializer_composite.rb
62
+ homepage: https://github.com/mrbrdo/datasource
63
+ licenses:
64
+ - MIT
65
+ metadata: {}
66
+ post_install_message:
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 2.2.2
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Ruby library for creating data source objects from database data
86
+ test_files:
87
+ - test/active_record_helper.rb
88
+ - test/schema.rb
89
+ - test/test_datasource.rb
90
+ - test/test_helper.rb
91
+ - test/test_serializer_composite.rb