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

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 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