praxis 2.0.pre.32 → 2.0.pre.33

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
  SHA256:
3
- metadata.gz: 481a1aedfa10aa914a0b0bba13a1b39ca3de5d9ecfd3840520674919fc82563e
4
- data.tar.gz: 7796b1d757c700cb9e56a9d16f1471ef5930ee0fe19b3b596d948a9a8b7f8b3c
3
+ metadata.gz: 9bdbabd51b8b6bb4a776ab4c728d36b3cf86f3b7a3423250e6658fa01653ba6a
4
+ data.tar.gz: e9edc1eacc8ebf67d8f8b5fdda19cb010b587f3e2d0319cbcdeec62908752d8b
5
5
  SHA512:
6
- metadata.gz: 91b4c7f698267e0a533ccc1ba73e500ed2c9570550b83dacee4cc1ff80bd615e55b66bfd9707ba25c05b38599a98da2661505aec15e96def2d0ae1b3f94f2b6e
7
- data.tar.gz: c21b5bed2302eba252b58fa78da841c82227994796344eacc0d2b9c20608d166e3f9e5fd3e5bb73c2fa191b909891c5d901627832de94a4a594ee085be3f26b6
6
+ metadata.gz: 174e74b7e4e55378edde0f623b4f2c49aa15409bfc64ae9f36aa8a0167794ed1c3b747331da5bdc66e672bcb58228cbaad89971689449d082d5080c009b7ad6c
7
+ data.tar.gz: c7574f73697e2067e6f338d1c19bdc6447ca24620ed49a1ff94882392c8df987cbc598624323ce3228330c27ce0dda7b4f0228b68a4ad18dbf6c37c995869e84
data/.travis.yml CHANGED
@@ -9,4 +9,7 @@ script:
9
9
  branches:
10
10
  only:
11
11
  - master
12
- - /.*/
12
+ - /.*/
13
+ gemfile:
14
+ - gemfiles/active_6.gemfile
15
+ - gemfiles/active_7.gemfile
data/Appraisals ADDED
@@ -0,0 +1,11 @@
1
+ appraise "active-6" do
2
+ # Just for the query selector/filtering/ordering extensions etc...
3
+ gem 'activerecord', '> 4', '< 7'
4
+ gem 'sequel', '~> 5'
5
+ end
6
+
7
+ appraise "active-7" do
8
+ # Just for the query selector/filtering/ordering extensions etc...
9
+ gem 'activerecord', '>=7'
10
+ gem 'sequel', '~> 5'
11
+ end
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Praxis Changelog
2
2
 
3
3
  ## next
4
+ - Better support for ordefing in newer versions of MySQL:
5
+ * Some versions will complain on an invalid query if you use an ORDER BY component that does not have the corresponding SELECT field
6
+ - Use the newest Attributor gem, which revamps and hardnens the struct/collection definion DSL, and provides much better error messages and safety. It also
7
+ bring the ability to define collections with `<T>[]` which is equivalent to the current `Attributor::Collection.of(<T>). For example, you can now do things
8
+ like `String[]`, `MyMediaType[]`, etc..
9
+ - Tightened a few type comparisons throughout the framework, and built full specs for struct/collection definitions in Blueprints.
10
+ - Built config for the Appraisals gem, so we can continuously test some of our extensions against different versions of ActiveRecord as it evolves (6x and7x for now)
4
11
 
5
12
  ## 2.0.pre.32
6
13
  - Spruced up the scaffolding generation, to be more configurable using a `.praxis_scaffold` file at the root, where one can specify things like
data/Gemfile CHANGED
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
- gem 'rubocop'
6
+ gem "rubocop"
7
7
 
8
8
  group :test do
9
- gem 'builder'
9
+ gem "builder"
10
10
 
11
- gem 'link_header'
12
- gem 'oj'
13
- gem 'parslet'
11
+ gem "link_header"
12
+ gem "oj"
13
+ gem "parslet"
14
14
  end
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop"
6
+ gem "activerecord", "> 4", "< 7"
7
+ gem "sequel", "~> 5"
8
+
9
+ group :test do
10
+ gem "builder"
11
+ gem "link_header"
12
+ gem "oj"
13
+ gem "parslet"
14
+ end
15
+
16
+ gemspec path: "../"
@@ -0,0 +1,199 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ praxis (2.0.pre.32)
5
+ activesupport (>= 3)
6
+ attributor (>= 6.5)
7
+ mime (~> 0)
8
+ mustermann (>= 1.1)
9
+ rack (>= 1)
10
+ terminal-table (~> 1.4)
11
+ thor
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ activemodel (6.1.7.3)
17
+ activesupport (= 6.1.7.3)
18
+ activerecord (6.1.7.3)
19
+ activemodel (= 6.1.7.3)
20
+ activesupport (= 6.1.7.3)
21
+ activesupport (6.1.7.3)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 1.6, < 2)
24
+ minitest (>= 5.1)
25
+ tzinfo (~> 2.0)
26
+ zeitwerk (~> 2.3)
27
+ appraisal (2.4.1)
28
+ bundler
29
+ rake
30
+ thor (>= 0.14.0)
31
+ ast (2.4.2)
32
+ attributor (6.5)
33
+ activesupport (>= 3)
34
+ hashie (~> 3)
35
+ randexp (~> 0)
36
+ binding_of_caller (1.0.0)
37
+ debug_inspector (>= 0.0.1)
38
+ builder (3.2.4)
39
+ byebug (11.1.3)
40
+ coderay (1.1.3)
41
+ concurrent-ruby (1.2.2)
42
+ coveralls (0.8.23)
43
+ json (>= 1.8, < 3)
44
+ simplecov (~> 0.16.1)
45
+ term-ansicolor (~> 1.3)
46
+ thor (>= 0.19.4, < 2.0)
47
+ tins (~> 1.6)
48
+ debug_inspector (1.1.0)
49
+ diff-lcs (1.5.0)
50
+ docile (1.4.0)
51
+ ffi (1.15.5)
52
+ formatador (1.1.0)
53
+ fuubar (2.5.1)
54
+ rspec-core (~> 3.0)
55
+ ruby-progressbar (~> 1.4)
56
+ guard (2.18.0)
57
+ formatador (>= 0.2.4)
58
+ listen (>= 2.7, < 4.0)
59
+ lumberjack (>= 1.0.12, < 2.0)
60
+ nenv (~> 0.1)
61
+ notiffany (~> 0.0)
62
+ pry (>= 0.13.0)
63
+ shellany (~> 0.0)
64
+ thor (>= 0.18.1)
65
+ guard-bundler (2.2.1)
66
+ bundler (>= 1.3.0, < 3)
67
+ guard (~> 2.2)
68
+ guard-compat (~> 1.1)
69
+ guard-compat (1.2.1)
70
+ guard-rspec (4.7.3)
71
+ guard (~> 2.1)
72
+ guard-compat (~> 1.1)
73
+ rspec (>= 2.99.0, < 4.0)
74
+ hashie (3.6.0)
75
+ i18n (1.13.0)
76
+ concurrent-ruby (~> 1.0)
77
+ json (2.6.3)
78
+ link_header (0.0.8)
79
+ listen (3.8.0)
80
+ rb-fsevent (~> 0.10, >= 0.10.3)
81
+ rb-inotify (~> 0.9, >= 0.9.10)
82
+ lumberjack (1.2.8)
83
+ method_source (1.0.0)
84
+ mime (0.4.4)
85
+ minitest (5.18.0)
86
+ mustermann (3.0.0)
87
+ ruby2_keywords (~> 0.0.1)
88
+ nenv (0.3.0)
89
+ notiffany (0.1.3)
90
+ nenv (~> 0.1)
91
+ shellany (~> 0.0)
92
+ oj (3.14.2)
93
+ parallel (1.23.0)
94
+ parser (3.2.2.1)
95
+ ast (~> 2.4.1)
96
+ parslet (2.0.0)
97
+ pry (0.14.2)
98
+ coderay (~> 1.1)
99
+ method_source (~> 1.0)
100
+ pry-byebug (3.10.1)
101
+ byebug (~> 11.0)
102
+ pry (>= 0.13, < 0.15)
103
+ pry-stack_explorer (0.6.1)
104
+ binding_of_caller (~> 1.0)
105
+ pry (~> 0.13)
106
+ rack (2.2.7)
107
+ rack-test (0.8.3)
108
+ rack (>= 1.0, < 3)
109
+ rainbow (3.1.1)
110
+ rake (13.0.6)
111
+ randexp (0.1.7)
112
+ rb-fsevent (0.11.2)
113
+ rb-inotify (0.10.1)
114
+ ffi (~> 1.0)
115
+ regexp_parser (2.8.0)
116
+ rexml (3.2.5)
117
+ rspec (3.12.0)
118
+ rspec-core (~> 3.12.0)
119
+ rspec-expectations (~> 3.12.0)
120
+ rspec-mocks (~> 3.12.0)
121
+ rspec-collection_matchers (1.2.0)
122
+ rspec-expectations (>= 2.99.0.beta1)
123
+ rspec-core (3.12.1)
124
+ rspec-support (~> 3.12.0)
125
+ rspec-expectations (3.12.2)
126
+ diff-lcs (>= 1.2.0, < 2.0)
127
+ rspec-support (~> 3.12.0)
128
+ rspec-its (1.3.0)
129
+ rspec-core (>= 3.0.0)
130
+ rspec-expectations (>= 3.0.0)
131
+ rspec-mocks (3.12.3)
132
+ diff-lcs (>= 1.2.0, < 2.0)
133
+ rspec-support (~> 3.12.0)
134
+ rspec-support (3.12.0)
135
+ rubocop (1.48.1)
136
+ json (~> 2.3)
137
+ parallel (~> 1.10)
138
+ parser (>= 3.2.0.0)
139
+ rainbow (>= 2.2.2, < 4.0)
140
+ regexp_parser (>= 1.8, < 3.0)
141
+ rexml (>= 3.2.5, < 4.0)
142
+ rubocop-ast (>= 1.26.0, < 2.0)
143
+ ruby-progressbar (~> 1.7)
144
+ unicode-display_width (>= 2.4.0, < 3.0)
145
+ rubocop-ast (1.28.0)
146
+ parser (>= 3.2.1.0)
147
+ ruby-progressbar (1.13.0)
148
+ ruby2_keywords (0.0.5)
149
+ sequel (5.55.0)
150
+ shellany (0.0.1)
151
+ simplecov (0.16.1)
152
+ docile (~> 1.1)
153
+ json (>= 1.8, < 3)
154
+ simplecov-html (~> 0.10.0)
155
+ simplecov-html (0.10.2)
156
+ sqlite3 (1.6.1-x86_64-darwin)
157
+ sync (0.5.0)
158
+ term-ansicolor (1.7.1)
159
+ tins (~> 1.0)
160
+ terminal-table (1.6.0)
161
+ thor (1.2.2)
162
+ tins (1.31.0)
163
+ sync
164
+ tzinfo (2.0.6)
165
+ concurrent-ruby (~> 1.0)
166
+ unicode-display_width (2.4.2)
167
+ zeitwerk (2.6.8)
168
+
169
+ PLATFORMS
170
+ x86_64-darwin-20
171
+
172
+ DEPENDENCIES
173
+ activerecord (> 4, < 7)
174
+ appraisal
175
+ builder
176
+ bundler
177
+ coveralls
178
+ fuubar (~> 2)
179
+ guard (~> 2)
180
+ guard-bundler (~> 2)
181
+ guard-rspec (~> 4)
182
+ link_header
183
+ oj
184
+ parslet
185
+ praxis!
186
+ pry
187
+ pry-byebug
188
+ pry-stack_explorer
189
+ rack-test (~> 0)
190
+ rake (>= 12.3.3)
191
+ rspec (~> 3)
192
+ rspec-collection_matchers (~> 1)
193
+ rspec-its (~> 1)
194
+ rubocop
195
+ sequel (~> 5)
196
+ sqlite3 (~> 1)
197
+
198
+ BUNDLED WITH
199
+ 2.4.12
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop"
6
+ gem "activerecord", ">=7"
7
+ gem "sequel", "~> 5"
8
+
9
+ group :test do
10
+ gem "builder"
11
+ gem "link_header"
12
+ gem "oj"
13
+ gem "parslet"
14
+ end
15
+
16
+ gemspec path: "../"
@@ -0,0 +1,197 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ praxis (2.0.pre.32)
5
+ activesupport (>= 3)
6
+ attributor (>= 6.5)
7
+ mime (~> 0)
8
+ mustermann (>= 1.1)
9
+ rack (>= 1)
10
+ terminal-table (~> 1.4)
11
+ thor
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ activemodel (7.0.4.2)
17
+ activesupport (= 7.0.4.2)
18
+ activerecord (7.0.4.2)
19
+ activemodel (= 7.0.4.2)
20
+ activesupport (= 7.0.4.2)
21
+ activesupport (7.0.4.2)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 1.6, < 2)
24
+ minitest (>= 5.1)
25
+ tzinfo (~> 2.0)
26
+ appraisal (2.4.1)
27
+ bundler
28
+ rake
29
+ thor (>= 0.14.0)
30
+ ast (2.4.2)
31
+ attributor (6.5)
32
+ activesupport (>= 3)
33
+ hashie (~> 3)
34
+ randexp (~> 0)
35
+ binding_of_caller (1.0.0)
36
+ debug_inspector (>= 0.0.1)
37
+ builder (3.2.4)
38
+ byebug (11.1.3)
39
+ coderay (1.1.3)
40
+ concurrent-ruby (1.2.2)
41
+ coveralls (0.8.23)
42
+ json (>= 1.8, < 3)
43
+ simplecov (~> 0.16.1)
44
+ term-ansicolor (~> 1.3)
45
+ thor (>= 0.19.4, < 2.0)
46
+ tins (~> 1.6)
47
+ debug_inspector (1.1.0)
48
+ diff-lcs (1.5.0)
49
+ docile (1.4.0)
50
+ ffi (1.15.5)
51
+ formatador (1.1.0)
52
+ fuubar (2.5.1)
53
+ rspec-core (~> 3.0)
54
+ ruby-progressbar (~> 1.4)
55
+ guard (2.18.0)
56
+ formatador (>= 0.2.4)
57
+ listen (>= 2.7, < 4.0)
58
+ lumberjack (>= 1.0.12, < 2.0)
59
+ nenv (~> 0.1)
60
+ notiffany (~> 0.0)
61
+ pry (>= 0.13.0)
62
+ shellany (~> 0.0)
63
+ thor (>= 0.18.1)
64
+ guard-bundler (2.2.1)
65
+ bundler (>= 1.3.0, < 3)
66
+ guard (~> 2.2)
67
+ guard-compat (~> 1.1)
68
+ guard-compat (1.2.1)
69
+ guard-rspec (4.7.3)
70
+ guard (~> 2.1)
71
+ guard-compat (~> 1.1)
72
+ rspec (>= 2.99.0, < 4.0)
73
+ hashie (3.6.0)
74
+ i18n (1.13.0)
75
+ concurrent-ruby (~> 1.0)
76
+ json (2.6.3)
77
+ link_header (0.0.8)
78
+ listen (3.8.0)
79
+ rb-fsevent (~> 0.10, >= 0.10.3)
80
+ rb-inotify (~> 0.9, >= 0.9.10)
81
+ lumberjack (1.2.8)
82
+ method_source (1.0.0)
83
+ mime (0.4.4)
84
+ minitest (5.18.0)
85
+ mustermann (3.0.0)
86
+ ruby2_keywords (~> 0.0.1)
87
+ nenv (0.3.0)
88
+ notiffany (0.1.3)
89
+ nenv (~> 0.1)
90
+ shellany (~> 0.0)
91
+ oj (3.14.2)
92
+ parallel (1.23.0)
93
+ parser (3.2.2.1)
94
+ ast (~> 2.4.1)
95
+ parslet (2.0.0)
96
+ pry (0.14.2)
97
+ coderay (~> 1.1)
98
+ method_source (~> 1.0)
99
+ pry-byebug (3.10.1)
100
+ byebug (~> 11.0)
101
+ pry (>= 0.13, < 0.15)
102
+ pry-stack_explorer (0.6.1)
103
+ binding_of_caller (~> 1.0)
104
+ pry (~> 0.13)
105
+ rack (2.2.7)
106
+ rack-test (0.8.3)
107
+ rack (>= 1.0, < 3)
108
+ rainbow (3.1.1)
109
+ rake (13.0.6)
110
+ randexp (0.1.7)
111
+ rb-fsevent (0.11.2)
112
+ rb-inotify (0.10.1)
113
+ ffi (~> 1.0)
114
+ regexp_parser (2.8.0)
115
+ rexml (3.2.5)
116
+ rspec (3.12.0)
117
+ rspec-core (~> 3.12.0)
118
+ rspec-expectations (~> 3.12.0)
119
+ rspec-mocks (~> 3.12.0)
120
+ rspec-collection_matchers (1.2.0)
121
+ rspec-expectations (>= 2.99.0.beta1)
122
+ rspec-core (3.12.1)
123
+ rspec-support (~> 3.12.0)
124
+ rspec-expectations (3.12.2)
125
+ diff-lcs (>= 1.2.0, < 2.0)
126
+ rspec-support (~> 3.12.0)
127
+ rspec-its (1.3.0)
128
+ rspec-core (>= 3.0.0)
129
+ rspec-expectations (>= 3.0.0)
130
+ rspec-mocks (3.12.3)
131
+ diff-lcs (>= 1.2.0, < 2.0)
132
+ rspec-support (~> 3.12.0)
133
+ rspec-support (3.12.0)
134
+ rubocop (1.48.1)
135
+ json (~> 2.3)
136
+ parallel (~> 1.10)
137
+ parser (>= 3.2.0.0)
138
+ rainbow (>= 2.2.2, < 4.0)
139
+ regexp_parser (>= 1.8, < 3.0)
140
+ rexml (>= 3.2.5, < 4.0)
141
+ rubocop-ast (>= 1.26.0, < 2.0)
142
+ ruby-progressbar (~> 1.7)
143
+ unicode-display_width (>= 2.4.0, < 3.0)
144
+ rubocop-ast (1.28.0)
145
+ parser (>= 3.2.1.0)
146
+ ruby-progressbar (1.13.0)
147
+ ruby2_keywords (0.0.5)
148
+ sequel (5.55.0)
149
+ shellany (0.0.1)
150
+ simplecov (0.16.1)
151
+ docile (~> 1.1)
152
+ json (>= 1.8, < 3)
153
+ simplecov-html (~> 0.10.0)
154
+ simplecov-html (0.10.2)
155
+ sqlite3 (1.6.1-x86_64-darwin)
156
+ sync (0.5.0)
157
+ term-ansicolor (1.7.1)
158
+ tins (~> 1.0)
159
+ terminal-table (1.6.0)
160
+ thor (1.2.2)
161
+ tins (1.31.0)
162
+ sync
163
+ tzinfo (2.0.6)
164
+ concurrent-ruby (~> 1.0)
165
+ unicode-display_width (2.4.2)
166
+
167
+ PLATFORMS
168
+ x86_64-darwin-20
169
+
170
+ DEPENDENCIES
171
+ activerecord (>= 7)
172
+ appraisal
173
+ builder
174
+ bundler
175
+ coveralls
176
+ fuubar (~> 2)
177
+ guard (~> 2)
178
+ guard-bundler (~> 2)
179
+ guard-rspec (~> 4)
180
+ link_header
181
+ oj
182
+ parslet
183
+ praxis!
184
+ pry
185
+ pry-byebug
186
+ pry-stack_explorer
187
+ rack-test (~> 0)
188
+ rake (>= 12.3.3)
189
+ rspec (~> 3)
190
+ rspec-collection_matchers (~> 1)
191
+ rspec-its (~> 1)
192
+ rubocop
193
+ sequel (~> 5)
194
+ sqlite3 (~> 1)
195
+
196
+ BUNDLED WITH
197
+ 2.4.12
@@ -18,7 +18,7 @@ module Praxis
18
18
  case val
19
19
  when Regexp
20
20
  options[:regexp] = val
21
- when String
21
+ when ::String
22
22
  options[:values] = [val]
23
23
  when nil
24
24
  # Defining the existence without any other options can only mean that it is required (otherwise it is a useless definition)
@@ -10,7 +10,8 @@ module Praxis
10
10
  # aren't invoked by just merely loading, and only really invoked when we've asked to render them
11
11
  # It takes the name of the group, and passes the attributes block that needs to be a subset of the MediaType where the group resides
12
12
  def group(name, **options, &block)
13
- attribute(name, Praxis::BlueprintAttributeGroup.for(target.options[:reference]), **options, &block)
13
+ # Pass the reference to the target type by default. But allow overriding it if needed
14
+ attribute(name, Praxis::BlueprintAttributeGroup.for(target), **{reference: target}.merge(options), &block)
14
15
  end
15
16
  end
16
17
 
@@ -62,7 +63,6 @@ module Praxis
62
63
 
63
64
  class << self
64
65
  attr_reader :attribute, :options
65
- attr_accessor :reference
66
66
  end
67
67
 
68
68
  def self.inherited(klass)
@@ -97,11 +97,7 @@ module Praxis
97
97
 
98
98
  def self.attributes(opts = {}, &block)
99
99
  if block_given?
100
- raise 'Redefining Blueprint attributes is not currently supported' if const_defined?(:Struct, false)
101
-
102
- raise "Reference mismatch in #{inspect}. Given :reference option #{opts[:reference].inspect}, while using #{reference.inspect}" if opts.key?(:reference) && opts[:reference] != reference
103
-
104
- opts[:reference] = (reference || self)
100
+ raise 'Redefining Blueprint attributes is not currently supported' if const_defined?(:InnerStruct, false)
105
101
 
106
102
  @options.merge!(opts.merge(dsl_compiler: DSLCompiler))
107
103
  @block = block
@@ -133,7 +129,7 @@ module Praxis
133
129
  new(value)
134
130
  end
135
131
  else
136
- if value.is_a?(domain_model) || value.is_a?(self::Struct)
132
+ if value.is_a?(domain_model) || value.is_a?(self::InnerStruct)
137
133
  # Wrap the value directly
138
134
  new(value)
139
135
  else
@@ -253,7 +249,7 @@ module Praxis
253
249
  @attribute = Attributor::Attribute.new(Attributor::Struct, @options, &@block)
254
250
  @block = nil
255
251
  @attribute.type.anonymous_type true
256
- const_set(:Struct, @attribute.type)
252
+ const_set(:InnerStruct, @attribute.type)
257
253
  end
258
254
 
259
255
  def self.define_readers!
@@ -288,6 +284,8 @@ module Praxis
288
284
  attributes.each do |name, attr|
289
285
  the_type = attr.type < Attributor::Collection ? attr.type.member_type : attr.type
290
286
  next if the_type < Blueprint
287
+ # TODO: Allow groups in the default fieldset?? or perhaps better to make people explicitly define them?
288
+ # next if (the_type < Blueprint && !(the_type < BlueprintAttributeGroup))
291
289
 
292
290
  # NOTE: we won't try to expand fields here, as we want to be lazy (and we're expanding)
293
291
  # every time a request comes in anyway. This could be an optimization we do at some point
@@ -353,6 +351,7 @@ module Praxis
353
351
 
354
352
  leftover = self.class.attributes.keys - keys_provided
355
353
  leftover.each do |key|
354
+ sub_context = self.class.generate_subcontext(context, key)
356
355
  attribute = self.class.attributes[key]
357
356
 
358
357
  errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is required."] if attribute.options[:required]
@@ -10,10 +10,8 @@ module Praxis
10
10
  def self.construct(attribute_definition, options = {})
11
11
  return self if attribute_definition.nil?
12
12
 
13
- reference_type = @media_type
14
13
  # Construct a group-derived class with the given mediatype as the reference
15
14
  ::Class.new(self) do
16
- @reference = reference_type
17
15
  attributes(**options, &attribute_definition)
18
16
  end
19
17
  end
@@ -34,7 +34,7 @@ module Praxis
34
34
  @model = model
35
35
  @filters_map = filters_map
36
36
  @logger = debug ? Logger.new($stdout) : nil
37
- @active_record_version_maj = ActiveRecord.gem_version.segments[0]
37
+ @active_record_version = ActiveRecord.gem_version
38
38
  end
39
39
 
40
40
  def debug_query(msg, query)
@@ -86,7 +86,8 @@ module Praxis
86
86
  )
87
87
  end
88
88
 
89
- if @active_record_version_maj < 6
89
+
90
+ if @active_record_version < Gem::Version.new('6')
90
91
  # ActiveRecord < 6 does not support '.and' so no nested things can be done
91
92
  # But we can still support the case of 1+ flat conditions of the same AND/OR type
92
93
  if root_parent_group.is_a?(FilteringParams::Condition)
@@ -347,8 +348,7 @@ module Praxis
347
348
  end
348
349
 
349
350
  # The value that we need to stick in the references method is different in the latest Rails
350
- maj, min, = ActiveRecord.gem_version.segments
351
- if maj == 5 || (maj == 6 && min.zero?)
351
+ if ActiveRecord.gem_version < Gem::Version.new('6')
352
352
  # In AR 6 (and 6.0) the references are simple strings
353
353
  def self.build_reference_value(column_prefix, **_args)
354
354
  column_prefix
@@ -2,17 +2,11 @@
2
2
 
3
3
  require 'active_record'
4
4
 
5
- maj, min, = ActiveRecord.gem_version.segments
6
-
7
- case maj
8
- when 5
5
+ if ActiveRecord.gem_version < Gem::Version.new('6')
9
6
  require_relative 'active_record_patches/5x'
10
- when 6
11
- if min.zero?
12
- require_relative 'active_record_patches/6_0'
13
- else
14
- require_relative 'active_record_patches/6_1_plus'
15
- end
7
+ elsif ActiveRecord.gem_version < Gem::Version.new('6.1')
8
+ require_relative 'active_record_patches/6_0'
16
9
  else
17
- raise 'Filtering only supported for ActiveRecord >= 5 && <= 6'
18
- end
10
+ # As of 7.0.4 our 6.1-plus patches still work
11
+ require_relative 'active_record_patches/6_1_plus'
12
+ end
@@ -45,6 +45,8 @@ module Praxis
45
45
  quoted_prefix = AttributeFiltering::ActiveRecordFilterQueryBuilder.quote_column_path(query: query, prefix: column_prefix, column_name: info[:attribute])
46
46
  order_clause = Arel.sql(ActiveRecord::Base.sanitize_sql_array("#{quoted_prefix} #{direction}"))
47
47
  query = query.order(order_clause)
48
+ # Add a select for any order clause (unless we're already selecting *), as latest MySQL versions require it for DISTINCT queries
49
+ query = query.select(quoted_prefix) unless query.select_values.empty?
48
50
  end
49
51
  query
50
52
  end
@@ -89,7 +89,7 @@ module Praxis
89
89
  end
90
90
 
91
91
  def default(spec)
92
- unless spec.is_a?(Hash) && spec.keys.size == 1 && %i[by page].include?(spec.keys.first)
92
+ unless spec.is_a?(::Hash) && spec.keys.size == 1 && %i[by page].include?(spec.keys.first)
93
93
  raise "'default' syntax for pagination takes exactly one key specification. Either by: <:fieldname> or page: <num>" \
94
94
  "#{spec} is invalid"
95
95
  end
@@ -101,7 +101,7 @@ module Praxis
101
101
 
102
102
  { by: value }
103
103
  when :page
104
- raise "Error setting default pagination. Initial page should be a integer (but got #{value})" unless value.is_a?(Integer)
104
+ raise "Error setting default pagination. Initial page should be a integer (but got #{value})" unless value.is_a?(::Integer)
105
105
  raise 'Cannot define a default pagination that is page-based, if page-based pagination is disallowed.' if target.defaults[:disallow_paging]
106
106
 
107
107
  { page: value }
@@ -67,19 +67,17 @@ module Praxis
67
67
  end
68
68
 
69
69
  def _join_foreign_key_for(assoc_reflection)
70
- maj, min, = ActiveRecord.gem_version.segments
71
- if maj >= 6 && min >= 1
70
+ if ActiveRecord.gem_version >= Gem::Version.new('6.1')
72
71
  assoc_reflection.join_foreign_key.to_sym
73
- else
72
+ else # below 6.1
74
73
  assoc_reflection.join_keys.foreign_key.to_sym
75
74
  end
76
75
  end
77
76
 
78
77
  def _join_primary_key_for(assoc_reflection)
79
- maj, min, = ActiveRecord.gem_version.segments
80
- if maj >= 6 && min >= 1
78
+ if ActiveRecord.gem_version >= Gem::Version.new('6.1')
81
79
  assoc_reflection.join_primary_key.to_sym
82
- else
80
+ else # below 6.1
83
81
  assoc_reflection.join_keys.key.to_sym
84
82
  end
85
83
  end
@@ -45,7 +45,7 @@ module Praxis
45
45
  # @see Attributor::Model#load
46
46
  def self.load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options)
47
47
  case value
48
- when String
48
+ when ::String
49
49
  return nil if value.blank?
50
50
 
51
51
  base, *parameters = value.split(PARAMETER_SEPARATOR)
@@ -66,7 +66,7 @@ module Praxis
66
66
  else
67
67
  obj.type = 'application'
68
68
  obj.subtype = base.split(WORD_SEPARATOR, 2).first
69
- obj.suffix = String.new
69
+ obj.suffix = ::String.new
70
70
  obj.parameters = {}
71
71
  end
72
72
  obj
@@ -126,7 +126,7 @@ module Praxis
126
126
  # @return [String] canonicalized representation of the media type including all suffixes and parameters
127
127
  def to_s
128
128
  # Our handcrafted media types consist of a rich chocolatey center
129
- s = String.new("#{type}/#{subtype}")
129
+ s = ::String.new("#{type}/#{subtype}")
130
130
 
131
131
  # coated in a hard candy shell
132
132
  s << '+' << suffix unless suffix.empty?
@@ -204,7 +204,7 @@ module Praxis
204
204
  obj.type = type
205
205
  obj.subtype = subtype
206
206
  target_suffix = suffix || self.suffix
207
- obj.suffix = redundant_suffix(target_suffix) ? String.new : target_suffix
207
+ obj.suffix = redundant_suffix(target_suffix) ? ::String.new : target_suffix
208
208
  obj.parameters = self.parameters.merge(parameters)
209
209
 
210
210
  obj
@@ -219,13 +219,13 @@ module Praxis
219
219
  hash[:part_name] = { type: name_type.describe(true) }
220
220
 
221
221
  unless shallow
222
- hash[:attributes] = {} if attributes.keys.any? { |name| name.is_a? String }
222
+ hash[:attributes] = {} if attributes.keys.any? { |name| name.is_a? ::String }
223
223
  hash[:pattern_attributes] = {} if attributes.keys.any? { |name| name.is_a? Regexp }
224
224
 
225
225
  if hash.key?(:attributes) || hash.key?(:pattern_attributes)
226
226
  describe_attributes(shallow, example: example).each do |name, sub_hash|
227
227
  case name
228
- when String
228
+ when ::String
229
229
  hash[:attributes][name] = sub_hash
230
230
  when Regexp
231
231
  hash[:pattern_attributes][name.source] = sub_hash
@@ -305,7 +305,7 @@ module Praxis
305
305
  return self << part
306
306
  elsif self.class.options[:case_insensitive_load]
307
307
  name = self.class.attributes.keys.find do |k|
308
- k.is_a?(String) && key.downcase == k.downcase
308
+ k.is_a?(::String) && key.downcase == k.downcase
309
309
  end
310
310
  if name
311
311
  part.name = name
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Praxis
4
- VERSION = '2.0.pre.32'
4
+ VERSION = '2.0.pre.33'
5
5
  end
data/praxis.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.executables << 'praxis'
24
24
 
25
25
  spec.add_dependency 'activesupport', '>= 3'
26
- spec.add_dependency 'attributor', '>= 6.5'
26
+ spec.add_dependency 'attributor', '>= 7.0'
27
27
  spec.add_dependency 'mime', '~> 0'
28
28
  spec.add_dependency 'mustermann', '>=1.1'
29
29
  spec.add_dependency 'rack', '>= 1'
@@ -32,6 +32,7 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.add_development_dependency 'bundler'
34
34
  spec.add_development_dependency 'rake', '>= 12.3.3'
35
+ spec.add_development_dependency "appraisal"
35
36
 
36
37
  if RUBY_PLATFORM !~ /java/
37
38
  spec.add_development_dependency 'pry'
@@ -50,7 +51,4 @@ Gem::Specification.new do |spec|
50
51
  spec.add_development_dependency 'rspec', '~> 3'
51
52
  spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
52
53
  spec.add_development_dependency 'rspec-its', '~> 1'
53
- # Just for the query selector extensions etc...
54
- spec.add_development_dependency 'activerecord', '> 4', '< 7'
55
- spec.add_development_dependency 'sequel', '~> 5'
56
54
  end
@@ -7,6 +7,280 @@ describe Praxis::Blueprint do
7
7
 
8
8
  its(:family) { should eq('hash') }
9
9
 
10
+ # This context might seem a duplication of tests that should be covered by the underlying attributor gem
11
+ # but while it is in structure, it is different because we're doing it with Blueprints (not Attributor Models)
12
+ # to make sure our Blueprints are behaving as expected.
13
+ context 'type resolution, option inheritance for attributes with and without references' do
14
+ # Overall strategy
15
+ # 1) When no type is specified:
16
+ # 1.1) if it is a leaf (no block)
17
+ # 1.1.1) with an reference with an attr with the same name
18
+ # - type copied from reference
19
+ # - reference options are inherited as well (and can be overridden by local attribute ones)
20
+ # 1.1.2) without a ref (or the ref does not have same attribute name)
21
+ # - Fail. Cannot determine type
22
+ # 1.2) if it has a block
23
+ # 1.2.1) with an reference with an attr with the same name
24
+ # - We assume you're re/defining a new Struct (or Struct[]), and we will incorporate the reference type
25
+ # for the matching name in case you are indeed redefining a subset of the attributes, so you can enjoy inheritance
26
+ # 1.2.2) without a ref (or the ref does not have same attribute name)
27
+ # - defaulted to Struct (if you meant Collection.of(Struct) things would fail later somehow)
28
+ # - options are NOT inherited at all (This is something we should ponder more about)
29
+ # 2) When type is specified:
30
+ # 2.1) if it is a leaf (no block)
31
+ # - ignore ref if there is one (with or without matching attribute name).
32
+ # - simply use provided type, and provided options (no inheritance)
33
+ # 2.2) if it has a block
34
+ # - Same as above: use type and options provided, ignore ref if there is one (with or without matching attribute name).
35
+
36
+ let(:mytype) do
37
+ Praxis::Blueprint.finalize!
38
+ Class.new(Praxis::Blueprint, &myblock).tap{|c| c._finalize!}
39
+ end
40
+ context 'with no explicit type specified' do
41
+ context 'without a block (if it is a leaf)' do
42
+ context 'that has a reference with an attribute with the same name' do
43
+ let(:myblock) {
44
+ Proc.new do
45
+ attributes reference: PersonBlueprint do
46
+ attribute :age, required: true, min: 42
47
+ end
48
+ end
49
+ }
50
+ it 'uses type from reference' do
51
+ expect(mytype.attributes).to have_key(:age)
52
+ expect(mytype.attributes[:age].type).to eq(PersonBlueprint.attributes[:age].type)
53
+ end
54
+ it 'copies over reference options and allows the attribute to override/add some' do
55
+ merged_options = PersonBlueprint.attributes[:age].options.merge(required: true, min: 42)
56
+ expect(mytype.attributes[:age].options).to include(merged_options)
57
+ end
58
+ end
59
+ context 'with a reference, but that does not have a matching attribute name' do
60
+ let(:myblock) {
61
+ Proc.new do
62
+ attributes reference: AddressBlueprint do
63
+ attribute :age
64
+ end
65
+ end
66
+ }
67
+ it 'fails resolving' do
68
+ expect{mytype.attributes}.to raise_error(/Type for attribute with name: age could not be determined./)
69
+ end
70
+ end
71
+ context 'without a reference' do
72
+ let(:myblock) {
73
+ Proc.new do
74
+ attributes do
75
+ attribute :age
76
+ end
77
+ end
78
+ }
79
+ it 'fails resolving' do
80
+ expect{mytype.attributes}.to raise_error(/Type for attribute with name: age could not be determined./)
81
+ end
82
+ end
83
+ end
84
+ context 'with block (if it is NOT a leaf)' do
85
+ context 'that has a reference with an attribute with the same name' do
86
+ context 'which is not a collection type' do
87
+ let(:myblock) {
88
+ Proc.new do
89
+ attributes reference: PersonBlueprint do
90
+ attribute :age , description: 'I am fully redefining' do
91
+ attribute :foobar, Integer, min: 42
92
+ end
93
+ end
94
+ end
95
+ }
96
+ it 'picks Struct, and makes sure to pass the reference of the attribute along' do
97
+ expect(mytype.attributes).to have_key(:age)
98
+ age_attribute = mytype.attributes[:age]
99
+ # Resolves to Struct
100
+ expect(age_attribute.type).to be < Attributor::Struct
101
+ # does NOT brings any ref options (except the right reference)
102
+ expect(age_attribute.options).to include(description: 'I am fully redefining')
103
+ # Yes, there is no way we can ever use an Integer when we're defining a Struct...but if the parent was a Struct, we would
104
+ expect(age_attribute.options).to include(reference: Attributor::Integer)
105
+ # And the nested attribute is correctly resolved as well, and ensures options are there
106
+ expect(age_attribute.type.attributes[:foobar].type).to eq(Attributor::Integer)
107
+ expect(age_attribute.type.attributes[:foobar].options).to eq(min: 42)
108
+ end
109
+ end
110
+ context 'which is a collection type' do
111
+ let(:myblock) {
112
+ Proc.new do
113
+ attributes reference: PersonBlueprint do
114
+ attribute :prior_addresses , description: 'I am fully redefining' do
115
+ attribute :street, required: true
116
+ attribute :new_attribute, String, default: 'foo'
117
+ end
118
+ end
119
+ end
120
+ }
121
+ it 'picks Struct, and makes sure to pass the reference of the attribute along' do
122
+ expect(mytype.attributes).to have_key(:prior_addresses)
123
+ prior_addresses_attribute = mytype.attributes[:prior_addresses]
124
+ # Resolves to Struct[]
125
+ expect(prior_addresses_attribute.type).to be < Attributor::Collection
126
+ expect(prior_addresses_attribute.type.member_type).to be < Attributor::Struct
127
+ # does NOT brings any ref options (except the right reference)
128
+ expect(prior_addresses_attribute.options).to include(description: 'I am fully redefining')
129
+ # Yes, there is no way we can ever use an Integer when we're defining a Struct...but if the parent was a Struct, we would
130
+ expect(prior_addresses_attribute.options).to include(reference: PersonBlueprint.attributes[:prior_addresses].type.member_type)
131
+ # And the nested attributes are correctly resolved as well, and ensures options are there
132
+ street_options_from_ref = PersonBlueprint.attributes[:prior_addresses].type.member_type.attributes[:street].options
133
+ expect(prior_addresses_attribute.type.member_type.attributes[:street].type).to eq(Attributor::String)
134
+ expect(prior_addresses_attribute.type.member_type.attributes[:street].options).to eq(street_options_from_ref.merge(required: true))
135
+
136
+ expect(prior_addresses_attribute.type.member_type.attributes[:new_attribute].type).to eq(Attributor::String)
137
+ expect(prior_addresses_attribute.type.member_type.attributes[:new_attribute].options).to eq(default: 'foo')
138
+ end
139
+ end
140
+ context 'in the unlikely case that the reference type has an anonymous Struct (or collection of)' do
141
+ let(:myblock) {
142
+ Proc.new do
143
+ attributes reference: PersonBlueprint do
144
+ attribute :funny_attribute, description: 'Funny business' do
145
+ attribute :foobar, Integer, min: 42
146
+ end
147
+ end
148
+ end
149
+ }
150
+ it 'correctly inherits it (same result as defaulting to Struct) and brings in the reference' do
151
+ expect(mytype.attributes).to have_key(:funny_attribute)
152
+ # Resolves to Struct, and brings (and merges) the ref options with the attribute's
153
+ expect(mytype.attributes[:funny_attribute].type).to be < Attributor::Struct
154
+ merged_options = {reference: PersonBlueprint.attributes[:funny_attribute].type}.merge(description: 'Funny business')
155
+ expect(mytype.attributes[:funny_attribute].options).to include(merged_options)
156
+ # And the nested attribute is correctly resolved as well, and ensures options are there
157
+ expect(mytype.attributes[:funny_attribute].type.attributes[:foobar].type).to eq(Attributor::Integer)
158
+ expect(mytype.attributes[:funny_attribute].type.attributes[:foobar].options).to eq(min: 42)
159
+ end
160
+ end
161
+ end
162
+ context 'with a reference, but that does not have a matching attribute name' do
163
+ let(:myblock) {
164
+ Proc.new do
165
+ attributes reference: AddressBlueprint do
166
+ attribute :age, description: 'I am redefining' do
167
+ attribute :foobar, Integer, min: 42
168
+ end
169
+ end
170
+ end
171
+ }
172
+ it 'correctly defaults to Struct uses only the local options (same exact as if it had no reference)' do
173
+ expect(mytype.attributes).to have_key(:age)
174
+ age_attribute = mytype.attributes[:age]
175
+ # Resolves to Struct
176
+ expect(age_attribute.type).to be < Attributor::Struct
177
+ # does NOT brings any ref options
178
+ expect(age_attribute.options).to eq(description: 'I am redefining')
179
+ # And the nested attribute is correctly resolved as well, and ensures options are there
180
+ expect(age_attribute.type.attributes[:foobar].type).to eq(Attributor::Integer)
181
+ expect(age_attribute.type.attributes[:foobar].options).to eq(min: 42)
182
+ end
183
+ end
184
+ context 'without a reference' do
185
+ let(:myblock) {
186
+ Proc.new do
187
+ attributes do
188
+ attribute :age, description: 'I am redefining' do
189
+ attribute :foobar, Integer, min: 42
190
+ end
191
+ end
192
+ end
193
+ }
194
+ it 'correctly defaults to Struct uses only the local options' do
195
+ expect(mytype.attributes).to have_key(:age)
196
+ age_attribute = mytype.attributes[:age]
197
+ # Resolves to Struct
198
+ expect(age_attribute.type).to be < Attributor::Struct
199
+ # does NOT brings any ref options
200
+ expect(age_attribute.options).to eq(description: 'I am redefining')
201
+ # And the nested attribute is correctly resolved as well, and ensures options are there
202
+ expect(age_attribute.type.attributes[:foobar].type).to eq(Attributor::Integer)
203
+ expect(age_attribute.type.attributes[:foobar].options).to eq(min: 42)
204
+ end
205
+ end
206
+ end
207
+ end
208
+ context 'with an explicit type specified' do
209
+ context 'without a reference' do
210
+ let(:myblock) {
211
+ Proc.new do
212
+ attributes do
213
+ attribute :age, String, description: 'I am a String now'
214
+ end
215
+ end
216
+ }
217
+ it 'always uses the provided type and local options specified' do
218
+ expect(mytype.attributes).to have_key(:age)
219
+ age_attribute = mytype.attributes[:age]
220
+ # Resolves to String
221
+ expect(age_attribute.type).to eq(Attributor::String)
222
+ # copies local options
223
+ expect(age_attribute.options).to eq(description: 'I am a String now')
224
+ end
225
+ end
226
+ context 'with a reference' do
227
+ let(:myblock) {
228
+ Proc.new do
229
+ attributes reference: PersonBlueprint do
230
+ attribute :age, String, description: 'I am a String now'
231
+ end
232
+ end
233
+ }
234
+ it 'always uses the provided type and local options specified (same as if it had no reference)' do
235
+ expect(mytype.attributes).to have_key(:age)
236
+ age_attribute = mytype.attributes[:age]
237
+ # Resolves to String
238
+ expect(age_attribute.type).to eq(Attributor::String)
239
+ # copies local options
240
+ expect(age_attribute.options).to eq(description: 'I am a String now')
241
+ end
242
+ end
243
+
244
+ context 'with a reference, which can further percolate down' do
245
+ let(:myblock) {
246
+ Proc.new do
247
+ attributes reference: PersonBlueprint do
248
+ attribute :age, String, description: 'I am a String now'
249
+ attribute :address, Struct, description: 'Address subset' do
250
+ attribute :street, required: true
251
+ end
252
+ attribute :tags
253
+ end
254
+ end
255
+ }
256
+
257
+ it 'brings the child reference for address so we can redefine it' do
258
+ expect(mytype.attributes.keys).to eq([:age, :address, :tags])
259
+ age_attribute = mytype.attributes[:age]
260
+ expect(age_attribute.type).to eq(Attributor::String)
261
+ expect(age_attribute.options).to eq(description: 'I am a String now')
262
+
263
+ address_attribute = mytype.attributes[:address]
264
+ expect(address_attribute.type).to be < Attributor::Struct
265
+ # It brings in our local options AND percolates down the reference type for address
266
+ expect(address_attribute.options).to include(description: 'Address subset', reference: AddressBlueprint)
267
+
268
+ # Address fields are properly resolved to match the corresponding AddressBlueprint
269
+ expect(address_attribute.type.attributes.keys).to eq([:street])
270
+ street_attribute = address_attribute.type.attributes[:street]
271
+ expect(street_attribute.type).to eq(AddressBlueprint.attributes[:street].type)
272
+ # Makes sure our local options on the street are kept
273
+ expect(street_attribute.options).to include(required: true)
274
+ # And brings in other options from the inherited street attribute
275
+ expect(street_attribute.options).to include(description: 'The street')
276
+
277
+ # It also properly resolves the direct tags attribute from the reference, pointing to the same type
278
+ tags_attribute = mytype.attributes[:tags]
279
+ expect(tags_attribute.type).to eq PersonBlueprint.attributes[:tags].type
280
+ end
281
+ end
282
+ end
283
+ end
10
284
  context 'deterministic examples' do
11
285
  it 'works' do
12
286
  person1 = PersonBlueprint.example('person 1')
@@ -75,7 +349,7 @@ describe Praxis::Blueprint do
75
349
  end
76
350
 
77
351
  it 'has an inner Struct class for the attributes' do
78
- expect(blueprint_class.attribute.type).to be blueprint_class::Struct
352
+ expect(blueprint_class.attribute.type).to be blueprint_class::InnerStruct
79
353
  end
80
354
 
81
355
  context 'an instance' do
@@ -221,22 +495,23 @@ describe Praxis::Blueprint do
221
495
  end
222
496
  end
223
497
 
224
- context 'with a provided :reference option on attributes' do
225
- context 'that does not match the value set on the class' do
226
- subject(:mismatched_reference) do
227
- Class.new(Praxis::Blueprint) do
228
- self.reference = Class.new(Praxis::Blueprint)
229
- attributes(reference: Class.new(Praxis::Blueprint)) {}
230
- end
231
- end
232
-
233
- it 'should raise an error' do
234
- expect do
235
- mismatched_reference.attributes
236
- end.to raise_error(/Reference mismatch/)
237
- end
238
- end
239
- end
498
+ # TODO: Think about this 'feature' ...
499
+ # context 'with a provided :reference option on attributes' do
500
+ # context 'that does not match the value set on the class' do
501
+ # subject(:mismatched_reference) do
502
+ # Class.new(Praxis::Blueprint) do
503
+ # self.reference = Class.new(Praxis::Blueprint)
504
+ # attributes(reference: Class.new(Praxis::Blueprint)) {}
505
+ # end
506
+ # end
507
+
508
+ # it 'should raise an error' do
509
+ # expect do
510
+ # mismatched_reference.attributes
511
+ # end.to raise_error(/Reference mismatch/)
512
+ # end
513
+ # end
514
+ # end
240
515
 
241
516
  context '.example' do
242
517
  context 'with some attribute values provided' do
@@ -154,6 +154,34 @@ describe Praxis::Extensions::Pagination::ActiveRecordPaginationHandler do
154
154
  end
155
155
  end
156
156
  end
157
+ context 'adds SELECT fields when necessary' do
158
+ let(:order_params) { book_ordering_params_attribute.load(op) }
159
+ context 'when query comes with SELECT *' do
160
+ let(:query) { ActiveBook.includes(:author) }
161
+ let(:op) { '-writer.books.name' }
162
+ it 'does not add any specific SELECT' do
163
+ expect(subject.all.select_values).to be_empty
164
+ end
165
+ end
166
+ context 'when query comes with some SELECT fields' do
167
+ let(:query) { ActiveBook.includes(:author).select('simple_name') }
168
+ context 'with one ordering field' do
169
+ let(:op) { '-writer.books.name' }
170
+ it 'adds the field to SELECT (and keeps the original one)' do
171
+ expect(subject.all.select_values).to include('"/author/books"."simple_name"')
172
+ expect(subject.all.select_values).to include('simple_name')
173
+ end
174
+ end
175
+ context 'with multiple ordering fields' do
176
+ let(:op) { '-writer.books.name,author.id' }
177
+ it 'adds both fields to SELECT (and keeps the original one)' do
178
+ expect(subject.all.select_values).to include('"/author/books"."simple_name"')
179
+ expect(subject.all.select_values).to include('"/author"."id"')
180
+ expect(subject.all.select_values).to include('simple_name')
181
+ end
182
+ end
183
+ end
184
+ end
157
185
  end
158
186
 
159
187
  context '.association_info_for' do
@@ -5,10 +5,12 @@ class PersonBlueprint < Praxis::Blueprint
5
5
  attribute :name, String, example: /[:first_name:]/
6
6
  attribute :email, String, example: proc { |person| "#{person.name}@example.com" }
7
7
 
8
- attribute :age, Integer
8
+ attribute :age, Integer, min: 0
9
+ # Weird, anonymous attribute built for specs only
10
+ attribute :funny_attribute, Struct, allow_extra: true
9
11
 
10
12
  attribute :full_name, FullName
11
- attribute :aliases, Attributor::Collection.of(FullName)
13
+ attribute :aliases, FullName[]
12
14
 
13
15
  attribute :address, AddressBlueprint, null: true, example: proc { |person, context| AddressBlueprint.example(context, resident: person) }
14
16
  attribute :work_address, AddressBlueprint, null: true
@@ -19,7 +21,7 @@ class PersonBlueprint < Praxis::Blueprint
19
21
  attribute :mother, String
20
22
  end
21
23
 
22
- attribute :tags, Attributor::Collection.of(String)
24
+ attribute :tags, Attributor::String[]
23
25
  attribute :href, String
24
26
  attribute :alive, Attributor::Boolean, default: true
25
27
  attribute :myself, PersonBlueprint, null: true
@@ -44,7 +46,7 @@ class AddressBlueprint < Praxis::Blueprint
44
46
  attributes do
45
47
  attribute :id, Integer
46
48
  attribute :name, String
47
- attribute :street, String
49
+ attribute :street, String, description: 'The street'
48
50
  attribute :state, String, values: %w[OR CA]
49
51
 
50
52
  attribute :resident, PersonBlueprint, example: proc { |address, context| PersonBlueprint.example(context, address: address) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.pre.32
4
+ version: 2.0.pre.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2023-05-01 00:00:00.000000000 Z
12
+ date: 2023-05-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '6.5'
34
+ version: '7.0'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: '6.5'
41
+ version: '7.0'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: mime
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -137,6 +137,20 @@ dependencies:
137
137
  - - ">="
138
138
  - !ruby/object:Gem::Version
139
139
  version: 12.3.3
140
+ - !ruby/object:Gem::Dependency
141
+ name: appraisal
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
140
154
  - !ruby/object:Gem::Dependency
141
155
  name: pry
142
156
  requirement: !ruby/object:Gem::Requirement
@@ -319,40 +333,6 @@ dependencies:
319
333
  - - "~>"
320
334
  - !ruby/object:Gem::Version
321
335
  version: '1'
322
- - !ruby/object:Gem::Dependency
323
- name: activerecord
324
- requirement: !ruby/object:Gem::Requirement
325
- requirements:
326
- - - ">"
327
- - !ruby/object:Gem::Version
328
- version: '4'
329
- - - "<"
330
- - !ruby/object:Gem::Version
331
- version: '7'
332
- type: :development
333
- prerelease: false
334
- version_requirements: !ruby/object:Gem::Requirement
335
- requirements:
336
- - - ">"
337
- - !ruby/object:Gem::Version
338
- version: '4'
339
- - - "<"
340
- - !ruby/object:Gem::Version
341
- version: '7'
342
- - !ruby/object:Gem::Dependency
343
- name: sequel
344
- requirement: !ruby/object:Gem::Requirement
345
- requirements:
346
- - - "~>"
347
- - !ruby/object:Gem::Version
348
- version: '5'
349
- type: :development
350
- prerelease: false
351
- version_requirements: !ruby/object:Gem::Requirement
352
- requirements:
353
- - - "~>"
354
- - !ruby/object:Gem::Version
355
- version: '5'
356
336
  description:
357
337
  email:
358
338
  - blanquer@gmail.com
@@ -368,6 +348,7 @@ files:
368
348
  - ".ruby-version"
369
349
  - ".simplecov"
370
350
  - ".travis.yml"
351
+ - Appraisals
371
352
  - CHANGELOG.md
372
353
  - CONTRIBUTING.md
373
354
  - Gemfile
@@ -378,6 +359,10 @@ files:
378
359
  - Rakefile
379
360
  - SELECTOR_NOTES.txt
380
361
  - bin/praxis
362
+ - gemfiles/active_6.gemfile
363
+ - gemfiles/active_6.gemfile.lock
364
+ - gemfiles/active_7.gemfile
365
+ - gemfiles/active_7.gemfile.lock
381
366
  - lib/praxis.rb
382
367
  - lib/praxis/action_definition.rb
383
368
  - lib/praxis/action_definition/headers_dsl_compiler.rb