Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +0 -12
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +0 -30
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  6. data/.github/workflows/custom_ci.yml +0 -66
  7. data/.github/workflows/docsite.yml +0 -34
  8. data/.github/workflows/sync_configs.yml +0 -34
  9. data/.gitignore +0 -16
  10. data/.rspec +0 -4
  11. data/.rubocop.yml +0 -95
  12. data/CHANGELOG.md +8 -0
  13. data/CODE_OF_CONDUCT.md +0 -13
  14. data/CONTRIBUTING.md +0 -29
  15. data/Gemfile +0 -19
  16. data/LICENSE +1 -1
  17. data/README.md +1 -1
  18. data/Rakefile +0 -6
  19. data/docsite/source/built-in-transformations.html.md +0 -47
  20. data/docsite/source/index.html.md +0 -15
  21. data/docsite/source/transformation-objects.html.md +0 -32
  22. data/docsite/source/using-standalone-functions.html.md +0 -82
  23. data/dry-transformer.gemspec +17 -10
  24. data/lib/dry/transformer/hash.rb +2 -1
  25. data/lib/dry/transformer/version.rb +1 -1
  26. data/spec/spec_helper.rb +0 -31
  27. data/spec/unit/array/combine_spec.rb +0 -224
  28. data/spec/unit/array_transformations_spec.rb +0 -233
  29. data/spec/unit/class_transformations_spec.rb +0 -50
  30. data/spec/unit/coercions_spec.rb +0 -132
  31. data/spec/unit/conditional_spec.rb +0 -48
  32. data/spec/unit/function_not_found_error_spec.rb +0 -12
  33. data/spec/unit/function_spec.rb +0 -193
  34. data/spec/unit/hash_transformations_spec.rb +0 -490
  35. data/spec/unit/proc_transformations_spec.rb +0 -20
  36. data/spec/unit/recursion_spec.rb +0 -145
  37. data/spec/unit/registry_spec.rb +0 -202
  38. data/spec/unit/store_spec.rb +0 -198
  39. data/spec/unit/transformer/class_interface_spec.rb +0 -350
  40. data/spec/unit/transformer/dsl_spec.rb +0 -15
  41. data/spec/unit/transformer/instance_methods_spec.rb +0 -25
  42. metadata +10 -56
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
---
2
2
SHA256:
3
- metadata.gz: 3b479f91512fd97581875e395c171f70031cd6316e7e546f634285a4db46c183
4
- data.tar.gz: b6b5dcc266532598a8b656d5a0597809ea90de917b2f624e95886ddadfa1b378
3
+ metadata.gz: '07286bcabed176ae3f17868f5e838decc2ba0c192828b6e69da92ec18f8713e0'
4
+ data.tar.gz: 3c5b577d2fc463182fd9fb28a333b594622c4afa61b276183f3247ce757c55e8
5
5
SHA512:
6
- metadata.gz: 1e97fff09478334ab2117f61c5133cc2ab9ea11f53ab465dbe766f6b8239e10dc9e7b030b52a33ecd368b57aac71f5b8cb6f05b0e9726e38a705d759696f98c0
7
- data.tar.gz: 46927ebbf2cc102a4527a0369e6675613301f78f4be6838bc981667b464e515878a8f3fcf470229fb32d8ff3b8cf00dd903cbf3ed20aa35b6cdc0881afe70999
6
+ metadata.gz: e100d0e8782d778c4abf0c6f805bfbcafd1d86ce5876a898706114fa658ed3c3045345470adc6bd44a750b995a92d0033d95fadbe0e565bca5fccd52d7b4a993
7
+ data.tar.gz: 41ea28575f7d9691f8e36e14920b4a65718738086a96ebc5b73cee2f3108d8884a99f6da0b933c17b1cf270a314a93fbb39c4b30eb4c51cc0736d690b22be662
data/.codeclimate.yml DELETED
@@ -1,12 +0,0 @@
1
- # this file is managed by dry-rb/devtools project
2
-
3
- version: "2"
4
-
5
- exclude_patterns:
6
- - "benchmarks/"
7
- - "examples/"
8
- - "spec/"
9
-
10
- plugins:
11
- rubocop:
12
- enabled: true
data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md DELETED
@@ -1,10 +0,0 @@
1
- ---
2
- name: "⚠️ Please don't ask for support via issues"
3
- about: See CONTRIBUTING.md for more information
4
- title: ''
5
- labels: ''
6
- assignees: ''
7
-
8
- ---
9
-
10
-
data/.github/ISSUE_TEMPLATE/---bug-report.md DELETED
@@ -1,30 +0,0 @@
1
- ---
2
- name: "\U0001F41B Bug report"
3
- about: See CONTRIBUTING.md for more information
4
- title: ''
5
- labels: bug
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Before you submit this: WE ONLY ACCEPT BUG REPORTS AND FEATURE REQUESTS**
11
-
12
- For more information see `CONTRIBUTING.md`.
13
-
14
- **Describe the bug**
15
-
16
- A clear and concise description of what the bug is.
17
-
18
- **To Reproduce**
19
-
20
- Provide detailed steps to reproduce, an executable script would be best.
21
-
22
- **Expected behavior**
23
-
24
- A clear and concise description of what you expected to happen.
25
-
26
- **Your environment**
27
-
28
- - Affects my production application: **YES/NO**
29
- - Ruby version: ...
30
- - OS: ...
data/.github/ISSUE_TEMPLATE/---feature-request.md DELETED
@@ -1,18 +0,0 @@
1
- ---
2
- name: "\U0001F6E0 Feature request"
3
- about: See CONTRIBUTING.md for more information
4
- title: ''
5
- labels: feature
6
- assignees: ''
7
-
8
- ---
9
-
10
- Summary of what the feature is supposed to do.
11
-
12
- ## Examples
13
-
14
- Code examples showing how the feature could be used.
15
-
16
- ## Resources
17
-
18
- Additional information, like a link to the discussion forum thread where the feature was discussed etc.
data/.github/workflows/custom_ci.yml DELETED
@@ -1,66 +0,0 @@
1
- # this file is managed by dry-rb/devtools project
2
-
3
- name: ci
4
-
5
- on:
6
- push:
7
- paths:
8
- - .github/workflows/custom_ci.yml
9
- - lib/**
10
- - spec/**
11
- - Gemfile
12
- - "*.gemspec"
13
-
14
- jobs:
15
- tests-mri:
16
- runs-on: ubuntu-latest
17
- strategy:
18
- fail-fast: false
19
- matrix:
20
- ruby: ["2.6.x", "2.5.x", "2.4.x"]
21
- include:
22
- - ruby: "2.6.x"
23
- coverage: "true"
24
- steps:
25
- - uses: actions/checkout@v1
26
- - name: Set up Ruby
27
- uses: actions/setup-ruby@v1
28
- with:
29
- ruby-version: ${{matrix.ruby}}
30
- - name: Download test reporter
31
- if: "matrix.coverage == 'true'"
32
- run: |
33
- mkdir -p tmp/
34
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./tmp/cc-test-reporter
35
- chmod +x ./tmp/cc-test-reporter
36
- ./tmp/cc-test-reporter before-build
37
- - name: Bundle install
38
- run: |
39
- gem install bundler
40
- bundle install --jobs 4 --retry 3 --without tools docs benchmarks
41
- - name: Run all tests
42
- env:
43
- COVERAGE: ${{matrix.coverage}}
44
- CODACY_PROJECT_TOKEN: ${{secrets.CODACY_PROJECT_TOKEN}}
45
- CODACY_RUN_LOCAL: true
46
- run: bundle exec rake
47
- tests-others:
48
- runs-on: ubuntu-latest
49
- strategy:
50
- fail-fast: false
51
- matrix:
52
- image: ["jruby:9.2.8", "ruby:rc"]
53
- container:
54
- image: ${{matrix.image}}
55
- steps:
56
- - uses: actions/checkout@v1
57
- - name: Install git
58
- run: |
59
- apt-get update
60
- apt-get install -y --no-install-recommends git
61
- - name: Bundle install
62
- run: |
63
- gem install bundler
64
- bundle install --jobs 4 --retry 3 --without tools docs benchmarks
65
- - name: Run all tests
66
- run: bundle exec rake
data/.github/workflows/docsite.yml DELETED
@@ -1,34 +0,0 @@
1
- # this file is managed by dry-rb/devtools project
2
-
3
- name: docsite
4
-
5
- on:
6
- push:
7
- paths:
8
- - docsite/**
9
- - .github/workflows/docsite.yml
10
- branches:
11
- - master
12
- - release-**
13
- tags:
14
-
15
- jobs:
16
- update-docs:
17
- runs-on: ubuntu-latest
18
- steps:
19
- - uses: actions/checkout@v1
20
- - name: Set up Ruby
21
- uses: actions/setup-ruby@v1
22
- with:
23
- ruby-version: "2.6.x"
24
- - name: Install dependencies
25
- run: |
26
- gem install bundler
27
- bundle install --jobs 4 --retry 3 --without benchmarks sql
28
- - name: Symlink ossy
29
- run: mkdir -p bin && ln -sf "$(bundle show ossy)/bin/ossy" bin/ossy
30
- - name: Trigger dry-rb.org deploy
31
- env:
32
- GITHUB_LOGIN: dry-bot
33
- GITHUB_TOKEN: ${{ secrets.GH_PAT }}
34
- run: bin/ossy github workflow dry-rb/dry-rb.org ci
data/.github/workflows/sync_configs.yml DELETED
@@ -1,34 +0,0 @@
1
- # this file is managed by dry-rb/devtools project
2
-
3
- name: sync_configs
4
-
5
- on:
6
- repository_dispatch:
7
-
8
- jobs:
9
- sync-configs:
10
- runs-on: ubuntu-latest
11
- if: github.event.action == 'sync_configs'
12
- steps:
13
- - uses: actions/checkout@v1
14
- - name: Update configuration files from devtools
15
- env:
16
- GITHUB_LOGIN: dry-bot
17
- GITHUB_TOKEN: ${{ secrets.GH_PAT }}
18
- run: |
19
- git clone https://github.com/dry-rb/devtools.git tmp/devtools
20
-
21
- if [ -f ".github/workflows/custom_ci.yml" ]; then
22
- rsync -av --exclude '.github/workflows/ci.yml' tmp/devtools/shared/ . ;
23
- else
24
- rsync -av tmp/devtools/shared/ . ;
25
- fi
26
-
27
- git config --local user.email "dry-bot@dry-rb.org"
28
- git config --local user.name "dry-bot"
29
- git add -A
30
- git commit -m "[devtools] config sync" || echo "nothing changed"
31
- - name: Push changes
32
- uses: ad-m/github-push-action@master
33
- with:
34
- github_token: ${{ secrets.GH_PAT }}
data/.gitignore DELETED
@@ -1,16 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /vendor/
5
- /_yardoc/
6
- /coverage/
7
- /doc/
8
- /pkg/
9
- /spec/reports/
10
- /tmp/
11
- *.bundle
12
- *.so
13
- *.o
14
- *.a
15
- mkmf.log
16
-
data/.rspec DELETED
@@ -1,4 +0,0 @@
1
- --color
2
- --require spec_helper
3
- --order random
4
-
data/.rubocop.yml DELETED
@@ -1,95 +0,0 @@
1
- # this file is managed by dry-rb/devtools project
2
-
3
- AllCops:
4
- TargetRubyVersion: 2.4
5
-
6
- Style/EachWithObject:
7
- Enabled: false
8
-
9
- Style/StringLiterals:
10
- Enabled: true
11
- EnforcedStyle: single_quotes
12
-
13
- Style/Alias:
14
- Enabled: false
15
-
16
- Style/LambdaCall:
17
- Enabled: false
18
-
19
- Style/StabbyLambdaParentheses:
20
- Enabled: false
21
-
22
- Style/FormatString:
23
- Enabled: false
24
-
25
- Style/Documentation:
26
- Enabled: false
27
-
28
- Layout/SpaceInLambdaLiteral:
29
- Enabled: false
30
-
31
- Layout/MultilineMethodCallIndentation:
32
- Enabled: true
33
- EnforcedStyle: indented
34
-
35
- Metrics/LineLength:
36
- Max: 100
37
-
38
- Metrics/MethodLength:
39
- Max: 22
40
-
41
- Metrics/ClassLength:
42
- Max: 150
43
-
44
- Metrics/AbcSize:
45
- Max: 20
46
-
47
- Metrics/BlockLength:
48
- Enabled: false
49
-
50
- Metrics/CyclomaticComplexity:
51
- Enabled: true
52
- Max: 10
53
-
54
- Lint/BooleanSymbol:
55
- Enabled: false
56
-
57
- Style/AccessModifierDeclarations:
58
- Enabled: false
59
-
60
- Style/BlockDelimiters:
61
- Enabled: false
62
-
63
- Layout/IndentFirstArrayElement:
64
- EnforcedStyle: consistent
65
-
66
- Style/ClassAndModuleChildren:
67
- Exclude:
68
- - "spec/**/*_spec.rb"
69
-
70
- Lint/HandleExceptions:
71
- Exclude:
72
- - "spec/spec_helper.rb"
73
-
74
- Naming/FileName:
75
- Exclude:
76
- - "lib/dry-*.rb"
77
-
78
- Style/SymbolArray:
79
- Exclude:
80
- - "spec/**/*_spec.rb"
81
-
82
- Style/ConditionalAssignment:
83
- Enabled: false
84
-
85
- Naming/MethodName:
86
- Enabled: false
87
-
88
- Style/AsciiComments:
89
- Enabled: false
90
-
91
- Style/DateTime:
92
- Enabled: false
93
-
94
- Style/IfUnlessModifier:
95
- Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 2020-01-14
2
+
3
+ ## Fixed
4
+
5
+ Fixed Dry::Transformer::HashTransformations.unwrap when hash contains root key (@AMHOL)
6
+
7
+ [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-transaction/compare/v0.1.0...v0.1.1)
8
+
1
9
# v0.1.0 2019-12-28
2
10
3
11
Initial port of the [transproc](https://github.com/solnic/transproc) gem.
data/CODE_OF_CONDUCT.md DELETED
@@ -1,13 +0,0 @@
1
- # Contributor Code of Conduct
2
-
3
- As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
-
5
- We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
-
7
- Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
-
9
- Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
-
11
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
-
13
- This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.4.0, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct](https://www.contributor-covenant.org/version/1/4/code-of-conduct)
data/CONTRIBUTING.md DELETED
@@ -1,29 +0,0 @@
1
- # Issue Guidelines
2
-
3
- ## Reporting bugs
4
-
5
- If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
-
7
- ## Reporting feature requests
8
-
9
- Report a feature request **only after discussing it first on [discourse.dry-rb.org](https://discourse.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
-
11
- ## Reporting questions, support requests, ideas, concerns etc.
12
-
13
- **PLEASE DON'T** - use [discourse.dry-rb.org](http://discourse.dry-rb.org) instead.
14
-
15
- # Pull Request Guidelines
16
-
17
- A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
-
19
- Other requirements:
20
-
21
- 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
- 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
- 3) Add API documentation if it's a new feature
24
- 4) Update API documentation if it changes an existing feature
25
- 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
-
27
- # Asking for help
28
-
29
- If these guidelines aren't helpful, and you're stuck, please post a message on [discourse.dry-rb.org](https://discourse.dry-rb.org) or join [our chat](https://dry-rb.zulipchat.com).
data/Gemfile DELETED
@@ -1,19 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gemspec
4
-
5
- gem 'rake'
6
- gem 'rspec', '~> 3.8'
7
- gem 'dry-equalizer', '~> 0.2'
8
-
9
- platform :mri do
10
- gem 'codacy-coverage', require: false
11
- gem 'simplecov', require: false
12
- end
13
-
14
- group :tools do
15
- gem 'pry'
16
- gem 'byebug', platform: :mri
17
- gem 'benchmark-ips'
18
- gem 'ossy', git: 'https://github.com/solnic/ossy.git', branch: 'master', platform: :mri
19
- end
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
The MIT License (MIT)
2
2
3
- Copyright (c) 2015-2019 dry-rb team
3
+ Copyright (c) 2015-2020 dry-rb team
4
4
5
5
Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
20
20
## Supported Ruby versions
21
21
22
- This library officially supports following Ruby versions:
22
+ This library officially supports the following Ruby versions:
23
23
24
24
* MRI >= `2.4`
25
25
* jruby >= `9.2`
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require 'bundler/gem_tasks'
2
- require 'rspec/core/rake_task'
3
-
4
- task default: :spec
5
-
6
- RSpec::Core::RakeTask.new(:spec)
data/docsite/source/built-in-transformations.html.md DELETED
@@ -1,47 +0,0 @@
1
- ---
2
- title: Built-in transformation
3
- layout: gem-single
4
- name: dry-transformer
5
- ---
6
-
7
- `dry-transformer` comes with a lot of built-in functions. They come in the form of modules with class methods, which you can import into a registry:
8
-
9
- * [Coercions](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/Coercions)
10
- * [Array transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/ArrayTransformations)
11
- * [Hash transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/HashTransformations)
12
- * [Class transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/ClassTransformations)
13
- * [Proc transformations](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/ProcTransformations)
14
- * [Conditional](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/Conditional)
15
- * [Recursion](https://www.rubydoc.info/gems/dry-transformer/Dry/Transformer/Recursion)
16
-
17
- You can import everything with:
18
-
19
- ```ruby
20
- module T
21
- extend Dry::Transformer::Registry
22
-
23
- import Dry::Transformer::Coercions
24
- import Dry::Transformer::ArrayTransformations
25
- import Dry::Transformer::HashTransformations
26
- import Dry::Transformer::ClassTransformations
27
- import Dry::Transformer::ProcTransformations
28
- import Dry::Transformer::Conditional
29
- import Dry::Transformer::Recursion
30
- end
31
-
32
- T[:to_string].(:abc) # => 'abc'
33
- ```
34
-
35
- Or import selectively with:
36
-
37
- ```ruby
38
- module T
39
- extend Dry::Transformer::Registry
40
-
41
- import :to_string, from: Dry::Transformer::Coercions, as: :stringify
42
- end
43
-
44
- T[:stringify].(:abc) # => 'abc'
45
- T[:to_string].(:abc)
46
- # => Dry::Transformer::FunctionNotFoundError: No registered function T[:to_string]
47
- ```
data/docsite/source/index.html.md DELETED
@@ -1,15 +0,0 @@
1
- ---
2
- title: Introduction
3
- description: Data transformation toolkit
4
- layout: gem-single
5
- type: gem
6
- name: dry-transformer
7
- sections:
8
- - transformation-objects
9
- - built-in-transformations
10
- - using-standalone-functions
11
- ---
12
-
13
- dry-transformer is a library that allows you to compose procs into a functional pipeline using left-to-right function composition.
14
-
15
- The approach came from Functional Programming, where simple functions are composed into more complex functions in order to transform some data. It works like `|>` in Elixir or `>>` in F#. dry-transformer provides a mechanism to define and compose transformations, along with a number of built-in transformations.
data/docsite/source/transformation-objects.html.md DELETED
@@ -1,32 +0,0 @@
1
- ---
2
- title: Transformation objects
3
- name: dry-transformer
4
- layout: gem-single
5
- ---
6
-
7
- You can define transformation classes using the DSL which converts every method call to its corresponding transformation, and composes these transformations into a transformation pipeline. Here's a simple example where the default registry is used:
8
-
9
- ```ruby
10
- class MyMapper < Dry::Transformer[Dry::Transformer::Registry]
11
- define! do
12
- map_array do
13
- symbolize_keys
14
- rename_keys user_name: :name
15
- nest :address, [:city, :street, :zipcode]
16
- end
17
- end
18
- end
19
-
20
- mapper = MyMapper.new
21
-
22
- mapper.(
23
- [
24
- { 'user_name' => 'Jane',
25
- 'city' => 'NYC',
26
- 'street' => 'Street 1',
27
- 'zipcode' => '123'
28
- }
29
- ]
30
- )
31
- # => [{:name=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]
32
- ```
data/docsite/source/using-standalone-functions.html.md DELETED
@@ -1,82 +0,0 @@
1
- ---
2
- title: Using standalone functions
3
- name: dry-transformer
4
- layout: gem-single
5
- ---
6
-
7
- You can use `dry-transformer` and its function registry feature stand-alone, without the need to define transformation classes. To do so, simply define a module and extend it with the registry API:
8
-
9
- ``` ruby
10
- require 'json'
11
- require 'dry/transformer/all'
12
-
13
- # create your own local registry for transformation functions
14
- module Functions
15
- extend Dry::Transformer::Registry
16
- end
17
-
18
- # import necessary functions from other transprocs...
19
- module Functions
20
- # import all singleton methods from a module/class
21
- import Dry::Transformer::HashTransformations
22
- import Dry::Transformer::ArrayTransformations
23
- end
24
-
25
- # ...or from any external library
26
- require 'dry-inflector'
27
-
28
- Inflector = Dry::Inflector.new
29
-
30
- module Functions
31
- # import only necessary singleton methods from a module/class
32
- # and rename them locally
33
- import :camelize, from: Inflector, as: :camel_case
34
- end
35
-
36
- def t(*args)
37
- Functions[*args]
38
- end
39
-
40
- # use imported transformation
41
- transformation = t(:camel_case)
42
-
43
- transformation.call 'i_am_a_camel'
44
- # => "IAmACamel"
45
-
46
- transformation = t(:map_array, (
47
- t(:symbolize_keys).>> t(:rename_keys, user_name: :user)
48
- )).>> t(:wrap, :address, [:city, :street, :zipcode])
49
-
50
- transformation.call(
51
- [
52
- { 'user_name' => 'Jane',
53
- 'city' => 'NYC',
54
- 'street' => 'Street 1',
55
- 'zipcode' => '123' }
56
- ]
57
- )
58
- # => [{:user=>"Jane", :address=>{:city=>"NYC", :street=>"Street 1", :zipcode=>"123"}}]
59
-
60
- # define your own composable transformation easily
61
- transformation = t(-> v { JSON.dump(v) })
62
-
63
- transformation.call(name: 'Jane')
64
- # => "{\"name\":\"Jane\"}"
65
-
66
- # ...or add it to registered functions via singleton method of the registry
67
- module Functions
68
- # ...
69
-
70
- def self.load_json(v)
71
- JSON.load(v)
72
- end
73
- end
74
-
75
- # ...or add it to registered functions via .register method
76
- Functions.register(:load_json) { |v| JSON.load(v) }
77
-
78
- transformation = t(:load_json) >> t(:map_array, t(:symbolize_keys))
79
-
80
- transformation.call('[{"name":"Jane"}]')
81
- # => [{ :name => "Jane" }]
82
- ```
data/dry-transformer.gemspec CHANGED
@@ -1,4 +1,5 @@
1
1
# frozen_string_literal: true
2
+ # this file is managed by dry-rb/devtools project
2
3
3
4
lib = File.expand_path('lib', __dir__)
4
5
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
@@ -6,17 +7,23 @@ require 'dry/transformer/version'
6
7
7
8
Gem::Specification.new do |spec|
8
9
spec.name = 'dry-transformer'
9
- spec.version = Dry::Transformer::VERSION.dup
10
- spec.authors = ['Piotr Solnica']
10
+ spec.authors = ["Piotr Solnica"]
11
+ spec.email = ["piotr.solnica@gmail.com"]
11
- spec.email = ['piotr.solnica@gmail.com']
12
- spec.summary = 'Data transformation toolkit'
13
- spec.description = spec.summary
14
- spec.homepage = 'https://dry-rb.org/gems/dry-transformer/'
15
12
spec.license = 'MIT'
13
+ spec.version = Dry::Transformer::VERSION.dup
16
14
17
- spec.files = `git ls-files -z`.split("\x0")
18
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.summary = "Data transformation toolkit"
16
+ spec.description = spec.summary
17
+ spec.homepage = 'https://dry-rb.org/gems/dry-transformer'
18
+ spec.files = Dir['CHANGELOG.md', 'LICENSE', 'README.md', 'dry-transformer.gemspec', 'lib/**/*']
20
19
spec.require_paths = ['lib']
21
- spec.required_ruby_version = '>= 2.3.0'
20
+
21
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/dry-rb/dry-transformer/blob/master/CHANGELOG.md'
23
+ spec.metadata['source_code_uri'] = 'https://github.com/dry-rb/dry-transformer'
24
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/dry-rb/dry-transformer/issues'
25
+
26
+ spec.required_ruby_version = '>= 2.4.0'
27
+
28
+ # to update dependencies edit project.yml
22
29
end
data/lib/dry/transformer/hash.rb CHANGED
@@ -310,9 +310,10 @@ module Dry
310
310
keys = nested_hash.keys
311
311
keys &= selected if selected
312
312
new_keys = prefix ? keys.map(&add_prefix) : keys
313
+ nested_contains_root_key = nested_hash.key?(root)
313
314
314
315
hash.update(Hash[new_keys.zip(keys.map { |key| nested_hash.delete(key) })])
315
- hash.delete(root) if nested_hash.empty?
316
+ hash.delete(root) if nested_hash.empty? && !nested_contains_root_key
316
317
end
317
318
end
318
319
data/lib/dry/transformer/version.rb CHANGED
@@ -2,6 +2,6 @@
2
2
3
3
module Dry
4
4
module Transformer
5
- VERSION = '0.1.0'
5
+ VERSION = '0.1.1'
6
6
end
7
7
end
data/spec/spec_helper.rb DELETED
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- if ENV['COVERAGE'] == 'true'
4
- require 'codacy-coverage'
5
- Codacy::Reporter.start
6
- end
7
-
8
- begin
9
- require 'byebug'
10
- rescue LoadError;end
11
-
12
- require 'dry/transformer/all'
13
-
14
- root = Pathname(__FILE__).dirname
15
- Dir[root.join('support/*.rb').to_s].each { |f| require f }
16
-
17
- # Namespace holding all objects created during specs
18
- module Test
19
- def self.remove_constants
20
- constants.each(&method(:remove_const))
21
- end
22
- end
23
-
24
- RSpec.configure do |config|
25
- config.after do
26
- Test.remove_constants
27
- end
28
-
29
- config.disable_monkey_patching!
30
- config.warnings = true
31
- end
data/spec/unit/array/combine_spec.rb DELETED
@@ -1,224 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'forwardable'
4
-
5
- RSpec.describe Dry::Transformer::ArrayTransformations do
6
- describe '.combine' do
7
- subject(:result) { described_class.t(:combine, mappings)[input] }
8
-
9
- let(:input) { [[]] }
10
- let(:mappings) { [] }
11
-
12
- it { is_expected.to be_a(Array) }
13
-
14
- it { is_expected.to eq([]) }
15
-
16
- context 'without groups' do
17
- let(:input) do
18
- [
19
- [
20
- {name: 'Jane', email: 'jane@doe.org'}.freeze,
21
- {name: 'Joe', email: 'joe@doe.org'}.freeze
22
- ].freeze
23
- ].freeze
24
- end
25
-
26
- it { is_expected.to eq input.first }
27
- end
28
-
29
- context 'with one group' do
30
- let(:input) do
31
- [
32
- [
33
- {name: 'Jane', email: 'jane@doe.org'}.freeze,
34
- {name: 'Joe', email: 'joe@doe.org'}.freeze
35
- ].freeze,
36
- [
37
- [
38
- {user: 'Jane', title: 'One'}.freeze,
39
- {user: 'Jane', title: 'Two'}.freeze,
40
- {user: 'Joe', title: 'Three'}.freeze
41
- ]
42
- ]
43
- ].freeze
44
- end
45
- let(:mappings) { [[:tasks, {name: :user}]] }
46
-
47
- it 'merges hashes from arrays using provided join keys' do
48
- output = [
49
- {name: 'Jane', email: 'jane@doe.org', tasks: [
50
- {user: 'Jane', title: 'One'},
51
- {user: 'Jane', title: 'Two'}
52
- ]},
53
- {name: 'Joe', email: 'joe@doe.org', tasks: [
54
- {user: 'Joe', title: 'Three'}
55
- ]}
56
- ]
57
- is_expected.to eql(output)
58
- end
59
- end
60
-
61
- context 'with empty nodes' do
62
- let(:input) do
63
- [
64
- [{name: 'Jane', email: 'jane@doe.org'}.freeze].freeze,
65
- [
66
- []
67
- ]
68
- ].freeze
69
- end
70
-
71
- let(:mappings) { [[:tasks, {name: :user}]] }
72
-
73
- it { is_expected.to eq([{name: 'Jane', email: 'jane@doe.org', tasks: []}]) }
74
- end
75
-
76
- context 'with double mapping' do
77
- let(:input) do
78
- [
79
- [
80
- {name: 'Jane', email: 'jane@doe.org'}.freeze
81
- ].freeze,
82
- [
83
- [
84
- {user: 'Jane', user_email: 'jane@doe.org', title: 'One'}.freeze,
85
- {user: 'Jane', user_email: '', title: 'Two'}.freeze
86
- ].freeze
87
- ].freeze
88
- ].freeze
89
- end
90
-
91
- let(:mappings) { [[:tasks, {name: :user, email: :user_email}]] }
92
-
93
- it 'searches by two keys simultaneously' do
94
- output = [
95
- {name: 'Jane', email: 'jane@doe.org', tasks: [
96
- {user: 'Jane', user_email: 'jane@doe.org', title: 'One'}
97
- ]}
98
- ]
99
- is_expected.to eql(output)
100
- end
101
- end
102
-
103
- context 'with non-array argument' do
104
- let(:input) do
105
- 123
106
- end
107
-
108
- let(:mappings) { [[:page, {page_id: :id}]] }
109
-
110
- it { is_expected.to eq(123) }
111
- end
112
-
113
- context 'with empty nested array' do
114
- let(:input) do
115
- [
116
- [],
117
- [
118
- []
119
- ]
120
- ]
121
- end
122
-
123
- let(:mappings) { [[:menu_items, {id: :menu_id}, [[:page, {page_id: :id}]]]] }
124
-
125
- it 'does not crash' do
126
- expect { result }.not_to raise_error
127
- end
128
- end
129
-
130
- context 'with enumerable input' do
131
- let(:my_enumerator) do
132
- Class.new do
133
- include Enumerable
134
- extend Forwardable
135
-
136
- def_delegator :@array, :each
137
-
138
- def initialize(array)
139
- @array = array
140
- end
141
- end
142
- end
143
-
144
- let(:input) do
145
- [
146
- my_enumerator.new([
147
- {name: 'Jane', email: 'jane@doe.org'}.freeze,
148
- {name: 'Joe', email: 'joe@doe.org'}.freeze
149
- ].freeze),
150
- my_enumerator.new([
151
- my_enumerator.new([
152
- {user: 'Jane', title: 'One'}.freeze,
153
- {user: 'Jane', title: 'Two'}.freeze,
154
- {user: 'Joe', title: 'Three'}.freeze
155
- ].freeze)
156
- ].freeze)
157
- ].freeze
158
- end
159
- let(:mappings) { [[:tasks, {name: :user}]] }
160
-
161
- it 'supports enumerables as well' do
162
- output = [
163
- {name: 'Jane', email: 'jane@doe.org', tasks: [
164
- {user: 'Jane', title: 'One'},
165
- {user: 'Jane', title: 'Two'}
166
- ]},
167
- {name: 'Joe', email: 'joe@doe.org', tasks: [
168
- {user: 'Joe', title: 'Three'}
169
- ]}
170
- ]
171
- is_expected.to eql(output)
172
- end
173
- end
174
-
175
- describe 'integration test' do
176
- let(:input) do
177
- [
178
- [
179
- {name: 'Jane', email: 'jane@doe.org'},
180
- {name: 'Joe', email: 'joe@doe.org'}
181
- ],
182
- [
183
- [
184
- # user tasks
185
- [
186
- {user: 'Jane', title: 'One'},
187
- {user: 'Jane', title: 'Two'},
188
- {user: 'Joe', title: 'Three'}
189
- ],
190
- [
191
- # task tags
192
- [
193
- {task: 'One', tag: 'red'},
194
- {task: 'Three', tag: 'blue'}
195
- ]
196
- ]
197
- ]
198
- ]
199
- ]
200
- end
201
-
202
- let(:mappings) { [[:tasks, {name: :user}, [[:tags, title: :task]]]] }
203
-
204
- it 'merges hashes from arrays using provided join keys' do
205
- output = [
206
- {name: 'Jane', email: 'jane@doe.org', tasks: [
207
- {user: 'Jane', title: 'One', tags: [{task: 'One', tag: 'red'}]},
208
- {user: 'Jane', title: 'Two', tags: []}
209
- ]},
210
- {
211
- name: 'Joe', email: 'joe@doe.org', tasks: [
212
- {
213
- user: 'Joe', title: 'Three', tags: [
214
- {task: 'Three', tag: 'blue'}
215
- ]
216
- }
217
- ]
218
- }
219
- ]
220
- is_expected.to eql(output)
221
- end
222
- end
223
- end
224
- end
data/spec/unit/array_transformations_spec.rb DELETED
@@ -1,233 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::ArrayTransformations do
4
- let(:hashes) { Dry::Transformer::HashTransformations }
5
-
6
- describe '.extract_key' do
7
- it 'extracts values by key from all hashes' do
8
- extract_key = described_class.t(:extract_key, 'name')
9
-
10
- input = [
11
- { 'name' => 'Alice', 'role' => 'sender' },
12
- { 'name' => 'Bob', 'role' => 'receiver' },
13
- { 'role' => 'listener' }
14
- ].freeze
15
-
16
- output = ['Alice', 'Bob', nil]
17
-
18
- expect(extract_key[input]).to eql(output)
19
- end
20
- end
21
-
22
- it { expect(described_class).not_to be_contain(:extract_key!) }
23
-
24
- describe '.insert_key' do
25
- it 'wraps values to tuples with given key' do
26
- insert_key = described_class.t(:insert_key, 'name')
27
-
28
- input = ['Alice', 'Bob', nil].freeze
29
-
30
- output = [
31
- { 'name' => 'Alice' },
32
- { 'name' => 'Bob' },
33
- { 'name' => nil }
34
- ]
35
-
36
- expect(insert_key[input]).to eql(output)
37
- end
38
- end
39
-
40
- it { expect(described_class).not_to be_contain(:insert_key!) }
41
-
42
- describe '.add_keys' do
43
- it 'returns a new array with missed keys added to tuples' do
44
- add_keys = described_class.t(:add_keys, [:foo, :bar, :baz])
45
-
46
- input = [{ foo: 'bar' }, { bar: 'baz' }].freeze
47
-
48
- output = [
49
- { foo: 'bar', bar: nil, baz: nil },
50
- { foo: nil, bar: 'baz', baz: nil }
51
- ]
52
-
53
- expect(add_keys[input]).to eql(output)
54
- end
55
- end
56
-
57
- it { expect(described_class).not_to be_contain(:add_keys!) }
58
-
59
- describe '.map_array' do
60
- it 'applies funtions to all values' do
61
- map = described_class.t(:map_array, hashes[:symbolize_keys])
62
-
63
- input = [
64
- { 'name' => 'Jane', 'title' => 'One' }.freeze,
65
- { 'name' => 'Jane', 'title' => 'Two' }.freeze
66
- ].freeze
67
-
68
- output = [
69
- { name: 'Jane', title: 'One' },
70
- { name: 'Jane', title: 'Two' }
71
- ]
72
-
73
- expect(map[input]).to eql(output)
74
- end
75
-
76
- it 'handles huge arrays' do
77
- map = described_class.t(:map_array, hashes[:symbolize_keys])
78
-
79
- input = Array.new(138_706) { |i| { 'key' => i } }
80
-
81
- expect { map[input] }.to_not raise_error
82
- end
83
-
84
- it 'handles flat value arrays' do
85
- map = described_class.t(:map_array, :upcase.to_proc)
86
-
87
- expect(map['foo']).to eql(%w(FOO))
88
- end
89
- end
90
-
91
- it { expect(described_class).not_to be_contain(:map_array!) }
92
-
93
- describe '.wrap' do
94
- it 'returns a new array with wrapped hashes' do
95
- wrap = described_class.t(:wrap, :task, [:title])
96
-
97
- input = [{ name: 'Jane', title: 'One' }]
98
- output = [{ name: 'Jane', task: { title: 'One' } }]
99
-
100
- expect(wrap[input]).to eql(output)
101
- end
102
-
103
- it 'returns a array new with deeply wrapped hashes' do
104
- wrap =
105
- described_class.t(
106
- :map_array,
107
- hashes[:nest, :user, [:name, :title]] +
108
- hashes[:map_value, :user, hashes[:nest, :task, [:title]]]
109
- )
110
-
111
- input = [{ name: 'Jane', title: 'One' }]
112
- output = [{ user: { name: 'Jane', task: { title: 'One' } } }]
113
-
114
- expect(wrap[input]).to eql(output)
115
- end
116
-
117
- it 'adds data to the existing tuples' do
118
- wrap = described_class.t(:wrap, :task, [:title])
119
-
120
- input = [{ name: 'Jane', task: { priority: 1 }, title: 'One' }]
121
- output = [{ name: 'Jane', task: { priority: 1, title: 'One' } }]
122
-
123
- expect(wrap[input]).to eql(output)
124
- end
125
- end
126
-
127
- describe '.group' do
128
- subject(:group) { described_class.t(:group, :tasks, [:title]) }
129
-
130
- it 'returns a new array with grouped hashes' do
131
- input = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
132
- output = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
133
-
134
- expect(group[input]).to eql(output)
135
- end
136
-
137
- it 'updates the existing group' do
138
- input = [
139
- {
140
- name: 'Jane',
141
- title: 'One',
142
- tasks: [{ type: 'one' }, { type: 'two' }]
143
- },
144
- {
145
- name: 'Jane',
146
- title: 'Two',
147
- tasks: [{ type: 'one' }, { type: 'two' }]
148
- }
149
- ]
150
- output = [
151
- {
152
- name: 'Jane',
153
- tasks: [
154
- { title: 'One', type: 'one' },
155
- { title: 'One', type: 'two' },
156
- { title: 'Two', type: 'one' },
157
- { title: 'Two', type: 'two' }
158
- ]
159
- }
160
- ]
161
-
162
- expect(group[input]).to eql(output)
163
- end
164
-
165
- it 'ingnores old values except for array of tuples' do
166
- input = [
167
- { name: 'Jane', title: 'One', tasks: [{ priority: 1 }, :wrong] },
168
- { name: 'Jane', title: 'Two', tasks: :wrong }
169
- ]
170
- output = [
171
- {
172
- name: 'Jane',
173
- tasks: [{ title: 'One', priority: 1 }, { title: 'Two' }]
174
- }
175
- ]
176
-
177
- expect(group[input]).to eql(output)
178
- end
179
- end
180
-
181
- describe '.ungroup' do
182
- subject(:ungroup) { described_class.t(:ungroup, :tasks, [:title]) }
183
-
184
- it 'returns a new array with ungrouped hashes' do
185
- input = [{ name: 'Jane', tasks: [{ title: 'One' }, { title: 'Two' }] }]
186
- output = [{ name: 'Jane', title: 'One' }, { name: 'Jane', title: 'Two' }]
187
-
188
- expect(ungroup[input]).to eql(output)
189
- end
190
-
191
- it 'returns an input with empty array removed' do
192
- input = [{ name: 'Jane', tasks: [] }]
193
- output = [{ name: 'Jane' }]
194
-
195
- expect(ungroup[input]).to eql(output)
196
- end
197
-
198
- it 'returns an input when a key is absent' do
199
- input = [{ name: 'Jane' }]
200
- output = [{ name: 'Jane' }]
201
-
202
- expect(ungroup[input]).to eql(output)
203
- end
204
-
205
- it 'ungroups array partially' do
206
- input = [
207
- {
208
- name: 'Jane',
209
- tasks: [
210
- { title: 'One', type: 'one' },
211
- { title: 'One', type: 'two' },
212
- { title: 'Two', type: 'one' },
213
- { title: 'Two', type: 'two' }
214
- ]
215
- }
216
- ]
217
- output = [
218
- {
219
- name: 'Jane',
220
- title: 'One',
221
- tasks: [{ type: 'one' }, { type: 'two' }]
222
- },
223
- {
224
- name: 'Jane',
225
- title: 'Two',
226
- tasks: [{ type: 'one' }, { type: 'two' }]
227
- }
228
- ]
229
-
230
- expect(ungroup[input]).to eql(output)
231
- end
232
- end
233
- end
data/spec/unit/class_transformations_spec.rb DELETED
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dry/equalizer'
4
-
5
- RSpec.describe Dry::Transformer::ClassTransformations do
6
- describe '.constructor_inject' do
7
- let(:klass) do
8
- Struct.new(:name, :age) { include Dry::Equalizer.new(:name, :age) }
9
- end
10
-
11
- it 'returns a new object initialized with the given arguments' do
12
- constructor_inject = described_class.t(:constructor_inject, klass)
13
-
14
- input = ['Jane', 25]
15
- output = klass.new(*input)
16
- result = constructor_inject[*input]
17
-
18
- expect(result).to eql(output)
19
- expect(result).to be_instance_of(klass)
20
- end
21
- end
22
-
23
- describe '.set_ivars' do
24
- let(:klass) do
25
- Class.new do
26
- include Dry::Equalizer.new(:name, :age)
27
-
28
- attr_reader :name, :age, :test
29
-
30
- def initialize(name:, age:)
31
- @name = name
32
- @age = age
33
- @test = true
34
- end
35
- end
36
- end
37
-
38
- it 'allocates a new object and sets instance variables from hash key/value pairs' do
39
- set_ivars = described_class.t(:set_ivars, klass)
40
-
41
- input = { name: 'Jane', age: 25 }
42
- output = klass.new(input)
43
- result = set_ivars[input]
44
-
45
- expect(result).to eql(output)
46
- expect(result.test).to be(nil)
47
- expect(result).to be_instance_of(klass)
48
- end
49
- end
50
- end
data/spec/unit/coercions_spec.rb DELETED
@@ -1,132 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::Coercions do
4
- describe '.identity' do
5
- let(:fn) { described_class.t(:identity) }
6
-
7
- it 'returns the original value' do
8
- expect(fn[:foo]).to eql :foo
9
- end
10
-
11
- it 'returns nil by default' do
12
- expect(fn[]).to eql nil
13
- end
14
- end
15
-
16
- describe '.to_string' do
17
- it 'turns integer into a string' do
18
- expect(described_class.t(:to_string)[1]).to eql('1')
19
- end
20
- end
21
-
22
- describe '.to_symbol' do
23
- it 'turns string into a symbol' do
24
- expect(described_class.t(:to_symbol)['test']).to eql(:test)
25
- end
26
-
27
- it 'turns non-string into a symbol' do
28
- expect(described_class.t(:to_symbol)[1]).to eql(:'1')
29
- end
30
- end
31
-
32
- describe '.to_integer' do
33
- it 'turns string into an integer' do
34
- expect(described_class.t(:to_integer)['1']).to eql(1)
35
- end
36
- end
37
-
38
- describe '.to_float' do
39
- it 'turns string into a float' do
40
- expect(described_class.t(:to_float)['1']).to eql(1.0)
41
- end
42
-
43
- it 'turns integer into a float' do
44
- expect(described_class.t(:to_float)[1]).to eql(1.0)
45
- end
46
- end
47
-
48
- describe '.to_decimal' do
49
- it 'turns string into a decimal' do
50
- expect(described_class.t(:to_decimal)['1.251']).to eql(BigDecimal('1.251'))
51
- end
52
-
53
- it 'turns float into a decimal' do
54
- expect(described_class.t(:to_decimal)[1.251]).to eql(BigDecimal('1.251'))
55
- end
56
-
57
- it 'turns integer into a decimal' do
58
- expect(described_class.t(:to_decimal)[1]).to eql(BigDecimal('1.0'))
59
- end
60
- end
61
-
62
- describe '.to_date' do
63
- it 'turns string into a date' do
64
- date = Date.new(1983, 11, 18)
65
- expect(described_class.t(:to_date)['18th, November 1983']).to eql(date)
66
- end
67
- end
68
-
69
- describe '.to_time' do
70
- it 'turns string into a time object' do
71
- time = Time.new(2012, 1, 23, 11, 7, 7)
72
- expect(described_class.t(:to_time)['2012-01-23 11:07:07']).to eql(time)
73
- end
74
- end
75
-
76
- describe '.to_datetime' do
77
- it 'turns string into a date' do
78
- datetime = DateTime.new(2012, 1, 23, 11, 7, 7)
79
- expect(described_class.t(:to_datetime)['2012-01-23 11:07:07']).to eql(datetime)
80
- end
81
- end
82
-
83
- describe '.to_boolean' do
84
- subject(:coercer) { described_class.t(:to_boolean) }
85
-
86
- Dry::Transformer::Coercions::TRUE_VALUES.each do |value|
87
- it "turns #{value.inspect} to true" do
88
- expect(coercer[value]).to be(true)
89
- end
90
- end
91
-
92
- Dry::Transformer::Coercions::FALSE_VALUES.each do |value|
93
- it "turns #{value.inspect} to false" do
94
- expect(coercer[value]).to be(false)
95
- end
96
- end
97
- end
98
-
99
- describe '.to_tuples' do
100
- subject(:to_tuples) { described_class.t(:to_tuples) }
101
-
102
- context 'non-array' do
103
- let(:input) { :foo }
104
-
105
- it 'returns an array with one blank tuple' do
106
- output = [{}]
107
-
108
- expect(to_tuples[input]).to eql(output)
109
- end
110
- end
111
-
112
- context 'empty array' do
113
- let(:input) { [] }
114
-
115
- it 'returns an array with one blank tuple' do
116
- output = [{}]
117
-
118
- expect(to_tuples[input]).to eql(output)
119
- end
120
- end
121
-
122
- context 'array of tuples' do
123
- let(:input) { [:foo, { bar: :BAZ }, :qux] }
124
-
125
- it 'returns an array with tuples only' do
126
- output = [{ bar: :BAZ }]
127
-
128
- expect(to_tuples[input]).to eql(output)
129
- end
130
- end
131
- end
132
- end
data/spec/unit/conditional_spec.rb DELETED
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::Conditional do
4
- describe '.not' do
5
- let(:fn) { described_class.t(:not, -> value { value.is_a? String }) }
6
- subject { fn[input] }
7
-
8
- context 'when predicate returns truthy value' do
9
- let(:input) { 'foo' }
10
- let(:output) { false }
11
-
12
- it 'applies the first transformation' do
13
- expect(subject).to eql output
14
- end
15
- end
16
-
17
- context 'when predicate returns falsey value' do
18
- let(:input) { :foo }
19
- let(:output) { true }
20
-
21
- it 'applies the first transformation' do
22
- expect(subject).to eql output
23
- end
24
- end
25
- end
26
-
27
- describe '.guard' do
28
- let(:fn) { described_class.t(:guard, condition, operation) }
29
- let(:condition) { ->(value) { value.is_a?(::String) } }
30
- let(:operation) { Dry::Transformer::Coercions.t(:to_integer) }
31
-
32
- context 'when predicate returns truthy value' do
33
- it 'applies the transformation and returns the result' do
34
- input = '2'
35
-
36
- expect(fn[input]).to eql(2)
37
- end
38
- end
39
-
40
- context 'when predicate returns falsey value' do
41
- it 'returns the original value' do
42
- input = { 'foo' => 'bar' }
43
-
44
- expect(fn[input]).to eql('foo' => 'bar')
45
- end
46
- end
47
- end
48
- end
data/spec/unit/function_not_found_error_spec.rb DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::FunctionNotFoundError do
4
- it 'complains that the function not registered' do
5
- Foo = Module.new { extend Dry::Transformer::Registry }
6
-
7
- expect { Foo[:foo] }.to raise_error do |error|
8
- expect(error).to be_kind_of described_class
9
- expect(error.message['function Foo[:foo]']).not_to be_nil
10
- end
11
- end
12
- end
data/spec/unit/function_spec.rb DELETED
@@ -1,193 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::Function do
4
- let(:container) do
5
- Module.new do
6
- extend Dry::Transformer::Registry
7
-
8
- import Dry::Transformer::HashTransformations
9
- end
10
- end
11
-
12
- describe '#name' do
13
- let(:block) { proc { |v| v } }
14
-
15
- it 'returns the name of the module function' do
16
- expect(container[:symbolize_keys].name).to eql :symbolize_keys
17
- end
18
-
19
- it 'returns the explicitly assigned name' do
20
- expect(described_class.new(block, name: :identity).name).to eql :identity
21
- end
22
-
23
- it 'returns the unnamed closure' do
24
- expect(described_class.new(block).name).to eql block
25
- end
26
- end
27
-
28
- describe '#>>' do
29
- it 'composes named functions' do
30
- f1 = container[:symbolize_keys]
31
- f2 = container[:rename_keys, user_name: :name]
32
-
33
- f3 = f1 >> f2
34
-
35
- expect(f3.to_ast).to eql(
36
- [
37
- :symbolize_keys, [],
38
- [
39
- :rename_keys, [user_name: :name]
40
- ]
41
- ]
42
- )
43
-
44
- expect(f3['user_name' => 'Jane']).to eql(name: 'Jane')
45
-
46
- f4 = f3 >> container[:nest, :details, [:name]]
47
-
48
- expect(f4.to_ast).to eql(
49
- [
50
- :symbolize_keys, [],
51
- [
52
- :rename_keys, [user_name: :name]
53
- ],
54
- [
55
- :nest, [:details, [:name]]
56
- ]
57
- ]
58
- )
59
-
60
- expect(f4['user_name' => 'Jane']).to eql(details: { name: 'Jane' })
61
- end
62
-
63
- it 'composes anonymous functions' do
64
- f1 = container[->(v, m) { v * m }, 2]
65
- f2 = container[:to_s.to_proc]
66
-
67
- f3 = f1 >> f2
68
-
69
- expect(f3.to_ast).to eql(
70
- [
71
- f1.fn, [2],
72
- [
73
- f2.fn, []
74
- ]
75
- ]
76
- )
77
- end
78
-
79
- it 'plays well with registered compositions' do
80
- container.register(:user_names, container[:symbolize_keys] + container[:rename_keys, user_name: :name])
81
- f = container[:user_names]
82
-
83
- expect(f['user_name' => 'Jane']).to eql(name: 'Jane')
84
- expect(f.to_ast).to eql(
85
- [
86
- :symbolize_keys, [],
87
- [
88
- :rename_keys, [user_name: :name]
89
- ]
90
- ]
91
- )
92
- end
93
-
94
- it 'plays well with registered functions' do
95
- container.register(:to_string, Dry::Transformer::Coercions.t(:to_string))
96
- fn = container.t(:to_string)
97
-
98
- expect(fn[:ok]).to eql('ok')
99
- expect(fn.to_ast).to eql([:to_string, []])
100
- end
101
-
102
- it 'plays well with functions as arguments' do
103
- container.register(:map_array, Dry::Transformer::ArrayTransformations.t(:map_array))
104
- container.register(:to_symbol, Dry::Transformer::Coercions.t(:to_symbol))
105
- fn = container.t(:map_array, container.t(:to_symbol))
106
-
107
- expect(fn.call(%w(a b c))).to eql([:a, :b, :c])
108
- expect(fn.to_ast).to eql(
109
- [
110
- :map_array, [
111
- [:to_symbol, []]
112
- ]
113
- ]
114
- )
115
- end
116
- end
117
-
118
- describe '#==' do
119
- let(:fns) do
120
- Module.new do
121
- extend Dry::Transformer::Registry
122
- import :wrap, from: Dry::Transformer::ArrayTransformations
123
- end
124
- end
125
-
126
- it 'returns true when the other is equal' do
127
- left = fns[:wrap, :user, [:name, :email]]
128
- right = fns[:wrap, :user, [:name, :email]]
129
-
130
- expect(left == right).to be(true)
131
- end
132
-
133
- it 'returns false when the other is not a fn' do
134
- left = fns[:wrap, :user, [:name, :email]]
135
- right = 'boo!'
136
-
137
- expect(left == right).to be(false)
138
- end
139
- end
140
-
141
- describe '#to_proc' do
142
- shared_examples :providing_a_proc do
143
- let(:fn) { described_class.new(source) }
144
- subject { fn.to_proc }
145
-
146
- it 'returns a proc' do
147
- expect(subject).to be_instance_of Proc
148
- end
149
-
150
- it 'works fine' do
151
- expect(subject.call :foo).to eql('foo')
152
- end
153
- end
154
-
155
- context 'from a method' do
156
- let(:source) do
157
- mod = Module.new do
158
- def self.get(x)
159
- x.to_s
160
- end
161
- end
162
- mod.method(:get)
163
- end
164
- it_behaves_like :providing_a_proc
165
- end
166
-
167
- context 'from a proc' do
168
- let(:source) { -> value { value.to_s } }
169
- it_behaves_like :providing_a_proc
170
- end
171
-
172
- context 'from a transproc' do
173
- let(:source) { Dry::Transformer::Function.new -> value { value.to_s } }
174
- it_behaves_like :providing_a_proc
175
-
176
- it 'can be applied to collection' do
177
- expect([:foo, :bar].map(&source)).to eql(%w(foo bar))
178
- end
179
- end
180
-
181
- context 'with curried args' do
182
- let(:source) { -> i, j { [i, j].join(' ') } }
183
-
184
- it 'works fine' do
185
- fn = described_class.new(source, args: ['world'])
186
-
187
- result = fn.to_proc.call('hello')
188
-
189
- expect(result).to eql('hello world')
190
- end
191
- end
192
- end
193
- end
data/spec/unit/hash_transformations_spec.rb DELETED
@@ -1,490 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::HashTransformations do
4
- describe '.map_keys' do
5
- it 'returns a new hash with given proc applied to keys' do
6
- map_keys = described_class.t(:map_keys, ->(key) { key.strip })
7
-
8
- input = { ' foo ' => 'bar' }.freeze
9
- output = { 'foo' => 'bar' }
10
-
11
- expect(map_keys[input]).to eql(output)
12
- end
13
- end
14
-
15
- it { expect(described_class).not_to be_contain(:map_keys!) }
16
-
17
- describe '.symbolize_keys' do
18
- it 'returns a new hash with symbolized keys' do
19
- symbolize_keys = described_class.t(:symbolize_keys)
20
-
21
- input = { 1 => 'bar' }.freeze
22
- output = { '1'.to_sym => 'bar' }
23
-
24
- expect(symbolize_keys[input]).to eql(output)
25
- end
26
- end
27
-
28
- describe '.deep_symbolize_keys' do
29
- it 'returns a new hash with symbolized keys' do
30
- symbolize_keys = described_class.t(:deep_symbolize_keys)
31
-
32
- input = { 'foo' => 'bar', 'baz' => [{ 'one' => 1 }, 'two'] }
33
- output = { foo: 'bar', baz: [{ one: 1 }, 'two'] }
34
-
35
- expect(symbolize_keys[input]).to eql(output)
36
- expect(input).to eql('foo' => 'bar', 'baz' => [{ 'one' => 1 }, 'two'])
37
- end
38
- end
39
-
40
- it { expect(described_class).not_to be_contain(:symbolize_keys!) }
41
-
42
- describe '.stringify_keys' do
43
- it 'returns a new hash with stringified keys' do
44
- stringify_keys = described_class.t(:stringify_keys)
45
-
46
- input = { foo: 'bar' }.freeze
47
- output = { 'foo' => 'bar' }
48
-
49
- expect(stringify_keys[input]).to eql(output)
50
- end
51
- end
52
-
53
- describe '.deep_stringify_keys' do
54
- it 'returns a new hash with symbolized keys' do
55
- stringify_keys = described_class.t(:deep_stringify_keys)
56
-
57
- input = { foo: 'bar', baz: [{ one: 1 }, 'two'] }
58
- output = { 'foo' => 'bar', 'baz' => [{ 'one' => 1 }, 'two'] }
59
-
60
- expect(stringify_keys[input]).to eql(output)
61
- end
62
- end
63
-
64
- it { expect(described_class).not_to be_contain(:stringify_keys!) }
65
-
66
- describe '.map_values' do
67
- it 'returns a new hash with given proc applied to values' do
68
- map_values = described_class.t(:map_values, ->(value) { value.strip })
69
-
70
- input = { 'foo' => ' bar ' }.freeze
71
- output = { 'foo' => 'bar' }
72
-
73
- expect(map_values[input]).to eql(output)
74
- end
75
- end
76
-
77
- it { expect(described_class).not_to be_contain(:map_values!) }
78
-
79
- describe '.rename_keys' do
80
- it 'returns a new hash with applied functions' do
81
- map = described_class.t(:rename_keys, 'foo' => :foo)
82
-
83
- input = { 'foo' => 'bar', :bar => 'baz' }.freeze
84
- output = { foo: 'bar', bar: 'baz' }
85
-
86
- expect(map[input]).to eql(output)
87
- end
88
-
89
- it 'only renames keys and never creates new ones' do
90
- map = described_class.t(:rename_keys, 'foo' => :foo, 'bar' => :bar)
91
-
92
- input = { 'bar' => 'baz' }.freeze
93
- output = { bar: 'baz' }
94
-
95
- expect(map[input]).to eql(output)
96
- end
97
- end
98
-
99
- it { expect(described_class).not_to be_contain(:rename_keys!) }
100
-
101
- describe '.copy_keys' do
102
- context 'with single destination key' do
103
- it 'returns a new hash with applied functions' do
104
- map = described_class.t(:copy_keys, 'foo' => :foo)
105
-
106
- input = { 'foo' => 'bar', :bar => 'baz' }.freeze
107
- output = { 'foo' => 'bar', foo: 'bar', bar: 'baz' }
108
-
109
- expect(map[input]).to eql(output)
110
- end
111
- end
112
-
113
- context 'with multiple destination keys' do
114
- it 'returns a new hash with applied functions' do
115
- map = described_class.t(:copy_keys, 'foo' => [:foo, :baz])
116
-
117
- input = { 'foo' => 'bar', :bar => 'baz' }.freeze
118
- output = { 'foo' => 'bar', foo: 'bar', baz: 'bar', bar: 'baz' }
119
-
120
- expect(map[input]).to eql(output)
121
- end
122
- end
123
- end
124
-
125
- it { expect(described_class).not_to be_contain(:copy_keys!) }
126
-
127
- describe '.map_value' do
128
- it 'applies function to value under specified key' do
129
- transformation =
130
- described_class.t(:map_value, :user, described_class.t(:symbolize_keys))
131
-
132
- input = { user: { 'name' => 'Jane' }.freeze }.freeze
133
- output = { user: { name: 'Jane' } }
134
-
135
- expect(transformation[input]).to eql(output)
136
- end
137
- end
138
-
139
- it { expect(described_class).not_to be_contain(:map_value!) }
140
-
141
- describe '.nest' do
142
- it 'returns new hash with keys nested under a new key' do
143
- nest = described_class.t(:nest, :baz, ['foo'])
144
-
145
- input = { 'foo' => 'bar' }.freeze
146
- output = { baz: { 'foo' => 'bar' } }
147
-
148
- expect(nest[input]).to eql(output)
149
- end
150
-
151
- it 'returns new hash with keys nested under the existing key' do
152
- nest = described_class.t(:nest, :baz, ['two'])
153
-
154
- input = {
155
- 'foo' => 'bar',
156
- baz: { 'one' => nil }.freeze,
157
- 'two' => false
158
- }.freeze
159
-
160
- output = { 'foo' => 'bar', baz: { 'one' => nil, 'two' => false } }
161
-
162
- expect(nest[input]).to eql(output)
163
- end
164
-
165
- it 'rewrites the existing key if its value is not a hash' do
166
- nest = described_class.t(:nest, :baz, ['two'])
167
-
168
- input = { 'foo' => 'bar', baz: 'one', 'two' => false }.freeze
169
- output = { 'foo' => 'bar', baz: { 'two' => false } }
170
-
171
- expect(nest[input]).to eql(output)
172
- end
173
-
174
- it 'returns new hash with an empty hash under a new key when nest-keys are missing' do
175
- nest = described_class.t(:nest, :baz, ['foo'])
176
-
177
- input = { 'bar' => 'foo' }.freeze
178
- output = { 'bar' => 'foo', baz: {} }
179
-
180
- expect(nest[input]).to eql(output)
181
- end
182
- end
183
-
184
- it { expect(described_class).not_to be_contain(:nest!) }
185
-
186
- describe '.unwrap' do
187
- it 'returns new hash with nested keys lifted to the root' do
188
- unwrap = described_class.t(:unwrap, 'wrapped', %w(one))
189
-
190
- input = {
191
- 'foo' => 'bar',
192
- 'wrapped' => { 'one' => nil, 'two' => false }.freeze
193
- }.freeze
194
-
195
- output = { 'foo' => 'bar', 'one' => nil, 'wrapped' => { 'two' => false } }
196
-
197
- expect(unwrap[input]).to eql(output)
198
- end
199
-
200
- it 'lifts all keys if none are passed' do
201
- unwrap = described_class.t(:unwrap, 'wrapped')
202
-
203
- input = { 'wrapped' => { 'one' => nil, 'two' => false }.freeze }.freeze
204
- output = { 'one' => nil, 'two' => false }
205
-
206
- expect(unwrap[input]).to eql(output)
207
- end
208
-
209
- it 'ignores unknown keys' do
210
- unwrap = described_class.t(:unwrap, 'wrapped', %w(one two three))
211
-
212
- input = { 'wrapped' => { 'one' => nil, 'two' => false }.freeze }.freeze
213
- output = { 'one' => nil, 'two' => false }
214
-
215
- expect(unwrap[input]).to eql(output)
216
- end
217
-
218
- it 'prefixes unwrapped keys and retains root string type if prefix option is truthy' do
219
- unwrap = described_class.t(:unwrap, 'wrapped', prefix: true)
220
-
221
- input = { 'wrapped' => { one: nil, two: false }.freeze }.freeze
222
- output = { 'wrapped_one' => nil, 'wrapped_two' => false }
223
-
224
- expect(unwrap[input]).to eql(output)
225
- end
226
-
227
- it 'prefixes unwrapped keys and retains root type if prefix option is truthy' do
228
- unwrap = described_class.t(:unwrap, :wrapped, prefix: true)
229
-
230
- input = { wrapped: { 'one' => nil, 'two' => false }.freeze }.freeze
231
- output = { wrapped_one: nil, wrapped_two: false }
232
-
233
- expect(unwrap[input]).to eql(output)
234
- end
235
- end
236
-
237
- it { expect(described_class).not_to be_contain(:unwrap!) }
238
-
239
- describe 'nested transform' do
240
- it 'applies functions to nested hashes' do
241
- symbolize_keys = described_class.t(:symbolize_keys)
242
- map_user_key = described_class.t(:map_value, :user, symbolize_keys)
243
-
244
- transformation = symbolize_keys >> map_user_key
245
-
246
- input = { 'user' => { 'name' => 'Jane' } }
247
- output = { user: { name: 'Jane' } }
248
-
249
- expect(transformation[input]).to eql(output)
250
- end
251
- end
252
-
253
- describe 'combining transformations' do
254
- it 'applies functions to the hash' do
255
- symbolize_keys = described_class.t(:symbolize_keys)
256
- map = described_class.t :rename_keys, user_name: :name, user_email: :email
257
-
258
- transformation = symbolize_keys >> map
259
-
260
- input = { 'user_name' => 'Jade', 'user_email' => 'jade@doe.org' }
261
- output = { name: 'Jade', email: 'jade@doe.org' }
262
-
263
- result = transformation[input]
264
-
265
- expect(result).to eql(output)
266
- end
267
- end
268
-
269
- describe '.reject_keys' do
270
- it 'returns a new hash with rejected keys' do
271
- reject_keys = described_class.t(:reject_keys, [:name, :age])
272
-
273
- input = { name: 'Jane', email: 'jane@doe.org', age: 21 }.freeze
274
- output = { email: 'jane@doe.org' }
275
-
276
- expect(reject_keys[input]).to eql(output)
277
- end
278
- end
279
-
280
- it { expect(described_class).not_to be_contain(:reject_keys!) }
281
-
282
- describe '.accept_keys' do
283
- it 'returns a new hash with rejected keys' do
284
- accept_keys = described_class.t(:accept_keys, [:age])
285
-
286
- input = { name: 'Jane', email: 'jane@doe.org', age: 21 }.freeze
287
- output = { age: 21 }
288
-
289
- expect(accept_keys[input]).to eql(output)
290
- end
291
- end
292
-
293
- it { expect(described_class).not_to be_contain(:accept_keys!) }
294
-
295
- describe '.fold' do
296
- let(:input) do
297
- {
298
- name: 'Jane',
299
- tasks: [
300
- { title: 'be nice', priority: 1 }.freeze,
301
- { title: 'sleep well' }.freeze
302
- ].freeze
303
- }.freeze
304
- end
305
-
306
- it 'returns a new hash with folded values' do
307
- fold = described_class.t(:fold, :tasks, :title)
308
-
309
- output = { name: 'Jane', tasks: ['be nice', 'sleep well'] }
310
-
311
- expect(fold[input]).to eql(output)
312
- end
313
-
314
- it 'uses nil if there was not such attribute' do
315
- fold = described_class.t(:fold, :tasks, :priority)
316
-
317
- output = { name: 'Jane', tasks: [1, nil] }
318
-
319
- expect(fold[input]).to eql(output)
320
- end
321
- end
322
-
323
- it { expect(described_class).not_to be_contain(:fold!) }
324
-
325
- describe '.split' do
326
- let(:input) do
327
- {
328
- name: 'Joe',
329
- tasks: [
330
- { title: 'sleep well', priority: 1 },
331
- { title: 'be nice', priority: 2 },
332
- { priority: 2 },
333
- { title: 'be cool' },
334
- {}
335
- ]
336
- }
337
- end
338
-
339
- it 'splits a tuple into array partially by given keys' do
340
- split = described_class.t(:split, :tasks, [:priority])
341
-
342
- output = [
343
- {
344
- name: 'Joe', priority: 1,
345
- tasks: [{ title: 'sleep well' }]
346
- },
347
- {
348
- name: 'Joe', priority: 2,
349
- tasks: [{ title: 'be nice' }, { title: nil }]
350
- },
351
- {
352
- name: 'Joe', priority: nil,
353
- tasks: [{ title: 'be cool' }, { title: nil }]
354
- }
355
- ]
356
-
357
- expect(split[input]).to eql output
358
- end
359
-
360
- it 'splits a tuple into array fully by all subkeys' do
361
- split = described_class.t(:split, :tasks, [:priority, :title])
362
-
363
- output = [
364
- { name: 'Joe', title: 'sleep well', priority: 1 },
365
- { name: 'Joe', title: 'be nice', priority: 2 },
366
- { name: 'Joe', title: nil, priority: 2 },
367
- { name: 'Joe', title: 'be cool', priority: nil },
368
- { name: 'Joe', title: nil, priority: nil }
369
- ]
370
-
371
- expect(split[input]).to eql output
372
- end
373
-
374
- it 'returns an array of one tuple with updated keys when there is nothing to split by' do
375
- output = [
376
- {
377
- name: 'Joe',
378
- tasks: [
379
- { title: 'sleep well', priority: 1 },
380
- { title: 'be nice', priority: 2 },
381
- { title: nil, priority: 2 },
382
- { title: 'be cool', priority: nil },
383
- { title: nil, priority: nil }
384
- ]
385
- }
386
- ]
387
-
388
- split = described_class.t(:split, :tasks, [])
389
- expect(split[input]).to eql output
390
-
391
- split = described_class.t(:split, :tasks, [:absent])
392
- expect(split[input]).to eql output
393
- end
394
-
395
- it 'returns an array of initial tuple when attribute is absent' do
396
- split = described_class.t(:split, :absent, [:priority, :title])
397
- expect(split[input]).to eql [input]
398
- end
399
-
400
- it 'ignores empty array' do
401
- input = { name: 'Joe', tasks: [] }
402
-
403
- split = described_class.t(:split, :tasks, [:title])
404
-
405
- expect(split[input]).to eql [{ name: 'Joe' }]
406
- end
407
- end
408
-
409
- describe ':eval_values' do
410
- it 'recursively evaluates values' do
411
- evaluate = described_class.t(:eval_values, 1)
412
-
413
- input = {
414
- one: 1, two: -> i { i + 1 },
415
- three: -> i { i + 2 }, four: 4,
416
- more: [{ one: -> i { i }, two: 2 }]
417
- }
418
-
419
- output = {
420
- one: 1, two: 2,
421
- three: 3, four: 4,
422
- more: [{ one: 1, two: 2 }]
423
- }
424
-
425
- expect(evaluate[input]).to eql(output)
426
- end
427
-
428
- it 'recursively evaluates values matching key names' do
429
- evaluate = described_class.t(:eval_values, 1, [:one, :two])
430
-
431
- input = {
432
- one: 1, two: -> i { i + 1 },
433
- three: -> i { i + 2 }, four: 4,
434
- array: [{ one: -> i { i }, two: 2 }],
435
- hash: { one: -> i { i } }
436
- }
437
-
438
- result = evaluate[input]
439
-
440
- expect(result[:three]).to be_a(Proc)
441
- expect(result).to include(two: 2)
442
- expect(result[:array]).to eql([{ one: 1, two: 2 }])
443
- expect(result[:hash]).to eql(one: 1)
444
- end
445
- end
446
-
447
- describe '.deep_merge' do
448
- let(:hash) {
449
- {
450
- name: 'Jane',
451
- email: 'jane@doe.org',
452
- favorites:
453
- {
454
- food: 'stroopwafel'
455
- }
456
- }
457
- }
458
-
459
- let(:update) {
460
- {
461
- email: 'jane@example.org',
462
- favorites:
463
- {
464
- color: 'orange'
465
- }
466
- }
467
- }
468
-
469
- it 'recursively merges hash values' do
470
- deep_merge = described_class.t(:deep_merge)
471
- output = {
472
- name: 'Jane',
473
- email: 'jane@example.org',
474
- favorites: { food: 'stroopwafel', color: 'orange' }
475
- }
476
-
477
- expect(deep_merge[hash, update]).to eql(output)
478
- end
479
-
480
- it 'does not alter the provided arguments' do
481
- original_hash = hash.dup
482
- original_update = update.dup
483
-
484
- described_class.t(:deep_merge)[hash, update]
485
-
486
- expect(hash).to eql(original_hash)
487
- expect(update).to eql(original_update)
488
- end
489
- end
490
- end
data/spec/unit/proc_transformations_spec.rb DELETED
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ostruct'
4
-
5
- RSpec.describe Dry::Transformer::ProcTransformations do
6
- describe '.bind' do
7
- let(:fn) { described_class.t(:bind, binding, proc) }
8
- let(:binding) { OpenStruct.new(prefix: prefix) }
9
- let(:proc) { -> v { [prefix, v].join('_') } }
10
- let(:prefix) { 'foo' }
11
- let(:input) { 'bar' }
12
- let(:output) { 'foo_bar' }
13
-
14
- subject { fn[input] }
15
-
16
- it 'binds the given proc to the specified binding' do
17
- is_expected.to eq(output)
18
- end
19
- end
20
- end
data/spec/unit/recursion_spec.rb DELETED
@@ -1,145 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::Recursion do
4
- let(:hashes) { Dry::Transformer::HashTransformations }
5
-
6
- describe '.recursion' do
7
- let(:original) do
8
- {
9
- 'foo' => 'bar',
10
- 'bar' => {
11
- 'foo' => 'bar',
12
- 'bar' => %w(foo bar baz),
13
- 'baz' => 'foo'
14
- },
15
- 'baz' => 'bar'
16
- }
17
- end
18
-
19
- let(:input) { original.dup }
20
-
21
- let(:output) do
22
- {
23
- 'foo' => 'bar',
24
- 'bar' => {
25
- 'foo' => 'bar',
26
- 'bar' => %w(foo bar)
27
- }
28
- }
29
- end
30
-
31
- context 'when function is non-destructive' do
32
- let(:map) do
33
- described_class.t(:recursion, -> enum {
34
- enum.reject { |v| v == 'baz' }
35
- })
36
- end
37
-
38
- it 'applies funtions to all items recursively' do
39
- expect(map[input]).to eql(output)
40
- expect(input).to eql(original)
41
- end
42
- end
43
-
44
- context 'when function is destructive' do
45
- let(:map) do
46
- described_class.t(:recursion, -> enum {
47
- enum.reject! { |v| v == 'baz' }
48
- })
49
- end
50
-
51
- it 'applies funtions to all items recursively and destructively' do
52
- expect(map[input]).to eql(output)
53
- expect(input).to eql(output)
54
- end
55
- end
56
- end
57
-
58
- describe '.array_recursion' do
59
- let(:original) do
60
- [
61
- 'foo',
62
- 'bar',
63
- nil,
64
- [
65
- 'foo',
66
- 'bar',
67
- nil,
68
- [
69
- 'foo',
70
- 'bar',
71
- nil
72
- ]
73
- ]
74
- ]
75
- end
76
-
77
- let(:input) { original.dup }
78
-
79
- let(:output) do
80
- [
81
- 'foo',
82
- 'bar',
83
- [
84
- 'foo',
85
- 'bar',
86
- %w(foo bar)
87
- ]
88
- ]
89
- end
90
-
91
- context 'when function is non-destructive' do
92
- let(:map) { described_class.t(:array_recursion, proc(&:compact)) }
93
-
94
- it 'applies funtions to all items recursively' do
95
- expect(map[input]).to eql(output)
96
- expect(input).to eql(original)
97
- end
98
- end
99
-
100
- context 'when function is destructive' do
101
- let(:map) { described_class.t(:array_recursion, proc(&:compact!)) }
102
-
103
- it 'applies funtions to all items recursively and destructively' do
104
- expect(map[input]).to eql(output)
105
- expect(input).to eql(output)
106
- end
107
- end
108
- end
109
-
110
- describe '.hash_recursion' do
111
- let(:input) do
112
- {
113
- 'foo' => 'bar',
114
- 'bar' => {
115
- 'foo' => 'bar',
116
- 'bar' => {
117
- 'foo' => 'bar'
118
- }.freeze
119
- }.freeze,
120
- 'baz' => 'bar'
121
- }.freeze
122
- end
123
-
124
- let(:output) do
125
- {
126
- foo: 'bar',
127
- bar: {
128
- foo: 'bar',
129
- bar: {
130
- foo: 'bar'
131
- }
132
- },
133
- baz: 'bar'
134
- }
135
- end
136
-
137
- let(:map) do
138
- described_class.t(:hash_recursion, hashes.t(:symbolize_keys))
139
- end
140
-
141
- it 'applies funtions to all values recursively' do
142
- expect(map[input]).to eql(output)
143
- end
144
- end
145
- end
data/spec/unit/registry_spec.rb DELETED
@@ -1,202 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::Registry do
4
- let(:foo) do
5
- Test::Foo = Module.new do
6
- extend Dry::Transformer::Registry
7
-
8
- def self.prefix(value, prefix)
9
- "#{prefix}_#{value}"
10
- end
11
- end
12
- end
13
-
14
- let(:bar) { Test::Bar = Module.new { extend Dry::Transformer::Registry } }
15
- let(:baz) { Test::Baz = Module.new { extend Dry::Transformer::Registry } }
16
-
17
- describe '.[]' do
18
- subject(:transproc) { foo[fn, 'baz'] }
19
-
20
- context 'from a method' do
21
- let(:fn) { :prefix }
22
-
23
- it 'builds a function from a method' do
24
- expect(transproc['qux']).to eql 'baz_qux'
25
- end
26
- end
27
-
28
- context 'from a closure' do
29
- let(:fn) { -> value, prefix { [prefix, '_', value].join } }
30
-
31
- it 'builds a function from a method' do
32
- expect(transproc['qux']).to eql 'baz_qux'
33
- end
34
- end
35
- end
36
-
37
- describe '.t' do
38
- subject(:transproc) { foo.t(:prefix, 'baz') }
39
-
40
- it 'is an alias for .[]' do
41
- expect(transproc['qux']).to eql 'baz_qux'
42
- end
43
- end
44
-
45
- describe '.contain?' do
46
- context 'with absent function' do
47
- it { expect(foo.contain?(:something)).to be false }
48
- end
49
-
50
- context 'with class method' do
51
- it { expect(foo.contain?(:prefix)).to be true }
52
- end
53
-
54
- context 'with imported methods' do
55
- before { bar.import foo }
56
-
57
- it { expect(bar.contain?(:prefix)).to be true }
58
- end
59
- end
60
-
61
- describe '.register' do
62
- it { expect(foo).not_to be_contain(:increment) }
63
-
64
- it { expect(foo).to be_contain(:prefix) }
65
-
66
- def do_register
67
- foo.register(:increment, ->(v) { v + 1 })
68
- end
69
-
70
- it 'returns self' do
71
- expect(do_register).to eq foo
72
- end
73
-
74
- it 'registers function' do
75
- do_register
76
- expect(foo).to be_contain(:increment)
77
- end
78
-
79
- it 'preserves previous functions' do
80
- do_register
81
- expect(foo).to be_contain(:prefix)
82
- end
83
-
84
- it 'makes function available' do
85
- do_register
86
- expect(foo[:increment][2]).to eq 3
87
- end
88
-
89
- it 'rejects to overwrite existing' do
90
- expect { foo.register(:prefix) {} }
91
- .to raise_error(Dry::Transformer::FunctionAlreadyRegisteredError)
92
- end
93
-
94
- it 'registers and fetches transproc function' do
95
- function = foo[:prefix, '1']
96
- foo.register(:prefix_one, function)
97
-
98
- expect(foo[:prefix_one]).to eq function
99
- end
100
-
101
- it 'allows to overwrite function arguments' do
102
- foo.register(:map_array, Dry::Transformer::ArrayTransformations.t(:map_array))
103
-
104
- fn = foo[:map_array, ->(value) { value.to_sym }]
105
-
106
- expect(fn.call(%w(a b c))).to eq([:a, :b, :c])
107
- end
108
-
109
- it 'registers and fetches composite' do
110
- composite = foo[:prefix, '1'] + foo[:prefix, '2']
111
- foo.register(:double_prefix, composite)
112
-
113
- expect(foo[:double_prefix]).to eq composite
114
- end
115
-
116
- context 'with block argument' do
117
- def do_register
118
- foo.register(:increment) { |v| v + 1 }
119
- end
120
-
121
- it 'registers function' do
122
- do_register
123
- expect(foo).to be_contain(:increment)
124
- end
125
-
126
- it 'makes function available' do
127
- do_register
128
- expect(foo[:increment][2]).to eq 3
129
- end
130
- end
131
- end
132
-
133
- describe '.import' do
134
- context 'a module' do
135
- subject(:import) { bar.import foo }
136
-
137
- it 'registers all its methods' do
138
- import
139
- expect(bar[:prefix, 'baz']['qux']).to eql 'baz_qux'
140
- end
141
-
142
- it 'returns itself' do
143
- expect(import).to eq bar
144
- end
145
- end
146
-
147
- context 'a method' do
148
- before { bar.import :prefix, from: foo }
149
-
150
- it 'registers a transproc' do
151
- expect(bar[:prefix, 'bar']['baz']).to eql 'bar_baz'
152
- end
153
- end
154
-
155
- context 'an imported method' do
156
- before do
157
- bar.import :prefix, from: foo, as: :affix
158
- baz.import :affix, from: bar
159
- end
160
-
161
- it 'registers a transproc' do
162
- expect(baz[:affix, 'bar']['baz']).to eql 'bar_baz'
163
- end
164
- end
165
-
166
- context 'a list of methods' do
167
- before { bar.import :prefix, from: foo }
168
- before { bar.import :prefix, from: foo, as: :affix }
169
- before { baz.import :prefix, :affix, from: bar }
170
-
171
- it 'registers a transproc' do
172
- expect(baz[:prefix, 'bar']['baz']).to eql 'bar_baz'
173
- expect(baz[:affix, 'bar']['baz']).to eql 'bar_baz'
174
- end
175
- end
176
-
177
- context 'a renamed method' do
178
- before { bar.import :prefix, from: foo, as: :affix }
179
-
180
- it 'registers a transproc under the new name' do
181
- expect(bar[:affix, 'bar']['baz']).to eql 'bar_baz'
182
- end
183
- end
184
-
185
- context 'an unknown method' do
186
- it 'fails' do
187
- expect { bar.import :suffix, from: foo }.to raise_error do |error|
188
- expect(error).to be_kind_of Dry::Transformer::FunctionNotFoundError
189
- expect(error.message).to include 'Foo[:suffix]'
190
- end
191
- end
192
- end
193
- end
194
-
195
- describe '.uses' do
196
- before { bar.uses foo }
197
-
198
- it 'is an alias for .import' do
199
- expect(bar[:prefix, 'baz']['qux']).to eql 'baz_qux'
200
- end
201
- end
202
- end
data/spec/unit/store_spec.rb DELETED
@@ -1,198 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Dry::Transformer::Store do
4
- let(:store) { described_class.new methods }
5
- let(:methods) { { foo: instance_double(Proc) } }
6
-
7
- describe '.new' do
8
- it 'is immutable' do
9
- expect(store).to be_frozen
10
- end
11
-
12
- it 'does not freeze the source hash' do
13
- expect { store }.not_to change { methods.frozen? }
14
- end
15
- end # describe .new
16
-
17
- describe '#methods' do
18
- it 'returns the hash from the initializer' do
19
- expect(store.methods).to eql methods
20
- end
21
-
22
- it 'returns empty hash by default' do
23
- expect(described_class.new.methods).to eql({})
24
- end
25
-
26
- it 'is immutable' do
27
- expect(store.methods).to be_frozen
28
- end
29
- end # describe #methods
30
-
31
- describe '#fetch' do
32
- it 'returns a registered proc' do
33
- expect(store.fetch(:foo)).to eql methods[:foo]
34
- end
35
-
36
- it 'does not accepts anything but symbol as key' do
37
- expect { store.fetch('foo') }.to raise_error KeyError
38
- end
39
-
40
- it 'raises KeyError if requested proc is unknown' do
41
- expect { store.fetch(:bar) }.to raise_error KeyError
42
- end
43
- end # describe #fetch
44
-
45
- describe '#contain?' do
46
- it 'returns true for registered proc' do
47
- expect(store.contain?(:foo)).to be true
48
- end
49
-
50
- it 'returns false if requested proc is unknown' do
51
- expect(store.contain?(:bar)).to be false
52
- end
53
- end # describe #fetch
54
-
55
- describe '#register' do
56
- subject { new_store }
57
- let(:new_store) { store.register(:increment, ->(v) { v + 1 }) }
58
-
59
- it { is_expected.to be_contain(:increment) }
60
-
61
- it { expect(new_store.fetch(:increment)[2]).to eq 3 }
62
-
63
- it 'preserves existing methods' do
64
- expect(new_store).to be_contain(:foo)
65
- end
66
-
67
- context 'with block argument' do
68
- let(:new_store) { store.register(:increment) { |v| v + 1 } }
69
-
70
- it 'works as well as with proc' do
71
- expect(new_store.fetch(:increment)[2]).to eq 3
72
- end
73
- end
74
- end
75
-
76
- describe '#import', :focus do
77
- before do
78
- module Bar
79
- def self.bar
80
- :bar
81
- end
82
- end
83
-
84
- module Baz
85
- def self.baz
86
- :baz
87
- end
88
- end
89
-
90
- module Qux
91
- extend Dry::Transformer::Registry
92
-
93
- import Bar
94
- import Baz
95
-
96
- def self.baz
97
- :qux_baz
98
- end
99
-
100
- def self.qux
101
- :qux
102
- end
103
- end
104
- end
105
-
106
- shared_examples :importing_method do
107
- let(:preserved) { subject.methods[:foo] }
108
- let(:imported) { subject.methods[key] }
109
-
110
- it '[preserves old methods]' do
111
- expect(preserved).to eql(methods[:foo])
112
- end
113
-
114
- it '[registers a new method]' do
115
- expect(imported).to be_kind_of Method
116
- expect(imported.call).to eql(value)
117
- end
118
- end
119
-
120
- context 'named method' do
121
- subject { store.import 'qux', from: Qux }
122
-
123
- it_behaves_like :importing_method do
124
- let(:key) { :qux }
125
- let(:value) { :qux }
126
- end
127
- end
128
-
129
- context 'named methods' do
130
- subject { store.import 'qux', 'bar', from: Qux }
131
-
132
- it_behaves_like :importing_method do
133
- let(:key) { :qux }
134
- let(:value) { :qux }
135
- end
136
-
137
- it_behaves_like :importing_method do
138
- let(:key) { :bar }
139
- let(:value) { :bar }
140
- end
141
- end
142
-
143
- context 'renamed method' do
144
- subject { store.import 'qux', from: Qux, as: 'quxx' }
145
-
146
- it_behaves_like :importing_method do
147
- let(:key) { :quxx }
148
- let(:value) { :qux }
149
- end
150
- end
151
-
152
- context 'imported proc' do
153
- subject { store.import 'bar', from: Qux, as: 'barr' }
154
-
155
- it_behaves_like :importing_method do
156
- let(:key) { :barr }
157
- let(:value) { :bar }
158
- end
159
- end
160
-
161
- context 'method that reloads imported proc' do
162
- subject { store.import 'baz', from: Qux, as: 'bazz' }
163
-
164
- it_behaves_like :importing_method do
165
- let(:key) { :bazz }
166
- let(:value) { :qux_baz }
167
- end
168
- end
169
-
170
- context 'module' do
171
- subject { store.import Qux }
172
-
173
- it_behaves_like :importing_method do
174
- let(:key) { :bar }
175
- let(:value) { :bar }
176
- end
177
-
178
- it_behaves_like :importing_method do
179
- let(:key) { :baz }
180
- let(:value) { :qux_baz }
181
- end
182
-
183
- it_behaves_like :importing_method do
184
- let(:key) { :qux }
185
- let(:value) { :qux }
186
- end
187
-
188
- it 'skips Dry::Transformer::Registry singleton methods' do
189
- pending "this fails for some reason" if RUBY_ENGINE == "jruby"
190
- expect(subject.methods.keys).to contain_exactly(:foo, :bar, :baz, :qux)
191
- end
192
- end
193
-
194
- after do
195
- %w(Bar Baz Qux).each { |name| Object.send :remove_const, name }
196
- end
197
- end # describe #import
198
- end # describe Dry::Transformer::Store
data/spec/unit/transformer/class_interface_spec.rb DELETED
@@ -1,350 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ostruct'
4
- require 'dry/equalizer'
5
-
6
- RSpec.describe Dry::Transformer do
7
- let(:container) { Module.new { extend Dry::Transformer::Registry } }
8
- let(:klass) { Dry::Transformer[container] }
9
- let(:transformer) { klass.new }
10
-
11
- describe '.import' do
12
- it 'allows importing functions into an auto-configured registry' do
13
- klass = Class.new(Dry::Transformer::Pipe) do
14
- import Dry::Transformer::ArrayTransformations
15
- import Dry::Transformer::Coercions
16
-
17
- define! do
18
- map_array(&:to_symbol)
19
- end
20
- end
21
-
22
- transformer = klass.new
23
-
24
- expect(transformer.(['foo', 'bar'])).to eql([:foo, :bar])
25
- end
26
- end
27
-
28
- describe '.new' do
29
- it 'supports arguments' do
30
- klass = Class.new(Dry::Transformer::Pipe) do
31
- import Dry::Transformer::ArrayTransformations
32
- import Dry::Transformer::Coercions
33
-
34
- define! do
35
- map_array(&:to_symbol)
36
- end
37
-
38
- def initialize(good)
39
- @good = good
40
- end
41
-
42
- def good?
43
- @good
44
- end
45
- end
46
-
47
- transformer = klass.new(true)
48
-
49
- expect(transformer).to be_good
50
- end
51
- end
52
-
53
- describe '.container' do
54
- it 'returns the configured container' do
55
- expect(klass.container).to be(container)
56
- end
57
-
58
- context 'with setter argument' do
59
- let(:container) { double(:custom_container) }
60
-
61
- it 'sets and returns the container' do
62
- klass.container(container)
63
-
64
- expect(klass.container).to be(container)
65
- end
66
- end
67
- end
68
-
69
- describe 'inheritance' do
70
- let(:container) do
71
- Module.new do
72
- extend Dry::Transformer::Registry
73
-
74
- def self.arbitrary(value, fn)
75
- fn[value]
76
- end
77
- end
78
- end
79
-
80
- let(:superclass) do
81
- Class.new(Dry::Transformer[container]) do
82
- define! do
83
- arbitrary ->(v) { v + 1 }
84
- end
85
- end
86
- end
87
-
88
- let(:subclass) do
89
- Class.new(superclass) do
90
- define! do
91
- arbitrary ->(v) { v * 2 }
92
- end
93
- end
94
- end
95
-
96
- it 'inherits container from superclass' do
97
- expect(subclass.container).to be(superclass.container)
98
- end
99
-
100
- it 'inherits transproc from superclass' do
101
- expect(superclass.new.call(2)).to be(3)
102
- expect(subclass.new.call(2)).to be(6)
103
- end
104
- end
105
-
106
- describe '.[]' do
107
- subject(:subclass) { klass[another_container] }
108
-
109
- let(:another_container) { double('Dry::Transformer') }
110
-
111
- it 'sets a container' do
112
- expect(subclass.container).to be(another_container)
113
- end
114
-
115
- it 'returns a class' do
116
- expect(subclass).to be_a(Class)
117
- end
118
-
119
- it 'creates a subclass of Transformer' do
120
- expect(subclass).to be < Dry::Transformer::Pipe
121
- end
122
-
123
- it 'does not change super class' do
124
- expect(klass.container).to be(container)
125
- end
126
-
127
- it 'does not inherit transproc' do
128
- expect(klass[container].new.transproc).to be_nil
129
- end
130
-
131
- context 'with predefined transformer' do
132
- let(:klass) do
133
- Class.new(Dry::Transformer[container]) do
</