diff --git a/.rubocop.yml b/.rubocop.yml index 537f3da..c844694 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,8 +1,63 @@ +require: + - rubocop-rspec + AllCops: + NewCops: enable TargetRubyVersion: 3.1 + Exclude: + - "spec/**/*" + SuggestExtensions: false + +Style/Documentation: + Enabled: true + +Metrics/MethodLength: + Max: 15 + +Metrics/AbcSize: + Max: 20 + +Metrics/CyclomaticComplexity: + Max: 10 + +Metrics/PerceivedComplexity: + Max: 10 + +Layout/LineLength: + Max: 120 + +Layout/LineEndStringConcatenationIndentation: + EnforcedStyle: aligned Style/StringLiterals: EnforcedStyle: double_quotes -Style/StringLiteralsInInterpolation: - EnforcedStyle: double_quotes +Style/SymbolArray: + EnforcedStyle: percent + +Style/WordArray: + EnforcedStyle: percent + +Style/HashSyntax: + EnforcedStyle: ruby19 + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + +Style/ClassAndModuleChildren: + EnforcedStyle: nested + +Style/ArgumentsForwarding: + Enabled: true + +Naming/BlockForwarding: + Enabled: true + +Gemspec/RequireMFA: + Enabled: false + +Gemspec/DevelopmentDependencies: + Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index b4afc28..c9f7d3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ -## [Unreleased] +# Changelog -## [0.1.0] - 2025-03-27 +All notable changes to this project will be documented in this file. -- Initial release +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.1.0] - 2024-03-27 + +### Added +- Initial release of Morphix +- Core transformation methods: + - `rename`: Rename keys with optional value transformation + - `map`: Transform values while preserving keys + - `reject`: Remove specific keys from the hash + - `reshape`: Transform nested hash structures + - `map_collection`: Transform arrays of hashes +- Support for complex data transformations: + - Nested structure handling + - Collection transformations + - Conditional transformations + - Data type conversions +- Robust error handling for edge cases +- Performance optimizations for large data structures +- Comprehensive test suite with RSpec +- Detailed documentation and examples + +### Features +- Fluent DSL for data transformation +- Immutable transformations (original data remains unchanged) +- Support for deeply nested data structures +- Efficient handling of large collections +- Flexible and extensible transformation blocks +- Type-safe transformations with error handling + +### Documentation +- Comprehensive README with examples +- Best practices and troubleshooting guide +- Common use cases documentation +- API documentation +- Performance considerations + +### Development +- Ruby 3.1.0 or higher required +- RSpec for testing +- RuboCop for code style enforcement +- Base64 dependency for encoding/decoding support + +[0.1.0]: https://github.com/OkayDave/morphix/releases/tag/v0.1.0 diff --git a/Gemfile b/Gemfile index 9db4e1e..75be4d1 100644 --- a/Gemfile +++ b/Gemfile @@ -6,8 +6,7 @@ source "https://rubygems.org" gemspec gem "irb" -gem "rake", "~> 13.0" - -gem "rspec", "~> 3.0" - -gem "rubocop", "~> 1.21" +gem "rake" +gem "rspec" +gem "rubocop" +gem "rubocop-rspec" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..91e61a5 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,100 @@ +PATH + remote: . + specs: + morphix (0.1.0) + base64 (~> 0.2.0) + +GEM + remote: https://rubygems.org/ + specs: + ast (2.4.3) + base64 (0.2.0) + date (3.4.1) + diff-lcs (1.6.1) + io-console (0.8.0) + irb (1.15.1) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) + json (2.10.2) + language_server-protocol (3.17.0.4) + lint_roller (1.1.0) + parallel (1.26.3) + parser (3.3.7.3) + ast (~> 2.4.1) + racc + pp (0.6.2) + prettyprint + prettyprint (0.2.0) + prism (1.4.0) + psych (5.2.3) + date + stringio + racc (1.8.1) + rainbow (3.1.1) + rake (13.2.1) + rdoc (6.13.0) + psych (>= 4.0.0) + regexp_parser (2.10.0) + reline (0.6.0) + io-console (~> 0.5) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.3) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.2) + rubocop (1.75.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) + parallel (~> 1.10) + parser (>= 3.3.0.2) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.43.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.43.0) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-capybara (2.22.1) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-factory_bot (2.27.1) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (2.31.0) + rubocop (~> 1.40) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-rspec_rails (~> 2.28) + rubocop-rspec_rails (2.29.1) + rubocop (~> 1.61) + ruby-progressbar (1.13.0) + stringio (3.1.6) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + +PLATFORMS + ruby + x86_64-linux + +DEPENDENCIES + irb + morphix! + rake + rspec + rubocop + rubocop-rspec + +BUNDLED WITH + 2.6.6 diff --git a/README.md b/README.md index f92bade..d65d7fd 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,490 @@ # Morphix -TODO: Delete this and the text below, and describe your gem - -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/morphix`. To experiment with that code, run `bin/console` for an interactive prompt. +A concise, expressive DSL for elegantly reshaping and transforming Ruby hashes and JSON structures. ## Installation -TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org. +Add this line to your application's Gemfile: -Install the gem and add to the application's Gemfile by executing: +```ruby +gem "morphix" +``` +And then execute: ```bash -bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG +$ bundle install ``` -If bundler is not being used to manage dependencies, install the gem by executing: - +Or install it yourself as: ```bash -gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG +$ gem install morphix +``` + +## Core Features + +- **Fluent DSL**: Clean, readable chainable methods (`rename`, `map`, `reject`, `reshape`, `map_collection`) +- **Nested Transformation**: Easily define transformations inside nested hashes or collections +- **Reusable Transformers**: Define transformations once, reuse them multiple times +- **Functional Style**: Immutable by default—returns new objects, leaving input untouched +- **Flexible & Extensible**: Easy to add custom transformations via Ruby blocks +- **Error Handling**: Robust error handling for edge cases and invalid data +- **Performance Optimized**: Efficiently handles large and complex data structures + +## Common Use Cases + +### API Response Normalization + +```ruby +transformer = Morphix::Transformer.new do + rename :user_full_name, to: :name + map :created_at do |timestamp| + Time.parse(timestamp) + end + reshape :address do + rename :postal_code, to: :postcode + map :coordinates do |coords| + { lat: coords[:lat].to_f, lng: coords[:lng].to_f } + end + end +end + +# Use it to normalize API responses +response = api_client.get_user(123) +normalized_data = transformer.apply(response) +``` + +### Data Migration + +```ruby +transformer = Morphix::Transformer.new do + map_collection :records do + rename :legacy_id, to: :id + map :status do |status| + case status + when "ACTIVE" then "active" + when "INACTIVE" then "inactive" + else "unknown" + end + end + reshape :metadata do + map :created_at, &:to_s + map :updated_at, &:to_s + end + end +end + +# Transform legacy data format to new format +legacy_data = load_legacy_records() +new_data = transformer.apply(legacy_data) +``` + +### Data Sanitization + +```ruby +transformer = Morphix::Transformer.new do + reject :password, :ssn, :credit_card + map :email do |email| + email.to_s.downcase.strip + end + map :phone do |phone| + phone.to_s.gsub(/[^0-9]/, "") + end +end + +# Sanitize user input before processing +user_data = get_user_input() +sanitized_data = transformer.apply(user_data) ``` ## Usage -TODO: Write usage instructions here +Morphix provides a simple DSL for transforming data structures. Here are some examples: -## Development +### Basic Transformations -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +#### Renaming Keys -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). +```ruby +transformer = Morphix::Transformer.new do + rename :old_name, to: :new_name +end -## Contributing +input = { old_name: "Dave" } +result = transformer.apply(input) +# => { new_name: "Dave" } +``` -Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/morphix. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/morphix/blob/master/CODE_OF_CONDUCT.md). +#### Transforming Values -## License +```ruby +transformer = Morphix::Transformer.new do + map :age, &:to_i +end + +input = { age: "40" } +result = transformer.apply(input) +# => { age: 40 } +``` + +#### Removing Keys + +```ruby +transformer = Morphix::Transformer.new do + reject :password +end + +input = { name: "Dave", password: "secret" } +result = transformer.apply(input) +# => { name: "Dave" } +``` + +### Nested Transformations + +#### Transforming Nested Hashes + +```ruby +transformer = Morphix::Transformer.new do + reshape :address do + rename :postal_code, to: :postcode + map :verified do |value| + value == "true" + end + end +end + +input = { + address: { + street: "Main St", + postal_code: "S2", + verified: "true" + } +} +result = transformer.apply(input) +# => { +# address: { +# street: "Main St", +# postcode: "S2", +# verified: true +# } +# } +``` + +#### Transforming Collections + +```ruby +transformer = Morphix::Transformer.new do + map_collection :users do + rename :username, to: :name + reject :internal_notes + end +end + +input = { + users: [ + { username: "Dave", internal_notes: "VIP" }, + { username: "Jason", internal_notes: "Banned" } + ] +} +result = transformer.apply(input) +# => { +# users: [ +# { name: "Dave" }, +# { name: "Jason" } +# ] +# } +``` + +### Complex Transformations + +#### Combining Multiple Transformations + +```ruby +transformer = Morphix::Transformer.new do + rename :full_name, to: :name + map :age, &:to_i + reject :password + reshape :address do + rename :postal_code, to: :postcode + map :coordinates do |coords| + { lat: coords[:lat].to_f, lng: coords[:lng].to_f } + end + end +end + +input = { + full_name: "Dave Cooper", + age: "40", + password: "secret", + address: { + street: "123 Main St", + postal_code: "12345", + coordinates: { lat: "40.7128", lng: "-74.0060" } + } +} +result = transformer.apply(input) +# => { +# name: "Dave Cooper", +# age: 40, +# address: { +# street: "123 Main St", +# postcode: "12345", +# coordinates: { lat: 40.7128, lng: -74.0060 } +# } +# } +``` -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). +### Advanced Features -## Code of Conduct +#### Conditional Transformations + +```ruby +transformer = Morphix::Transformer.new do + map :status do |status| + case status + when "active" then 1 + when "pending" then 0 + when "deleted" then -1 + else nil + end + end +end + +input = { status: "active" } +result = transformer.apply(input) +# => { status: 1 } +``` + +#### Complex Data Type Conversions + +```ruby +transformer = Morphix::Transformer.new do + map :dates do |dates| + dates.transform_values { |v| Time.strptime(v, "%Y-%m-%d %H:%M:%S") } + end + map :numbers do |nums| + nums.transform_values(&:to_f) + end + map :flags do |flags| + flags.transform_values { |v| v.to_s.downcase == "true" } + end +end + +input = { + dates: { created: "2024-03-27 10:30:00", updated: "2024-03-28 15:45:00" }, + numbers: { latitude: "40.7128", longitude: "-74.0060" }, + flags: { active: "True", deleted: "FALSE", archived: "true" } +} +result = transformer.apply(input) +# => { +# dates: { +# created: #