{"id":13484239,"url":"https://github.com/Shopify/measured","last_synced_at":"2025-03-27T16:30:34.802Z","repository":{"id":28115344,"uuid":"31614166","full_name":"Shopify/measured","owner":"Shopify","description":"Encapsulate measurements and their units in Ruby and Ruby on Rails.","archived":false,"fork":false,"pushed_at":"2025-01-28T13:50:39.000Z","size":342,"stargazers_count":371,"open_issues_count":8,"forks_count":29,"subscribers_count":486,"default_branch":"main","last_synced_at":"2025-03-27T11:09:54.438Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Shopify.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-03-03T18:29:09.000Z","updated_at":"2025-03-24T10:46:17.000Z","dependencies_parsed_at":"2024-04-15T22:34:42.547Z","dependency_job_id":"a2a45317-d356-4676-aabc-423a108728da","html_url":"https://github.com/Shopify/measured","commit_stats":{"total_commits":191,"total_committers":33,"mean_commits":5.787878787878788,"dds":0.5759162303664922,"last_synced_commit":"27bb3e95669cee50134c073e11f0326cf15f7661"},"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fmeasured","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fmeasured/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fmeasured/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Fmeasured/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Shopify","download_url":"https://codeload.github.com/Shopify/measured/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245882193,"owners_count":20687844,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T17:01:21.172Z","updated_at":"2025-03-27T16:30:34.760Z","avatar_url":"https://github.com/Shopify.png","language":"Ruby","readme":"# Measured [![Build Status](https://github.com/Shopify/measured/workflows/CI/badge.svg)](https://github.com/Shopify/measured/actions?query=workflow%3ACI)\n\nEncapsulates measurements with their units. Provides easy conversion between units. Built in support for weight, length, and volume.\n\nLightweight and easily extensible to include other units and conversions. Conversions done with `Rational` for precision.\n\nSince 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.\n\n## Installation\n\nUsing bundler, add to the Gemfile:\n\n```ruby\ngem 'measured'\n```\n\nOr stand alone:\n\n```\n$ gem install measured\n```\n\n## Usage\n\nInitialize a measurement:\n\n```ruby\nMeasured::Weight.new(\"12\", \"g\")\n\u003e #\u003cMeasured::Weight: 12 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\n```\n\nConvert to return a new measurement:\n\n```ruby\nMeasured::Weight.new(\"12\", \"g\").convert_to(\"kg\")\n\u003e #\u003cMeasured::Weight: 0.012 #\u003cMeasured::Unit: kg (kilogram, kilograms) 1000/1 g\u003e\u003e\n```\n\nAgnostic to symbols/strings:\n\n```ruby\nMeasured::Weight.new(1, \"kg\") == Measured::Weight.new(1, :kg)\n\u003e true\n```\n\nSeamlessly handles aliases:\n\n```ruby\nMeasured::Weight.new(12, :oz) == Measured::Weight.new(\"12\", :ounce)\n\u003e true\n```\n\nRaises on unknown units:\n\n```ruby\nbegin\n  Measured::Weight.new(1, :stone)\nrescue Measured::UnitError\n  puts \"Unknown unit\"\nend\n```\n\nParse from string without having to split out the value and unit first:\n\n```ruby\nMeasured::Weight.parse(\"123 grams\")\n\u003e #\u003cMeasured::Weight: 123 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\n```\n\nParse can scrub extra whitespace and split number from unit:\n\n```ruby\nMeasured::Weight.parse(\" 2kg \")\n\u003e #\u003cMeasured::Weight: 2 #\u003cMeasured::Unit: kg (kilogram, kilograms) 1000/1 g\u003e\u003e\n```\n\nPerform addition / subtraction against other units, all represented internally as `Rational` or `BigDecimal`:\n\n```ruby\nMeasured::Weight.new(1, :g) + Measured::Weight.new(2, :g)\n\u003e #\u003cMeasured::Weight: 3 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\nMeasured::Weight.new(\"2.1\", :g) - Measured::Weight.new(1, :g)\n\u003e #\u003cMeasured::Weight: 1.1 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\n```\n\nMultiplication and division by units is not supported, but the actual value can be scaled by a scalar:\n\n```ruby\nMeasured::Weight.new(10, :g).scale(0.5)\n\u003e #\u003cMeasured::Weight: 5 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\nMeasured::Weight.new(2, :g).scale(3)\n\u003e #\u003cMeasured::Weight: 6 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\n```\n\nIn cases of differing units, the left hand side takes precedence:\n\n```ruby\nMeasured::Weight.new(1000, :g) + Measured::Weight.new(1, :kg)\n\u003e #\u003cMeasured::Weight: 2000 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\n```\n\nConverts units only as needed for equality comparison:\n\n```ruby\n\u003e Measured::Weight.new(1000, :g) == Measured::Weight.new(1, :kg)\ntrue\n```\n\nExtract the unit and the value:\n\n```ruby\nweight = Measured::Weight.new(\"1.2\", \"grams\")\nweight.value\n\u003e #\u003cBigDecimal:7fabf6c1d0a0,'0.12E1',18(18)\u003e\nweight.unit\n\u003e #\u003cMeasured::Unit: g (gram, grams)\u003e\n```\n\nSee all valid units:\n\n```ruby\nMeasured::Weight.unit_names\n\u003e [\"g\", \"kg\", \"lb\", \"oz\"]\n```\n\nCheck if a unit is a valid unit or alias:\n\n```ruby\nMeasured::Weight.unit_or_alias?(:g)\n\u003e true\nMeasured::Weight.unit_or_alias?(\"gram\")\n\u003e true\nMeasured::Weight.unit_or_alias?(\"stone\")\n\u003e false\n```\n\nSee all valid units with their aliases:\n\n```ruby\nMeasured::Weight.unit_names_with_aliases\n\u003e [\"g\", \"gram\", \"grams\", \"kg\", \"kilogram\", \"kilograms\", \"lb\", \"lbs\", \"ounce\", \"ounces\", \"oz\", \"pound\", \"pounds\"]\n```\n\nString formatting:\n```ruby\nMeasured::Weight.new(\"3.14\", \"grams\").format(\"%.1\u003cvalue\u003ef %\u003cunit\u003es\")\n\u003e \"3.1 g\"\n```\n\nIf no string is passed to the `format` method it defaults to `\"%.2\u003cvalue\u003ef %\u003cunit\u003es\"`.\n\nIf the unit isn't the standard SI unit, it will include a conversion string.\n\n```ruby\nMeasured::Weight.new(\"3.14\", \"kg\").format\n\u003e \"3.14 kg (1000/1 g)\"\nMeasured::Weight.new(\"3.14\", \"kg\").format(with_conversion_string: false)\n\u003e \"3.14 kg\"\n```\n\n### Active Record\n\nThis gem also provides an Active Record adapter for persisting and retrieving measurements with their units, and model validations.\n\nColumns 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.\n\n```ruby\nclass AddWeightAndLengthToThings \u003c ActiveRecord::Migration\n  def change\n    add_column :things, :minimum_weight_value, :decimal, precision: 10, scale: 2\n    add_column :things, :minimum_weight_unit, :string, limit: 12\n\n    add_column :things, :total_length_value, :decimal, precision: 10, scale: 2, default: 0\n    add_column :things, :total_length_unit, :string, limit: 12, default: \"cm\"\n  end\nend\n```\n\nA column can be declared as a measurement with its measurement subclass:\n\n```ruby\nclass Thing \u003c ActiveRecord::Base\n  measured Measured::Weight, :minimum_weight\n  measured Measured::Length, :total_length\n  measured Measured::Volume, :total_volume\nend\n```\n\nYou can optionally customize the model's unit column by specifying it in the `unit_field_name` option, as follows:\n\n```ruby\nclass ThingWithCustomUnitAccessor \u003c ActiveRecord::Base\n  measured_length :length, :width, :height,     unit_field_name: :size_unit\n  measured_weight :total_weight, :extra_weight, unit_field_name: :weight_unit\n  measured_volume :total_volume, :extra_volume, unit_field_name: :volume_unit\nend\n```\n\nSimilarly, you can optionally customize the model's value column by specifying it in the `value_field_name` option, as follows:\n\n```ruby\nclass ThingWithCustomValueAccessor \u003c ActiveRecord::Base\n  measured_length :length, value_field_name: :custom_length\n  measured_weight :total_weight, value_field_name: :custom_weight\n  measured_volume :volume, value_field_name: :custom_volume\nend\n```\n\nThere are some simpler methods for predefined types:\n\n```ruby\nclass Thing \u003c ActiveRecord::Base\n  measured_weight :minimum_weight\n  measured_length :total_length\n  measured_volume :total_volume\nend\n```\n\nThis will allow you to access and assign a measurement object:\n\n```ruby\nthing = Thing.new\nthing.minimum_weight = Measured::Weight.new(10, \"g\")\nthing.minimum_weight_unit     # \"g\"\nthing.minimum_weight_value    # 10\n```\n\nOrder of assignment does not matter, and each property can be assigned separately and with mass assignment:\n\n```ruby\nparams = { total_length_unit: \"cm\", total_length_value: \"3\" }\nthing = Thing.new(params)\nthing.total_length.to_s   # 3 cm\n```\n\n### Validations\n\nValidations are available:\n\n```ruby\nclass Thing \u003c ActiveRecord::Base\n  measured_length :total_length\n\n  validates :total_length, measured: true\nend\n```\n\nThis will validate that the unit is defined on the measurement, and that there is a value.\n\nRather than `true` the validation can accept a hash with the following options:\n\n* `message`: Override the default \"is invalid\" message.\n* `units`: A subset of units available for this measurement. Units must be in existing measurement.\n* `greater_than`\n* `greater_than_or_equal_to`\n* `equal_to`\n* `less_than`\n* `less_than_or_equal_to`\n\nAll 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.\n\n**Note:** Validations are strongly recommended since assigning an invalid unit will cause the measurement to return `nil`, even if there is a value:\n\n```ruby\nthing = Thing.new\nthing.total_length_value = 1\nthing.total_length_unit = \"invalid\"\nthing.total_length  # nil\n```\n\n## Units and conversions\n\n### SI units support\n\nThere is support for SI units through the use of `si_unit`. Units declared through it will have automatic support for all SI prefixes:\n\n| Multiplying Factor                | SI Prefix | Scientific Notation   |\n| --------------------------------- | --------- | --------------------- |\n| 1 000 000 000 000 000 000 000 000 | yotta (Y) | 10^24                 |\n| 1 000 000 000 000 000 000 000     | zetta (Z) | 10^21                 |\n| 1 000 000 000 000 000 000         | exa (E)   | 10^18                 |\n| 1 000 000 000 000 000             | peta (P)  | 10^15                 |\n| 1 000 000 000 000                 | tera (T)  | 10^12                 |\n| 1 000 000 000                     | giga (G)  | 10^9                  |\n| 1 000 000                         | mega (M)  | 10^6                  |\n| 1 000                             | kilo (k)  | 10^3                  |\n| 0.001                             | milli (m) | 10^-3                 |\n| 0.000 001                         | micro (µ) | 10^-6                 |\n| 0.000 000 001                     | nano (n)  | 10^-9                 |\n| 0.000 000 000 001                 | pico (p)  | 10^-12                |\n| 0.000 000 000 000 001             | femto (f) | 10^-15                |\n| 0.000 000 000 000 000 001         | atto (a)  | 10^-18                |\n| 0.000 000 000 000 000 000 001     | zepto (z) | 10^-21                |\n| 0.000 000 000 000 000 000 000 001 | yocto (y) | 10^-24                |\n\n### Bundled unit conversion\n\n* `Measured::Weight`\n  * g, gram, grams, and all SI prefixes\n  * t, metric_ton, metric_tons\n  * slug, slugs\n  * N, newtons, newton\n  * long_ton, long_tons, weight_ton, weight_tons, 'W/T', imperial_ton, imperial_tons, displacement_ton, displacement_tons\n  * short_ton, short_tons\n  * lb, lbs, pound, pounds\n  * oz, ounce, ounces\n* `Measured::Length`\n  * m, meter, metre, meters, metres, and all SI prefixes\n  * in, inch, inches\n  * ft, foot, feet\n  * yd, yard, yards\n  * mi, mile, miles\n* `Measured::Volume`\n  * l, liter, litre, liters, litres, and all SI prefixes\n  * m3, cubic_meter, cubic_meters, cubic_metre, cubic_metres\n  * ft3, cubic_foot, cubic_feet\n  * in3, cubic_inch, cubic_inches\n  * gal, imp_gal, imperial_gallon, imp_gals, imperial_gallons\n  * us_gal, us_gallon, us_gals, us_gallons\n  * qt, imp_qt, imperial_quart, imp_qts, imperial_quarts\n  * us_qt, us_quart, us_quarts\n  * pt, imp_pt, imperial_pint, imp_pts, imperial_pints\n  * us_pt, us_pint, us_pints\n  * oz, fl_oz, imp_fl_oz, imperial_fluid_ounce, imperial_fluid_ounces\n  * us_oz, us_fl_oz, us_fluid_ounce, us_fluid_ounces\n\nYou can skip these and only define your own units by doing:\n\n```ruby\ngem 'measured', require: 'measured/base'\n```\n\n### Shortcut syntax\n\nThere is a shortcut initialization syntax for creating instances of measurement classes that can avoid the `.new`:\n\n```ruby\nMeasured::Weight(1, :g)\n\u003e #\u003cMeasured::Weight: 1 #\u003cMeasured::Unit: g (gram, grams)\u003e\u003e\n```\n\n### Adding new units\n\nExtending this library to support other units is simple. To add a new conversion, use `Measured.build` to define your base unit and conversion units:\n\n```ruby\nMeasured::Thing = Measured.build do\n  unit :base_unit,           # Add a unit to the system\n    aliases: [:bu]           # Allow it to be aliased to other names/symbols\n\n  unit :another_unit,        # Add a second unit to the system\n    aliases: [:au],          # All units allow aliases, as long as they are unique\n    value: \"1.5 bu\"        # The conversion rate to another unit\nend\n```\n\nAll unit names are case sensitive.\n\nValues for conversion units can be defined as a string with two tokens `\"number unit\"` or as an array with two elements. All values will be parsed as / coerced to `Rational`. Conversion paths don't have to be direct as a conversion table will be built for all possible conversions.\n\n### Namespaces\n\nAll units and classes are namespaced by default, but can be aliased in your application.\n\n```ruby\nWeight = Measured::Weight\nLength = Measured::Length\nVolume = Measured::Volume\n```\n\n## Alternatives\n\nExisting alternatives which were considered:\n\n### Gem: [ruby-units](https://github.com/olbrich/ruby-units)\n* **Pros**\n  * Accurate math and conversion factors.\n  * Includes nearly every unit you could ask for.\n* **Cons**\n  * Opens up and modifies `Array`, `Date`, `Fixnum`, `Math`, `Numeric`, `String`, `Time`, and `Object`, then depends on those changes internally.\n  * Lots of code to solve a relatively simple problem.\n  * No Active Record adapter.\n\n### Gem: [quantified](https://github.com/Shopify/quantified)\n* **Pros**\n  * Lightweight.\n* **Cons**\n  * All math done with floats making it highly lossy.\n  * All units assumed to be pluralized, meaning using unit abbreviations is not possible.\n  * Not actively maintained.\n  * No Active Record adapter.\n\n### Gem: [unitwise](https://github.com/joshwlewis/unitwise)\n* **Pros**\n  * Well written.\n  * Conversions done with Unified Code for Units of Measure (UCUM) so highly accurate and reliable.\n* **Cons**\n  * Lots of code. Good code, but lots of it.\n  * Many modifications to core types.\n  * Active Record adapter exists but is written and maintained by a different person/org.\n  * Not actively maintained.\n\n## Contributing\n\n1. Fork it ( https://github.com/Shopify/measured/fork )\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create a new Pull Request\n\n## Authors\n\n* [Kevin McPhillips](https://github.com/kmcphillips) at [Shopify](http://shopify.com/careers)\n* [Sai Warang](https://github.com/cyprusad) at [Shopify](http://shopify.com/careers)\n* [Gareth du Plooy](https://github.com/garethson) at [Shopify](http://shopify.com/careers)\n","funding_links":[],"categories":["Ruby","Gems","Measurements"],"sub_categories":["Attributes Management"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FShopify%2Fmeasured","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FShopify%2Fmeasured","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FShopify%2Fmeasured/lists"}