measured 2.8.2 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +20 -0
  3. data/.github/workflows/ci.yml +12 -6
  4. data/.github/workflows/cla.yml +23 -0
  5. data/.github/workflows/dependabot_auto_merge.yml +93 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +13 -0
  8. data/Gemfile +2 -0
  9. data/README.md +114 -4
  10. data/cache/weight.json +230 -0
  11. data/dev.yml +1 -2
  12. data/gemfiles/rails-7.0.gemfile +6 -0
  13. data/gemfiles/rails-7.1.gemfile +6 -0
  14. data/gemfiles/rails-edge.gemfile +6 -0
  15. data/lib/measured/measurable.rb +3 -1
  16. data/lib/measured/rails/active_record.rb +130 -0
  17. data/lib/measured/rails/validations.rb +68 -0
  18. data/lib/measured/railtie.rb +12 -0
  19. data/lib/measured/units/weight.rb +3 -2
  20. data/lib/measured/version.rb +1 -1
  21. data/lib/measured.rb +2 -0
  22. data/lib/tapioca/dsl/compilers/measured_rails.rb +110 -0
  23. data/measured.gemspec +5 -0
  24. data/test/internal/app/models/thing.rb +14 -0
  25. data/test/internal/app/models/thing_with_custom_unit_accessor.rb +18 -0
  26. data/test/internal/app/models/thing_with_custom_value_accessor.rb +19 -0
  27. data/test/internal/app/models/validated_thing.rb +45 -0
  28. data/test/internal/config/database.yml +3 -0
  29. data/test/internal/config.ru +9 -0
  30. data/test/internal/db/.gitignore +1 -0
  31. data/test/internal/db/schema.rb +99 -0
  32. data/test/internal/log/.gitignore +1 -0
  33. data/test/measurable_test.rb +4 -0
  34. data/test/rails/active_record_test.rb +433 -0
  35. data/test/rails/validation_test.rb +252 -0
  36. data/test/tapioca/dsl/compilers/measured_rails_test.rb +220 -0
  37. data/test/test_helper.rb +15 -0
  38. data/test/units/weight_test.rb +77 -2
  39. metadata +84 -10
  40. data/gemfiles/activesupport-5.2.gemfile +0 -5
  41. data/gemfiles/activesupport-6.0.gemfile +0 -5
  42. data/gemfiles/activesupport-6.1.gemfile +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 808003a2689c157f3d3a1ef7ff2eb2f1decc242d23c40a536d9bca8c57364a79
4
- data.tar.gz: 42f4a72969b5331d7cd460f87d5668b3b331001b82f351c221f7153488859be5
3
+ metadata.gz: 3b304fbf20408beb13b8408d74c17747f00ccb991b586775b91249f2e90e9073
4
+ data.tar.gz: d8df7e3a16743234d79b1d8375f2f6d83e69f18824fdd1ff95c530fdd333d3a3
5
5
  SHA512:
6
- metadata.gz: 4019bd9f128308af57f5ce9440e6a53f05587c1589f120b711c937b70d422a865c14d2e678d4b55bb13054e24a2663b5cb6ef51bfad12e834027eb4a644d5651
7
- data.tar.gz: caf36d6ad0dbbd0e187adbb0d5a2ddd2718a38ddd67920e628c1d7ad09423f9c35edcef7f05cb863fec8e2849bb0eb8ce4145496aa0749cf2aaafbcd6459ba13
6
+ metadata.gz: 3f7e92b9cdc9165c11dca6857ae9bbc141b938eb071e89f56ca1e13198fc6d281bde0abe4d3436d2f102d869057c5b1f60562bdcb2f9376739a4927b11ccb248
7
+ data.tar.gz: 4b3d830a1814b6b64dd9123a40e444918adf3a546b925bb57088bc2c4324da0ccd7040253a838d24f0c53ea1c749cbade548dee29f2995678be903f87af04dcf
@@ -0,0 +1,20 @@
1
+ version: 2
2
+ registries:
3
+ ruby-shopify:
4
+ type: rubygems-server
5
+ url: https://pkgs.shopify.io/basic/gems/ruby
6
+ username: ${{secrets.RUBYGEMS_SERVER_PKGS_SHOPIFY_IO_USERNAME}}
7
+ password: ${{secrets.RUBYGEMS_SERVER_PKGS_SHOPIFY_IO_PASSWORD}}
8
+ github-com:
9
+ type: git
10
+ url: https://github.com
11
+ username: ${{secrets.DEPENDENCIES_GITHUB_USER}}
12
+ password: ${{secrets.DEPENDENCIES_GITHUB_TOKEN}}
13
+ updates:
14
+ - package-ecosystem: bundler
15
+ directory: "/"
16
+ schedule:
17
+ interval: weekly
18
+ open-pull-requests-limit: 100
19
+ insecure-external-code-execution: allow
20
+ registries: "*"
@@ -10,14 +10,20 @@ jobs:
10
10
  strategy:
11
11
  matrix:
12
12
  ruby:
13
- - '2.6'
14
- - '2.7'
15
- - '3.0'
13
+ - '3.1'
14
+ - '3.2'
15
+ - '3.3'
16
16
  gemfile:
17
17
  - Gemfile
18
- - gemfiles/activesupport-5.2.gemfile
19
- - gemfiles/activesupport-6.0.gemfile
20
- - gemfiles/activesupport-6.1.gemfile
18
+ - gemfiles/rails-7.0.gemfile
19
+ - gemfiles/rails-7.1.gemfile
20
+ - gemfiles/rails-edge.gemfile
21
+ exclude:
22
+ - ruby: '3.1'
23
+ gemfile: gemfiles/rails-edge.gemfile
24
+ - ruby: '3.2'
25
+ gemfile: gemfiles/rails-edge.gemfile
26
+
21
27
  name: Ruby ${{ matrix.ruby }} ${{ matrix.gemfile }}
22
28
  steps:
23
29
  - uses: actions/checkout@v1
@@ -0,0 +1,23 @@
1
+ # .github/workflows/cla.yml
2
+ name: Contributor License Agreement (CLA)
3
+
4
+ on:
5
+ pull_request_target:
6
+ types: [opened, synchronize]
7
+ issue_comment:
8
+ types: [created]
9
+
10
+ jobs:
11
+ cla:
12
+ runs-on: ubuntu-latest
13
+ if: |
14
+ (github.event.issue.pull_request
15
+ && !github.event.issue.pull_request.merged_at
16
+ && contains(github.event.comment.body, 'signed')
17
+ )
18
+ || (github.event.pull_request && !github.event.pull_request.merged)
19
+ steps:
20
+ - uses: Shopify/shopify-cla-action@v1
21
+ with:
22
+ github-token: ${{ secrets.GITHUB_TOKEN }}
23
+ cla-token: ${{ secrets.CLA_TOKEN }}
@@ -0,0 +1,93 @@
1
+ name: Dependabot auto-merge
2
+ on: pull_request_target
3
+
4
+ jobs:
5
+ dependabot:
6
+ runs-on: shopify-ubuntu-latest
7
+ if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}
8
+ steps:
9
+ - name: Dependabot metadata
10
+ id: metadata
11
+ uses: dependabot/fetch-metadata@v1.6.0
12
+ with:
13
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
14
+
15
+ - name: Waiting for CI to finish
16
+ id: check_ci_failure
17
+ continue-on-error: true
18
+ if: ${{ steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.dependency-group == 'auto_merge' }}
19
+ uses: actions/github-script@v6
20
+ with:
21
+ script: |
22
+ function sleep(ms) {
23
+ return new Promise(resolve => setTimeout(resolve, ms))
24
+ }
25
+ const query = `query ($org: String!, $repo: String!, $pullRequestNumber: Int!) {
26
+ organization(login: $org) {
27
+ repository(name: $repo) {
28
+ pullRequest(number: $pullRequestNumber) {
29
+ commits(last: 1) {
30
+ nodes {
31
+ commit {
32
+ status {
33
+ state
34
+ }
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }`;
42
+ const variables = {
43
+ org: context.repo.owner,
44
+ repo: context.repo.repo,
45
+ pullRequestNumber: context.issue.number
46
+ }
47
+ // Try for 30 minutes
48
+ let attempts = 30
49
+ let ci_state = false
50
+ for (let i = 1; i <= attempts; i++) {
51
+ console.log(`Sleeping for 60 seconds`)
52
+ await sleep(60000)
53
+ const result = await github.graphql(query, variables)
54
+ const state = result["organization"]["repository"]["pullRequest"]["commits"]["nodes"][0]["commit"]["status"]["state"]
55
+ console.log(`Status is ${state} after ${i} attempts`)
56
+ if (state === "SUCCESS") {
57
+ ci_state = true
58
+ console.log("Proceeding with workflow as CI succeed")
59
+ break
60
+ }
61
+ }
62
+ core.setOutput("ci_state", ci_state)
63
+ - name: Send Slack notification if auto-merge failed
64
+ if: ${{ steps.check_ci_failure.outputs.ci_state == 'false' }}
65
+ uses: ruby/action-slack@v3.0.0
66
+ with:
67
+ payload: |
68
+ {
69
+ "attachments": [{
70
+ "text": "Auto-merge failed for pull request <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}> in repository ${{ github.repository }}",
71
+ "color": "danger"
72
+ }
73
+ ]
74
+ }
75
+ env:
76
+ SLACK_WEBHOOK_URL: ${{ secrets.METRICS_SLACK_WEBHOOK_URL }}
77
+
78
+ - name: Approve and merge
79
+ if: ${{ steps.check_ci_failure.outputs.ci_state == 'true' && (steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.dependency-group == 'auto_merge') }}
80
+ uses: actions/github-script@v6
81
+ with:
82
+ script: |
83
+ await github.rest.pulls.createReview({
84
+ pull_number: context.issue.number,
85
+ owner: context.repo.owner,
86
+ repo: context.repo.repo,
87
+ event: 'APPROVE',
88
+ })
89
+ await github.rest.pulls.merge({
90
+ owner: context.repo.owner,
91
+ repo: context.repo.repo,
92
+ issue_number: context.issue.number,
93
+ })
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@ Unreleased
2
2
  -----
3
3
 
4
4
 
5
+ 3.1.0
6
+ -----
7
+ * Drop support for Rails 6 and Ruby 3.0.
8
+ * Add qunitals. Add aliases for UK ton/tonne. (@ragarwal6397)
9
+
10
+
11
+ 3.0.0
12
+ -----
13
+
14
+ * Merge functionality of `measured-rails` into this gem. From this version on, this gem is able to automatically integrate with Active Record out of the box. (@paracycle)
15
+ * Add `:gm` and `:gms` as aliases to weight. (@kushagra-03)
16
+ * Adds support for initializing `Measured` objects with a rational value from string. (@dvisockas)
17
+ * Make `Measured` initialization faster by avoiding string substitution in certain cases. (@bitwise-aiden)
5
18
 
6
19
  2.8.2
7
20
  -----
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ gem "activerecord"
data/README.md CHANGED
@@ -4,7 +4,7 @@ Encapsulates measurements with their units. Provides easy conversion between uni
4
4
 
5
5
  Lightweight and easily extensible to include other units and conversions. Conversions done with `Rational` for precision.
6
6
 
7
- The adapter to integrate `measured` with Ruby on Rails is in a separate [`measured-rails`](https://github.com/Shopify/measured-rails) gem.
7
+ Since version 3.0.0, the adapter to integrate `measured` with Ruby on Rails is also a part of this gem. If you had been using [`measured-rails`](https://github.com/Shopify/measured-rails) for that functionality, you should now remove `measured-rails` from your gem file.
8
8
 
9
9
  ## Installation
10
10
 
@@ -158,6 +158,116 @@ Measured::Weight.new("3.14", "kg").format(with_conversion_string: false)
158
158
  > "3.14 kg"
159
159
  ```
160
160
 
161
+ ### Active Record
162
+
163
+ This gem also provides an Active Record adapter for persisting and retrieving measurements with their units, and model validations.
164
+
165
+ Columns are expected to have the `_value` and `_unit` suffix, and be `DECIMAL` and `VARCHAR`, and defaults are accepted. Customizing the column used to hold units is supported, see below for details.
166
+
167
+ ```ruby
168
+ class AddWeightAndLengthToThings < ActiveRecord::Migration
169
+ def change
170
+ add_column :things, :minimum_weight_value, :decimal, precision: 10, scale: 2
171
+ add_column :things, :minimum_weight_unit, :string, limit: 12
172
+
173
+ add_column :things, :total_length_value, :decimal, precision: 10, scale: 2, default: 0
174
+ add_column :things, :total_length_unit, :string, limit: 12, default: "cm"
175
+ end
176
+ end
177
+ ```
178
+
179
+ A column can be declared as a measurement with its measurement subclass:
180
+
181
+ ```ruby
182
+ class Thing < ActiveRecord::Base
183
+ measured Measured::Weight, :minimum_weight
184
+ measured Measured::Length, :total_length
185
+ measured Measured::Volume, :total_volume
186
+ end
187
+ ```
188
+
189
+ You can optionally customize the model's unit column by specifying it in the `unit_field_name` option, as follows:
190
+
191
+ ```ruby
192
+ class ThingWithCustomUnitAccessor < ActiveRecord::Base
193
+ measured_length :length, :width, :height, unit_field_name: :size_unit
194
+ measured_weight :total_weight, :extra_weight, unit_field_name: :weight_unit
195
+ measured_volume :total_volume, :extra_volume, unit_field_name: :volume_unit
196
+ end
197
+ ```
198
+
199
+ Similarly, you can optionally customize the model's value column by specifying it in the `value_field_name` option, as follows:
200
+
201
+ ```ruby
202
+ class ThingWithCustomValueAccessor < ActiveRecord::Base
203
+ measured_length :length, value_field_name: :custom_length
204
+ measured_weight :total_weight, value_field_name: :custom_weight
205
+ measured_volume :volume, value_field_name: :custom_volume
206
+ end
207
+ ```
208
+
209
+ There are some simpler methods for predefined types:
210
+
211
+ ```ruby
212
+ class Thing < ActiveRecord::Base
213
+ measured_weight :minimum_weight
214
+ measured_length :total_length
215
+ measured_volume :total_volume
216
+ end
217
+ ```
218
+
219
+ This will allow you to access and assign a measurement object:
220
+
221
+ ```ruby
222
+ thing = Thing.new
223
+ thing.minimum_weight = Measured::Weight.new(10, "g")
224
+ thing.minimum_weight_unit # "g"
225
+ thing.minimum_weight_value # 10
226
+ ```
227
+
228
+ Order of assignment does not matter, and each property can be assigned separately and with mass assignment:
229
+
230
+ ```ruby
231
+ params = { total_length_unit: "cm", total_length_value: "3" }
232
+ thing = Thing.new(params)
233
+ thing.total_length.to_s # 3 cm
234
+ ```
235
+
236
+ ### Validations
237
+
238
+ Validations are available:
239
+
240
+ ```ruby
241
+ class Thing < ActiveRecord::Base
242
+ measured_length :total_length
243
+
244
+ validates :total_length, measured: true
245
+ end
246
+ ```
247
+
248
+ This will validate that the unit is defined on the measurement, and that there is a value.
249
+
250
+ Rather than `true` the validation can accept a hash with the following options:
251
+
252
+ * `message`: Override the default "is invalid" message.
253
+ * `units`: A subset of units available for this measurement. Units must be in existing measurement.
254
+ * `greater_than`
255
+ * `greater_than_or_equal_to`
256
+ * `equal_to`
257
+ * `less_than`
258
+ * `less_than_or_equal_to`
259
+
260
+ All comparison validations require `Measured::Measurable` values, not scalars. Most of these options replace the `numericality` validator which compares the measurement/method name/proc to the column's value. Validations can also be combined with `presence` validator.
261
+
262
+ **Note:** Validations are strongly recommended since assigning an invalid unit will cause the measurement to return `nil`, even if there is a value:
263
+
264
+ ```ruby
265
+ thing = Thing.new
266
+ thing.total_length_value = 1
267
+ thing.total_length_unit = "invalid"
268
+ thing.total_length # nil
269
+ ```
270
+
161
271
  ## Units and conversions
162
272
 
163
273
  ### SI units support
@@ -269,7 +379,7 @@ Existing alternatives which were considered:
269
379
  * **Cons**
270
380
  * Opens up and modifies `Array`, `Date`, `Fixnum`, `Math`, `Numeric`, `String`, `Time`, and `Object`, then depends on those changes internally.
271
381
  * Lots of code to solve a relatively simple problem.
272
- * No ActiveRecord adapter.
382
+ * No Active Record adapter.
273
383
 
274
384
  ### Gem: [quantified](https://github.com/Shopify/quantified)
275
385
  * **Pros**
@@ -278,7 +388,7 @@ Existing alternatives which were considered:
278
388
  * All math done with floats making it highly lossy.
279
389
  * All units assumed to be pluralized, meaning using unit abbreviations is not possible.
280
390
  * Not actively maintained.
281
- * No ActiveRecord adapter.
391
+ * No Active Record adapter.
282
392
 
283
393
  ### Gem: [unitwise](https://github.com/joshwlewis/unitwise)
284
394
  * **Pros**
@@ -287,7 +397,7 @@ Existing alternatives which were considered:
287
397
  * **Cons**
288
398
  * Lots of code. Good code, but lots of it.
289
399
  * Many modifications to core types.
290
- * ActiveRecord adapter exists but is written and maintained by a different person/org.
400
+ * Active Record adapter exists but is written and maintained by a different person/org.
291
401
  * Not actively maintained.
292
402
 
293
403
  ## Contributing