measured 2.8.2 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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