surus 0.3.2 → 0.4.0

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.
@@ -0,0 +1,17 @@
1
+ module Surus
2
+ module JSON
3
+ module Model
4
+ def find_json(id, options={})
5
+ sql = RowQuery.new(where(id: id), options).to_sql
6
+ connection.select_value sql
7
+ end
8
+
9
+ def all_json(options={})
10
+ sql = ArrayAggQuery.new(scoped, options).to_sql
11
+ connection.select_value sql
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ ActiveRecord::Base.extend Surus::JSON::Model
@@ -0,0 +1,78 @@
1
+ module Surus
2
+ module JSON
3
+ class Query
4
+ attr_reader :original_scope
5
+ attr_reader :options
6
+
7
+ def initialize(original_scope, options={})
8
+ @original_scope = original_scope
9
+ @options = options
10
+ end
11
+
12
+ private
13
+ def klass
14
+ original_scope.klass
15
+ end
16
+
17
+ def subquery_sql
18
+ select(columns.map(&:to_s).join(', ')).to_sql
19
+ end
20
+
21
+ def columns
22
+ selected_columns + association_columns
23
+ end
24
+
25
+ def table_columns
26
+ klass.columns
27
+ end
28
+
29
+ def selected_columns
30
+ if options.key? :columns
31
+ options[:columns]
32
+ else
33
+ table_columns.map(&:name)
34
+ end
35
+ end
36
+
37
+ def association_columns
38
+ included_associations_name_and_options.map do |association_name, association_options|
39
+ association = klass.reflect_on_association association_name
40
+ subquery = case association.source_macro
41
+ when :belongs_to
42
+ association_scope = BelongsToScopeBuilder.new(original_scope, association).scope
43
+ RowQuery.new(association_scope, association_options).to_sql
44
+ when :has_many
45
+ association_scope = HasManyScopeBuilder.new(original_scope, association).scope
46
+ ArrayAggQuery.new(association_scope, association_options).to_sql
47
+ when :has_and_belongs_to_many
48
+ association_scope = HasAndBelongsToManyScopeBuilder.new(original_scope, association).scope
49
+ ArrayAggQuery.new(association_scope, association_options).to_sql
50
+ end
51
+ "(#{subquery}) #{association_name}"
52
+ end
53
+ end
54
+
55
+ def included_associations_name_and_options
56
+ _include = options[:include]
57
+ if _include.nil?
58
+ {}
59
+ elsif _include.kind_of?(::Hash)
60
+ _include
61
+ elsif _include.kind_of?(::Array)
62
+ _include.each_with_object({}) do |e, hash|
63
+ if e.kind_of?(Hash)
64
+ hash.merge!(e)
65
+ else
66
+ hash[e] = {}
67
+ end
68
+ end
69
+ else
70
+ {_include => {}}
71
+ end
72
+ end
73
+
74
+ delegate :connection, :quoted_table_name, to: :klass
75
+ delegate :select, to: :original_scope
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,9 @@
1
+ module Surus
2
+ module JSON
3
+ class RowQuery < Query
4
+ def to_sql
5
+ "select row_to_json(t) from (#{subquery_sql}) t"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Surus
2
- VERSION = "0.3.2"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -7,17 +7,17 @@ describe Surus::Array::DecimalSerializer do
7
7
  [[BigDecimal("0.0")], "single element"],
8
8
  [[BigDecimal("1.0"), BigDecimal("2.0")], "multiple elements"],
9
9
  [[BigDecimal("-1.0"), BigDecimal("-2.0")], "negative element"],
10
- [[BigDecimal("1232493289348929843.323422349274382923")], "high magnitude element"],
10
+ [[BigDecimal("1232493289348929843.323422349274382923")], "high magnitude element"],
11
11
  [[BigDecimal("1.0"), BigDecimal("2.0"), BigDecimal("2.0")], "duplicated elements"],
12
12
  [[BigDecimal("1.0"), nil], "an element is nil"],
13
13
  [(10_000.times.map { |n| n * BigDecimal("1.13312") }).to_a, "huge array"]
14
14
  ]
15
-
15
+
16
16
  round_trip_examples.each do |value, description|
17
17
  it "round trips when #{description}" do
18
18
  r = DecimalArrayRecord.create! :decimals => value
19
19
  r.reload
20
- r.decimals.should == value
21
- end
20
+ expect(r.decimals).to eq(value)
21
+ end
22
22
  end
23
23
  end
@@ -7,17 +7,17 @@ describe Surus::Array::FloatSerializer do
7
7
  [[0.0], "single element"],
8
8
  [[1.0, 2.0], "multiple elements"],
9
9
  [[-1.0, -2.0], "negative element"],
10
- [[4.4325349e+45, 1.2324323e+77, 1.1242342e-57, 3e99], "high magnitude elements"],
10
+ [[4.4325349e+45, 1.2324323e+77, 1.1242342e-57, 3e99], "high magnitude elements"],
11
11
  [[1.0, 2.0, 2.0, 2.0], "duplicated elements"],
12
12
  [[1.0, nil], "an element is nil"],
13
13
  [(10_000.times.map { |n| n * 1.0 }).to_a, "huge array"]
14
14
  ]
15
-
15
+
16
16
  round_trip_examples.each do |value, description|
17
17
  it "round trips when #{description}" do
18
18
  r = FloatArrayRecord.create! :floats => value
19
19
  r.reload
20
- r.floats.should == value
21
- end
20
+ expect(r.floats).to eq(value)
21
+ end
22
22
  end
23
23
  end
@@ -11,12 +11,12 @@ describe Surus::Array::IntegerSerializer do
11
11
  [[1, nil], "an element is nil"],
12
12
  [(1..10_000).to_a, "huge array"]
13
13
  ]
14
-
14
+
15
15
  round_trip_examples.each do |value, description|
16
16
  it "round trips when #{description}" do
17
17
  r = IntegerArrayRecord.create! :integers => value
18
18
  r.reload
19
- r.integers.should == value
20
- end
19
+ expect(r.integers).to eq(value)
20
+ end
21
21
  end
22
22
  end
@@ -18,12 +18,12 @@ describe Surus::Array::TextSerializer do
18
18
  [[%q~foo \\ / " ; ' ( ) {}bar \\'~, "bar"], "an element many special characters"],
19
19
  [("aaa".."zzz").to_a, "huge array"]
20
20
  ]
21
-
21
+
22
22
  round_trip_examples.each do |value, description|
23
23
  it "round trips when #{description}" do
24
24
  r = TextArrayRecord.create! :texts => value
25
25
  r.reload
26
- r.texts.should == value
27
- end
26
+ expect(r.texts).to eq(value)
27
+ end
28
28
  end
29
29
  end
@@ -36,9 +36,59 @@ CREATE TABLE float_array_records(
36
36
 
37
37
 
38
38
 
39
- DROP TABLE IF EXISTS float_array_records;
39
+ DROP TABLE IF EXISTS decimal_array_records;
40
40
 
41
41
  CREATE TABLE decimal_array_records(
42
42
  id serial PRIMARY KEY,
43
43
  decimals decimal[]
44
+ );
45
+
46
+
47
+
48
+ DROP TABLE IF EXISTS users CASCADE;
49
+
50
+ CREATE TABLE users(
51
+ id serial PRIMARY KEY,
52
+ name varchar NOT NULL,
53
+ email varchar NOT NULL
54
+ );
55
+
56
+
57
+
58
+ DROP TABLE IF EXISTS forums CASCADE;
59
+
60
+ CREATE TABLE forums(
61
+ id serial PRIMARY KEY,
62
+ name varchar NOT NULL
63
+ );
64
+
65
+
66
+
67
+ DROP TABLE IF EXISTS posts CASCADE;
68
+
69
+ CREATE TABLE posts(
70
+ id serial PRIMARY KEY,
71
+ forum_id integer NOT NULL REFERENCES forums,
72
+ author_id integer NOT NULL REFERENCES users,
73
+ subject varchar NOT NULL,
74
+ body varchar NOT NULL
75
+ );
76
+
77
+
78
+
79
+ DROP TABLE IF EXISTS tags CASCADE;
80
+
81
+ CREATE TABLE tags(
82
+ id serial PRIMARY KEY,
83
+ name varchar NOT NULL UNIQUE
84
+ );
85
+
86
+
87
+
88
+ DROP TABLE IF EXISTS posts_tags CASCADE;
89
+
90
+ CREATE TABLE posts_tags(
91
+ post_id integer NOT NULL REFERENCES posts,
92
+ tag_id integer NOT NULL REFERENCES tags,
93
+ PRIMARY KEY (post_id, tag_id)
44
94
  );
@@ -0,0 +1,21 @@
1
+ FactoryGirl.define do
2
+ factory :forum do
3
+ name { Faker::Lorem.sentence }
4
+ end
5
+
6
+ factory :post do
7
+ forum
8
+ author
9
+ subject { Faker::Lorem.sentence }
10
+ body { Faker::Lorem.paragraph }
11
+ end
12
+
13
+ factory :tag do
14
+ sequence(:name) { |n| "#{Faker::Lorem.word} - #{n}" }
15
+ end
16
+
17
+ factory :user, aliases: [:author] do
18
+ name { Faker::Internet.user_name }
19
+ email { Faker::Internet.email }
20
+ end
21
+ end
@@ -8,7 +8,7 @@ describe Surus::Hstore::Serializer do
8
8
  [{"foo" => "bar", "baz" => "quz"}, "multiple key/value pairs"],
9
9
  [{"foo" => nil}, "value is nil"],
10
10
  ]
11
-
11
+
12
12
  [
13
13
  ['"', 'double quote (")'],
14
14
  ["'", "single quote (')"],
@@ -22,13 +22,13 @@ describe Surus::Hstore::Serializer do
22
22
  round_trip_examples << [{"foo#{value}foo" => "bar"}, "key with #{description} in middle"]
23
23
  round_trip_examples << [{"foo#{value}" => "bar"}, "key with #{description} at end"]
24
24
  round_trip_examples << [{value => "bar"}, "key is #{description}"]
25
-
25
+
26
26
  round_trip_examples << [{"foo" => "#{value}bar"}, "value with #{description} at beginning"]
27
27
  round_trip_examples << [{"foo" => "bar#{value}bar"}, "value with #{description} in middle"]
28
28
  round_trip_examples << [{"foo" => "bar#{value}"}, "value with #{description} at end"]
29
29
  round_trip_examples << [{"foo" => value}, "value is #{description}"]
30
30
  end
31
-
31
+
32
32
  [
33
33
  [:foo, "symbol"],
34
34
  [0, "integer 0"],
@@ -55,12 +55,12 @@ describe Surus::Hstore::Serializer do
55
55
  round_trip_examples << [{value => "bar"}, "key is #{description}"]
56
56
  round_trip_examples << [{value => value}, "key and value are each #{description}"]
57
57
  end
58
-
58
+
59
59
  round_trip_examples.each do |value, description|
60
60
  it "round trips when #{description}" do
61
61
  r = HstoreRecord.create! :properties => value
62
62
  r.reload
63
- r.properties.should == value
64
- end
63
+ expect(r.properties).to eq(value)
64
+ end
65
65
  end
66
66
  end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+ require 'oj'
3
+ ActiveRecord::Base.include_root_in_json = false
4
+
5
+ describe 'json' do
6
+ describe 'find_json' do
7
+ context 'with only id parameter' do
8
+ it 'is entire row as json' do
9
+ user = FactoryGirl.create :user
10
+ to_json = Oj.load user.to_json
11
+ find_json = Oj.load User.find_json(user.id)
12
+ expect(find_json).to eq(to_json)
13
+ end
14
+ end
15
+
16
+ context 'with columns parameter' do
17
+ it 'is selected row columns as json' do
18
+ user = FactoryGirl.create :user
19
+ to_json = Oj.load user.to_json only: [:id, :name]
20
+ find_json = Oj.load User.find_json(user.id, columns: [:id, :name])
21
+ expect(find_json).to eq(to_json)
22
+ end
23
+ end
24
+
25
+ context 'with includes option' do
26
+ it 'includes entire belongs_to object' do
27
+ post = FactoryGirl.create :post
28
+ to_json = Oj.load post.to_json(include: :author)
29
+ find_json = Oj.load Post.find_json(post.id, include: :author)
30
+ expect(find_json).to eq(to_json)
31
+ end
32
+
33
+ it 'filters by belongs_to conditions' do
34
+ post = FactoryGirl.create :post
35
+ find_json = Oj.load Post.find_json(post.id, include: :forum_with_impossible_conditions)
36
+ expect(find_json.fetch('forum_with_impossible_conditions')).to be_nil
37
+ end
38
+
39
+ it 'includes multiple entire belongs_to objects' do
40
+ post = FactoryGirl.create :post
41
+ to_json = Oj.load post.to_json(include: [:author, :forum])
42
+ find_json = Oj.load Post.find_json(post.id, include: [:author, :forum])
43
+ expect(find_json).to eq(to_json)
44
+ end
45
+
46
+ it 'includes only selected columns of belongs_to object' do
47
+ post = FactoryGirl.create :post
48
+ to_json = Oj.load post.to_json(include: {author: {only: [:id, :name]}})
49
+ find_json = Oj.load Post.find_json(post.id, include: {author: {columns: [:id, :name]}})
50
+ expect(find_json).to eq(to_json)
51
+ end
52
+
53
+ it 'includes entire has_many association' do
54
+ user = FactoryGirl.create :user
55
+ posts = FactoryGirl.create_list :post, 2, author: user
56
+ user.reload
57
+ to_json = Oj.load user.to_json(include: :posts)
58
+ find_json = Oj.load User.find_json(user.id, include: :posts)
59
+ expect(find_json).to eq(to_json)
60
+ end
61
+
62
+ it 'preserves has_many order' do
63
+ user = FactoryGirl.create :user
64
+ posts = FactoryGirl.create_list :post, 2, author: user
65
+ user.reload
66
+ to_json = Oj.load user.to_json(include: :posts_with_order)
67
+ find_json = Oj.load User.find_json(user.id, include: :posts_with_order)
68
+ expect(find_json).to eq(to_json)
69
+ end
70
+
71
+ it 'filters by has_many conditions' do
72
+ user = FactoryGirl.create :user
73
+ FactoryGirl.create :post, author: user, subject: 'foo'
74
+ FactoryGirl.create :post, author: user
75
+ user.reload
76
+ to_json = Oj.load user.to_json(include: :posts_with_conditions)
77
+ find_json = Oj.load User.find_json(user.id, include: :posts_with_conditions)
78
+ expect(find_json).to eq(to_json)
79
+ end
80
+
81
+ it 'includes only select columns of has_many association' do
82
+ user = FactoryGirl.create :user
83
+ posts = FactoryGirl.create_list :post, 2, author: user
84
+ user.reload
85
+ to_json = Oj.load user.to_json(include: {posts: {only: [:id, :subject]}})
86
+ find_json = Oj.load User.find_json(user.id, include: {posts: {columns: [:id, :subject]}})
87
+ expect(find_json).to eq(to_json)
88
+ end
89
+
90
+ it 'includes empty array for empty has_many association' do
91
+ user = FactoryGirl.create :user
92
+ to_json = Oj.load user.to_json(include: :posts)
93
+ find_json = Oj.load User.find_json(user.id, include: :posts)
94
+ expect(find_json).to eq(to_json)
95
+ end
96
+
97
+ it 'includes entire has_and_belongs_to_many association' do
98
+ post = FactoryGirl.create :post
99
+ tag = FactoryGirl.create :tag
100
+ post.tags << tag
101
+ to_json = Oj.load post.to_json(include: :tags)
102
+ find_json = Oj.load Post.find_json(post.id, include: :tags)
103
+ expect(find_json).to eq(to_json)
104
+ end
105
+
106
+ it 'excludes other has_and_belongs_to_many association records' do
107
+ post = FactoryGirl.create :post
108
+ tag = FactoryGirl.create :tag
109
+ post.tags << tag
110
+ other_post = FactoryGirl.create :post
111
+ other_tag = FactoryGirl.create :tag
112
+ other_post.tags << other_tag
113
+ to_json = Oj.load post.to_json(include: :tags)
114
+ find_json = Oj.load Post.find_json(post.id, include: :tags)
115
+ expect(find_json).to eq(to_json)
116
+ end
117
+
118
+ it 'includes empty array for empty has_and_belongs_to_many' do
119
+ post = FactoryGirl.create :post
120
+ to_json = Oj.load post.to_json(include: :tags)
121
+ find_json = Oj.load Post.find_json(post.id, include: :tags)
122
+ expect(find_json).to eq(to_json)
123
+ end
124
+
125
+ it 'includes nested associations' do
126
+ user = FactoryGirl.create :user
127
+ post = FactoryGirl.create :post, author: user
128
+ user.reload
129
+ to_json = Oj.load user.to_json(include: {posts: {include: :forum}})
130
+ find_json = Oj.load User.find_json(user.id, include: {posts: {include: :forum}})
131
+ expect(find_json).to eq(to_json)
132
+ end
133
+
134
+ it 'includes nested associations with columns' do
135
+ user = FactoryGirl.create :user
136
+ post = FactoryGirl.create :post, author: user
137
+ user.reload
138
+ to_json = Oj.load user.to_json(include: {posts: {only: [:id], include: :forum}})
139
+ find_json = Oj.load User.find_json(user.id, include: {posts: {columns: [:id], include: :forum}})
140
+ expect(find_json).to eq(to_json)
141
+ end
142
+ end
143
+ end
144
+
145
+ describe 'all_json' do
146
+ it 'is all rows as array' do
147
+ users = FactoryGirl.create_list :user, 3
148
+ to_json = Oj.load users.to_json
149
+ all_json = Oj.load User.all_json
150
+ expect(all_json).to eq(to_json)
151
+ end
152
+ end
153
+ end