├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .pelusa.yml ├── .rspec ├── .rubocop.yml ├── .ruby-gemset ├── .yardopts ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.devtools ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── TODO ├── benchmarks └── speed.rb ├── config ├── devtools.yml ├── flay.yml ├── flog.yml ├── mutant.yml ├── reek.yml ├── roodi.yml ├── rubocop.yml └── yardstick.yml ├── ice_nine.gemspec ├── lib ├── ice_nine.rb └── ice_nine │ ├── core_ext │ └── object.rb │ ├── freezer.rb │ ├── freezer │ ├── array.rb │ ├── false_class.rb │ ├── hash.rb │ ├── module.rb │ ├── nil_class.rb │ ├── no_freeze.rb │ ├── numeric.rb │ ├── object.rb │ ├── struct.rb │ ├── symbol.rb │ └── true_class.rb │ ├── support │ └── recursion_guard.rb │ └── version.rb └── spec ├── integration └── ice_nine │ └── class_methods │ ├── deep_freeze_bang_spec.rb │ └── deep_freeze_spec.rb ├── shared ├── array_deep_freeze.rb ├── hash_deep_freeze.rb ├── ice_nine_deep_freeze.rb ├── no_freeze_deep_freeze.rb ├── object_deep_freeze.rb └── range_deep_freeze.rb ├── spec_helper.rb ├── support └── config_alias.rb └── unit ├── ice_nine ├── class_methods │ ├── deep_freeze_bang_spec.rb │ └── deep_freeze_spec.rb ├── core_ext │ └── object │ │ ├── deep_freeze_bang_spec.rb │ │ └── deep_freeze_spec.rb ├── freezer │ ├── array │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── class_methods │ │ ├── deep_freeze_bang_spec.rb │ │ ├── deep_freeze_spec.rb │ │ └── element_reader_spec.rb │ ├── false_class │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── hash │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── module │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── nil_class │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── no_freeze │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── numeric │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── object │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── struct │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ ├── symbol │ │ └── class_methods │ │ │ └── deep_freeze_spec.rb │ └── true_class │ │ └── class_methods │ │ └── deep_freeze_spec.rb └── recursion_guard │ ├── frozen │ └── guard_spec.rb │ └── object_set │ └── guard_spec.rb └── object └── deep_freeze_spec.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: push 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | ruby-version: ['2.7', '3.0', '3.1', '3.2'] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Set up Ruby 18 | uses: ruby/setup-ruby@v1 19 | with: 20 | ruby-version: ${{ matrix.ruby-version }} 21 | bundler-cache: true # 'ruby/setup-ruby' action provides built-in caching 22 | 23 | - name: Run tests 24 | run: bundle exec rspec 25 | 26 | - name: Run mutant 27 | run: bundle exec mutant run 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## Rubinius 17 | *.rbc 18 | .rbx 19 | 20 | ## PROJECT::GENERAL 21 | *.gem 22 | coverage 23 | profiling 24 | turbulence 25 | rdoc 26 | pkg 27 | tmp 28 | doc 29 | log 30 | .yardoc 31 | measurements 32 | 33 | ## BUNDLER 34 | .bundle 35 | Gemfile.lock 36 | 37 | ## PROJECT::SPECIFIC 38 | -------------------------------------------------------------------------------- /.pelusa.yml: -------------------------------------------------------------------------------- 1 | sources: lib/**/*.rb 2 | lints: 3 | InstanceVariables: 4 | limit: 1 5 | LineRestriction: 6 | limit: 87 7 | ElseClauses: 8 | exclude: 9 | - 'IceNine::Freezer' 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | --profile 4 | --warnings 5 | --order random 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - 'Gemfile' 4 | - 'Gemfile.devtools' 5 | - 'benchmarks/**/*' 6 | - 'ice_nine.gemspec' 7 | - 'vendor/**/*' 8 | - 'Guardfile' 9 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | ice_nine 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | - README.md LICENSE 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ------------ 3 | 4 | * If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin. 5 | * Fork the project. 6 | * Make your feature addition or bug fix. 7 | * Follow this [style guide](https://github.com/dkubb/styleguide). 8 | * Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered. 9 | * Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 10 | * Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged. 11 | * Send me a pull request. Bonus points for topic branches. 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | group :development, :test do 8 | gem 'rspec', '~> 3.8', '>= 3.8.0' 9 | gem 'mutant', github: 'mbj/mutant' 10 | gem 'mutant-rspec', github: 'mbj/mutant' 11 | 12 | source 'https://oss:sxCL1o1navkPi2XnGB5WYBrhpY9iKIPL@gem.mutant.dev' do 13 | gem 'mutant-license' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile.devtools: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | group :development do 4 | gem 'rake', '~> 10.4.0' 5 | gem 'rspec', '~> 3.1.0' 6 | gem 'rspec-core', '~> 3.1.7' 7 | gem 'rspec-its', '~> 1.1.0' 8 | gem 'yard', '~> 0.8.7.6' 9 | 10 | platform :rbx do 11 | gem 'rubysl-singleton', '~> 2.0.0' 12 | end 13 | end 14 | 15 | group :yard do 16 | gem 'kramdown', '~> 1.5.0' 17 | end 18 | 19 | group :guard do 20 | gem 'guard', '~> 2.10.1' 21 | gem 'guard-bundler', '~> 2.0.0' 22 | gem 'guard-rspec', '~> 4.3.1' 23 | gem 'guard-rubocop', '~> 1.2.0' 24 | 25 | # file system change event handling 26 | gem 'listen', '~> 2.8.1' 27 | gem 'rb-fchange', '~> 0.0.6', require: false 28 | gem 'rb-fsevent', '~> 0.9.4', require: false 29 | gem 'rb-inotify', '~> 0.9.5', require: false 30 | 31 | # notification handling 32 | gem 'libnotify', '~> 0.8.4', require: false 33 | gem 'rb-notifu', '~> 0.0.4', require: false 34 | gem 'terminal-notifier-guard', '~> 1.6.4', require: false 35 | end 36 | 37 | group :metrics do 38 | gem 'coveralls', '~> 0.7.2' 39 | gem 'flay', '~> 2.5.0' 40 | gem 'flog', '~> 4.3.0' 41 | gem 'reek', '~> 1.5.0' 42 | gem 'rubocop', '~> 0.27.1' 43 | gem 'simplecov', '~> 0.9.1' 44 | gem 'yardstick', '~> 0.9.9' 45 | 46 | platforms :mri do 47 | gem 'mutant', '~> 0.6.7', git: 'https://github.com/mbj/mutant.git' 48 | gem 'mutant-rspec', '~> 0.6.7', git: 'https://github.com/mbj/mutant.git' 49 | end 50 | 51 | platforms :ruby_19, :ruby_20 do 52 | gem 'yard-spellcheck', '~> 0.1.5' 53 | end 54 | 55 | platform :rbx do 56 | gem 'json', '~> 1.8.1' 57 | gem 'racc', '~> 1.4.12' 58 | gem 'rubysl-logger', '~> 2.1.0' 59 | gem 'rubysl-open-uri', '~> 2.0.0' 60 | gem 'rubysl-prettyprint', '~> 2.0.3' 61 | end 62 | end 63 | 64 | group :benchmarks do 65 | gem 'rbench', '~> 0.2.3' 66 | end 67 | 68 | platform :jruby do 69 | group :jruby do 70 | gem 'jruby-openssl', '~> 0.9.4' 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | guard :bundler do 4 | watch('Gemfile') 5 | end 6 | 7 | # rubocop:disable LineLength 8 | guard :rspec, cli: File.read('.rspec').split.join(' '), keep_failed: false do 9 | # run all specs if configuration is modified 10 | watch('Guardfile') { 'spec' } 11 | watch('Gemfile.lock') { 'spec' } 12 | watch('spec/spec_helper.rb') { 'spec' } 13 | 14 | # run all specs if supporting files files are modified 15 | watch(%r{\Aspec/(?:lib|support|shared)/.+\.rb\z}) { 'spec' } 16 | 17 | # run unit specs if associated lib code is modified 18 | watch(/\Alib\/(.+)\.rb/) { |m| Dir["spec/unit/#{m[1]}"] } 19 | watch(%r{\Alib/(.+)/support/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}/#{m[2]}"] } 20 | watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' } 21 | 22 | # run a spec if it is modified 23 | watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z}) 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2023 Dan Kubb 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ice_nine 2 | ======== 3 | 4 | Deep freeze ruby objects 5 | 6 | [![Gem Version](https://badge.fury.io/rb/ice_nine.svg)][gem] 7 | [![Build Status](https://github.com/dkubb/ice_nine/actions/workflows/ci.yml/badge.svg)][ci] 8 | [![Code Climate](https://codeclimate.com/github/dkubb/ice_nine.png)][codeclimate] 9 | [![Inline docs](http://inch-ci.org/github/dkubb/ice_nine.svg?branch=master)][inch] 10 | 11 | [gem]: https://rubygems.org/gems/ice_nine 12 | [ci]: https://github.com/dkubb/ice_nine/actions/ 13 | [travis]: https://travis-ci.org/dkubb/ice_nine 14 | [gemnasium]: https://gemnasium.com/dkubb/ice_nine 15 | [codeclimate]: https://codeclimate.com/github/dkubb/ice_nine 16 | [inch]: http://inch-ci.org/github/dkubb/ice_nine 17 | 18 | Examples 19 | -------- 20 | 21 | ```ruby 22 | require 'ice_nine' 23 | 24 | # Deep freezes most kinds of objects 25 | hash = IceNine.deep_freeze('a' => '1') 26 | array = IceNine.deep_freeze([ 'a', 'b', 'c' ]) 27 | range = IceNine.deep_freeze('a'..'z') 28 | struct = IceNine.deep_freeze(Struct.new(:a, :b).new('a', 'b')) 29 | object = IceNine.deep_freeze(Object.new) 30 | user = IceNine.deep_freeze(User.new(name: 'dkubb')) 31 | 32 | # Faster deep freeze that skips deep-freezing frozen objects 33 | object = IceNine.deep_freeze!(Object.new) 34 | 35 | # Add core extension for Object#deep_freeze (not required by default) 36 | require 'ice_nine' 37 | require 'ice_nine/core_ext/object' 38 | 39 | object = Object.new 40 | object.deep_freeze 41 | ``` 42 | 43 | Contributing 44 | ------------ 45 | 46 | See [CONTRIBUTING.md](CONTRIBUTING.md) for details. 47 | 48 | Copyright 49 | --------- 50 | 51 | Copyright © 2012-2023 Dan Kubb. See LICENSE for details. 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | Rake.application.load_imports 4 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Add a utility method that allows "NoFreeze" subclasses to be easily added for 2 | any constant. 3 | * Refactor the existing NoFreeze subclasses to use the method. 4 | 5 | * Consider adding support for OpenStruct 6 | * should freeze the singleton class too in order to prevent no new members 7 | * being added (by new_ostruct_member) 8 | -------------------------------------------------------------------------------- /benchmarks/speed.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # encoding: utf-8 4 | 5 | # benchmark speed of deep freeze 6 | 7 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 8 | 9 | require 'rbench' 10 | require 'ice_nine' 11 | 12 | # @return [Hash] 13 | def self.nested(depth, width, array_length) 14 | hash = {} 15 | 16 | 1.upto(width) do |n| 17 | hash[n.to_s] = n.to_s 18 | end 19 | 20 | unless depth == 1 21 | hash[(width - 1).to_s] = array_length.times.map { nested(depth - 1, width, array_length) } 22 | hash[width.to_s] = nested(depth - 1, width, array_length) 23 | end 24 | 25 | hash 26 | end 27 | 28 | hash = nested(3, 5, 500) 29 | hash2 = nested(3, 5, 500) 30 | 31 | RBench.run do 32 | report('deep_freeze') { IceNine.deep_freeze(hash) } 33 | report('deep_freeze!') { IceNine.deep_freeze!(hash2) } 34 | end 35 | -------------------------------------------------------------------------------- /config/devtools.yml: -------------------------------------------------------------------------------- 1 | --- 2 | unit_test_timeout: 0.1 3 | -------------------------------------------------------------------------------- /config/flay.yml: -------------------------------------------------------------------------------- 1 | --- 2 | threshold: 8 3 | total_score: 52 4 | -------------------------------------------------------------------------------- /config/flog.yml: -------------------------------------------------------------------------------- 1 | --- 2 | threshold: 5.5 3 | -------------------------------------------------------------------------------- /config/mutant.yml: -------------------------------------------------------------------------------- 1 | --- 2 | integration: 3 | name: rspec 4 | requires: 5 | - ice_nine 6 | matcher: 7 | subjects: 8 | - IceNine* 9 | mutation: 10 | timeout: 1.0 11 | coverage_criteria: 12 | timeout: true 13 | -------------------------------------------------------------------------------- /config/reek.yml: -------------------------------------------------------------------------------- 1 | --- 2 | Attribute: 3 | enabled: true 4 | exclude: [] 5 | BooleanParameter: 6 | enabled: true 7 | exclude: [] 8 | ClassVariable: 9 | enabled: true 10 | exclude: [] 11 | ControlParameter: 12 | enabled: true 13 | exclude: [] 14 | DataClump: 15 | enabled: true 16 | exclude: [] 17 | max_copies: 2 18 | min_clump_size: 2 19 | DuplicateMethodCall: 20 | enabled: true 21 | exclude: [] 22 | max_calls: 1 23 | allow_calls: [] 24 | FeatureEnvy: 25 | enabled: true 26 | exclude: 27 | - IceNine::RecursionGuard::Frozen#guard 28 | IrresponsibleModule: 29 | enabled: true 30 | exclude: [] 31 | LongParameterList: 32 | enabled: true 33 | exclude: [] 34 | max_params: 2 35 | overrides: 36 | initialize: 37 | max_params: 3 38 | LongYieldList: 39 | enabled: true 40 | exclude: [] 41 | max_params: 2 42 | NestedIterators: 43 | enabled: true 44 | exclude: [] 45 | max_allowed_nesting: 1 46 | ignore_iterators: [] 47 | NilCheck: 48 | enabled: true 49 | exclude: [] 50 | RepeatedConditional: 51 | enabled: true 52 | exclude: [] 53 | max_ifs: 1 54 | TooManyInstanceVariables: 55 | enabled: true 56 | exclude: [] 57 | max_instance_variables: 3 58 | TooManyMethods: 59 | enabled: true 60 | exclude: [] 61 | max_methods: 10 62 | TooManyStatements: 63 | enabled: true 64 | exclude: 65 | - each 66 | max_statements: 5 67 | UncommunicativeMethodName: 68 | enabled: true 69 | exclude: [] 70 | reject: 71 | - !ruby/regexp /^[a-z]$/ 72 | - !ruby/regexp /[0-9]$/ 73 | - !ruby/regexp /[A-Z]/ 74 | accept: [] 75 | UncommunicativeModuleName: 76 | enabled: true 77 | exclude: [] 78 | reject: 79 | - !ruby/regexp /^.$/ 80 | - !ruby/regexp /[0-9]$/ 81 | accept: [] 82 | UncommunicativeParameterName: 83 | enabled: true 84 | exclude: [] 85 | reject: 86 | - !ruby/regexp /^.$/ 87 | - !ruby/regexp /[0-9]$/ 88 | - !ruby/regexp /[A-Z]/ 89 | accept: [] 90 | UncommunicativeVariableName: 91 | enabled: true 92 | exclude: [] 93 | reject: 94 | - !ruby/regexp /^.$/ 95 | - !ruby/regexp /[0-9]$/ 96 | - !ruby/regexp /[A-Z]/ 97 | accept: [] 98 | UnusedParameters: 99 | enabled: true 100 | exclude: [] 101 | UtilityFunction: 102 | enabled: true 103 | exclude: 104 | - IceNine::RecursionGuard::Frozen#guard 105 | max_helper_calls: 0 106 | -------------------------------------------------------------------------------- /config/roodi.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AbcMetricMethodCheck: { score: 1 } 3 | AssignmentInConditionalCheck: { } 4 | CaseMissingElseCheck: { } 5 | ClassLineCountCheck: { line_count: 88 } 6 | ClassNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' } 7 | ClassVariableCheck: { } 8 | CyclomaticComplexityBlockCheck: { complexity: 3 } 9 | CyclomaticComplexityMethodCheck: { complexity: 1 } 10 | EmptyRescueBodyCheck: { } 11 | ForLoopCheck: { } 12 | MethodLineCountCheck: { line_count: 1 } 13 | MethodNameCheck: { pattern: !ruby/regexp '/\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/' } 14 | ModuleLineCountCheck: { line_count: 92 } 15 | ModuleNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' } 16 | ParameterNumberCheck: { parameter_count: 0 } 17 | -------------------------------------------------------------------------------- /config/rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: ../.rubocop.yml 2 | 3 | # Avoid parameter lists longer than five parameters. 4 | ParameterLists: 5 | Max: 3 6 | CountKeywordArgs: true 7 | 8 | # Avoid more than `Max` levels of nesting. 9 | BlockNesting: 10 | Max: 3 11 | 12 | # Align with the style guide. 13 | CollectionMethods: 14 | PreferredMethods: 15 | collect: 'map' 16 | inject: 'reduce' 17 | find: 'detect' 18 | find_all: 'select' 19 | 20 | # Do not force public/protected/private keyword to be indented at the same 21 | # level as the def keyword. My personal preference is to outdent these keywords 22 | # because I think when scanning code it makes it easier to identify the 23 | # sections of code and visually separate them. When the keyword is at the same 24 | # level I think it sort of blends in with the def keywords and makes it harder 25 | # to scan the code and see where the sections are. 26 | AccessModifierIndentation: 27 | Enabled: false 28 | 29 | # Limit line length 30 | LineLength: 31 | Max: 79 32 | 33 | # Disable documentation checking until a class needs to be documented once 34 | Documentation: 35 | Enabled: false 36 | 37 | # Do not always use &&/|| instead of and/or. 38 | AndOr: 39 | Enabled: false 40 | 41 | # Do not favor modifier if/unless usage when you have a single-line body 42 | IfUnlessModifier: 43 | Enabled: false 44 | 45 | # Allow case equality operator (in limited use within the specs) 46 | CaseEquality: 47 | Enabled: false 48 | 49 | # Constants do not always have to use SCREAMING_SNAKE_CASE 50 | ConstantName: 51 | Enabled: false 52 | 53 | # Not all trivial readers/writers can be defined with attr_* methods 54 | TrivialAccessors: 55 | Enabled: false 56 | 57 | # Allow empty lines around class body 58 | EmptyLinesAroundClassBody: 59 | Enabled: false 60 | 61 | # Allow empty lines around module body 62 | EmptyLinesAroundModuleBody: 63 | Enabled: false 64 | 65 | # Allow multiple line operations to not require indentation 66 | MultilineOperationIndentation: 67 | Enabled: false 68 | 69 | # Prefer String#% over Kernel#sprintf 70 | FormatString: 71 | Enabled: false 72 | 73 | # Use square brackets for literal Array objects 74 | PercentLiteralDelimiters: 75 | PreferredDelimiters: 76 | '%': () 77 | '%i': '[]' 78 | '%q': () 79 | '%Q': () 80 | '%r': '{}' 81 | '%s': () 82 | '%w': '[]' 83 | '%W': '[]' 84 | '%x': () 85 | 86 | # Align if/else blocks with the variable assignment 87 | EndAlignment: 88 | AlignWith: variable 89 | 90 | # Do not always align parameters when it is easier to read 91 | AlignParameters: 92 | Exclude: 93 | - spec/**/*_spec.rb 94 | 95 | # Prefer #kind_of? over #is_a? 96 | ClassCheck: 97 | EnforcedStyle: kind_of? 98 | 99 | # Do not prefer double quotes to be used when %q or %Q is more appropriate 100 | UnneededPercentQ: 101 | Enabled: false 102 | 103 | # Allow a maximum ABC score 104 | Metrics/AbcSize: 105 | Max: 6.08 106 | 107 | # Allow modules to be qualified with a "::" prefix 108 | ClassAndModuleChildren: 109 | Exclude: 110 | - spec/**/*_spec.rb 111 | 112 | # Allow additional spaces 113 | ExtraSpacing: 114 | Enabled: false 115 | -------------------------------------------------------------------------------- /config/yardstick.yml: -------------------------------------------------------------------------------- 1 | --- 2 | threshold: 100 3 | -------------------------------------------------------------------------------- /ice_nine.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require File.expand_path('../lib/ice_nine/version', __FILE__) 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = 'ice_nine' 7 | gem.version = IceNine::VERSION.dup 8 | gem.authors = ['Dan Kubb'] 9 | gem.email = %w[dan.kubb@gmail.com] 10 | gem.description = 'Deep Freeze Ruby Objects' 11 | gem.summary = gem.description 12 | gem.homepage = 'https://github.com/dkubb/ice_nine' 13 | gem.license = 'MIT' 14 | 15 | gem.require_paths = %w[lib] 16 | gem.files = `git ls-files`.split($/) 17 | gem.test_files = `git ls-files -- spec/{unit,integration}`.split($/) 18 | gem.extra_rdoc_files = %w[LICENSE README.md TODO] 19 | 20 | gem.required_ruby_version = '>= 2.7.3' 21 | 22 | gem.add_development_dependency('bundler', '~> 2.2', '>= 2.2.33') 23 | gem.add_development_dependency('rake', '~> 13.0', '>= 13.0.6') 24 | end 25 | -------------------------------------------------------------------------------- /lib/ice_nine.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'ice_nine/support/recursion_guard' 4 | 5 | require 'ice_nine/freezer' 6 | require 'ice_nine/freezer/object' 7 | require 'ice_nine/freezer/no_freeze' 8 | require 'ice_nine/freezer/array' 9 | 10 | require 'ice_nine/freezer/false_class' 11 | require 'ice_nine/freezer/hash' 12 | require 'ice_nine/freezer/nil_class' 13 | require 'ice_nine/freezer/module' 14 | require 'ice_nine/freezer/numeric' 15 | require 'ice_nine/freezer/struct' 16 | require 'ice_nine/freezer/symbol' 17 | require 'ice_nine/freezer/true_class' 18 | 19 | require 'ice_nine/version' 20 | 21 | # Base IceNine module 22 | module IceNine 23 | 24 | # Deep Freeze an object 25 | # 26 | # @example 27 | # object = IceNine.deep_freeze(object) 28 | # 29 | # @param [Object] object 30 | # 31 | # @return [Object] 32 | # 33 | # @api public 34 | def self.deep_freeze(object) 35 | Freezer.deep_freeze(object) 36 | end 37 | 38 | # Deep Freeze an object 39 | # 40 | # This method uses a faster algorithm that will assume objects that are 41 | # `frozen?` do not need to be frozen deeply. Use this method when `object` 42 | # contains no shallowly frozen objects that need deep freezing. 43 | # 44 | # @example 45 | # IceNine.deep_freeze!(['a', 'b']).map(&:frozen?) # [true, true] 46 | # 47 | # @example 48 | # IceNine.deep_freeze!(['a', 'b'].freeze).map(&:frozen?) # [false, false] 49 | # 50 | # @param [Object] object 51 | # 52 | # @return [Object] 53 | # 54 | # @api public 55 | def self.deep_freeze!(object) 56 | Freezer.deep_freeze!(object) 57 | end 58 | 59 | end # IceNine 60 | -------------------------------------------------------------------------------- /lib/ice_nine/core_ext/object.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | 5 | # Core Ruby extensions 6 | module CoreExt 7 | 8 | # Extend Object with deep freezing 9 | module Object 10 | 11 | # Deep freeze an object 12 | # 13 | # @example 14 | # object = object.deep_freeze 15 | # 16 | # @return [self] 17 | # 18 | # @api public 19 | def deep_freeze 20 | IceNine.deep_freeze(self) 21 | end 22 | 23 | # Deep freeze an object 24 | # 25 | # @see IceNine.deep_freeze! 26 | # 27 | # @example 28 | # object = object.deep_freeze! 29 | # 30 | # @return [self] 31 | # 32 | # @api public 33 | def deep_freeze! 34 | IceNine.deep_freeze!(self) 35 | end 36 | 37 | end # Object 38 | end # CoreExt 39 | end # IceNine 40 | 41 | # Add Object#deep_freeze 42 | Object.instance_eval { include IceNine::CoreExt::Object } 43 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | 5 | # The default class that handles freezing objects 6 | class Freezer 7 | 8 | # Cache the Freezer classes returned for each type 9 | @freezer_cache = Hash.new do |cache, mod| 10 | freezer = nil 11 | mod.ancestors.each do |ancestor| 12 | freezer = find(ancestor.name.to_s) and break 13 | end 14 | cache[mod] = freezer 15 | end 16 | 17 | # Look up the Freezer descendant by object type 18 | # 19 | # @example 20 | # freezer_class = IceNine::Freezer[mod] 21 | # 22 | # @param [Module] mod 23 | # 24 | # @return [Class] 25 | # 26 | # @api public 27 | def self.[](mod) 28 | @freezer_cache[mod] 29 | end 30 | 31 | # Deep freeze an object with a particular Freezer 32 | # 33 | # @see IceNine.deep_freeze 34 | # 35 | # @param [Object] object 36 | # 37 | # @return [Object] 38 | # 39 | # @api public 40 | def self.deep_freeze(object) 41 | guarded_deep_freeze(object, RecursionGuard::ObjectSet.new) 42 | end 43 | 44 | # Deep freeze an object with a particular Freezer 45 | # 46 | # @see IceNine.deep_freeze! 47 | # 48 | # @param [Object] object 49 | # 50 | # @return [Object] 51 | # 52 | # @api public 53 | def self.deep_freeze!(object) 54 | guarded_deep_freeze(object, RecursionGuard::Frozen.new) 55 | end 56 | 57 | # Find a Freezer descendant by name 58 | # 59 | # @param [String] name 60 | # 61 | # @return [Class] 62 | # returned if a matching freezer is found 63 | # @return [nil] 64 | # returned if no matching freezer is found 65 | # 66 | # @api private 67 | def self.find(name) 68 | freezer = name.split('::').reduce(self) do |mod, const| 69 | mod.const_lookup(const) or break mod 70 | end 71 | freezer if freezer < self # only return a descendant freezer 72 | end 73 | 74 | private_class_method :find 75 | 76 | # Look up a constant in the namespace 77 | # 78 | # @param [String] namespace 79 | # 80 | # @return [Module] 81 | # returned if a matching constant is found 82 | # @return [nil] 83 | # returned if no matching constant is found 84 | # 85 | # @api private 86 | def self.const_lookup(namespace) 87 | const_get(namespace) if const_defined?(namespace, nil) 88 | end 89 | 90 | # Deep freeze an object with a particular Freezer and RecursionGuard 91 | # 92 | # @param [Object] object 93 | # @param [RecursionGuard] recursion_guard 94 | # 95 | # @return [Object] 96 | # 97 | # @api private 98 | def self.guarded_deep_freeze(object, recursion_guard) 99 | recursion_guard.guard(object) do 100 | self[object.class].guarded_deep_freeze(object, recursion_guard) 101 | end 102 | end 103 | 104 | class << self 105 | protected :const_lookup, :guarded_deep_freeze 106 | end 107 | 108 | end # Freezer 109 | end # IceNine 110 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/array.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # A freezer class for handling Array objects 7 | class Array < Object 8 | 9 | # Deep Freeze an Array 10 | # 11 | # @example 12 | # array = IceNine:Freezer::Array.deep_freeze(%w[a b c]) 13 | # array.select(&:frozen?) # => ['a', 'b', 'c'] 14 | # 15 | # @param [Array] array 16 | # @param [RecursionGuard] recursion_guard 17 | # 18 | # @return [Array] 19 | def self.guarded_deep_freeze(array, recursion_guard) 20 | super 21 | array.each do |entry| 22 | Freezer.guarded_deep_freeze(entry, recursion_guard) 23 | end 24 | end 25 | 26 | end # Array 27 | end # Freezer 28 | end # IceNine 29 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/false_class.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # Skip freezing false objects 7 | class FalseClass < NoFreeze; end 8 | 9 | end # Freezer 10 | end # IceNine 11 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/hash.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # A freezer class for handling Hash objects 7 | class Hash < Object 8 | 9 | # Deep Freeze a Hash 10 | # 11 | # @example 12 | # hash = IceNine::Freezer::Hash.deep_freeze('a' => '1', 'b' => '2') 13 | # hash.keys.select(&:frozen?) # => ['a', 'b'] 14 | # hash.values.select(&:frozen?) # => ['1', '2'] 15 | # 16 | # @param [Hash] hash 17 | # @param [RecursionGuard] recursion_guard 18 | # 19 | # @return [Hash] 20 | def self.guarded_deep_freeze(hash, recursion_guard) 21 | super 22 | default = hash.default_proc || hash.default 23 | Freezer.guarded_deep_freeze(default, recursion_guard) 24 | freeze_key_value_pairs(hash, recursion_guard) 25 | end 26 | 27 | # Handle freezing the key/value pairs 28 | # 29 | # @param [Hash] hash 30 | # @param [RecursionGuard] recursion_guard 31 | # 32 | # @return [undefined] 33 | # 34 | # @api private 35 | def self.freeze_key_value_pairs(hash, recursion_guard) 36 | hash.each do |key, value| 37 | Freezer.guarded_deep_freeze(key, recursion_guard) 38 | Freezer.guarded_deep_freeze(value, recursion_guard) 39 | end 40 | end 41 | 42 | private_class_method :freeze_key_value_pairs 43 | 44 | end # Hash 45 | end # Freezer 46 | end # IceNine 47 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/module.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # Skip freezing Module objects 7 | class Module < NoFreeze; end 8 | 9 | end # Freezer 10 | end # IceNine 11 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/nil_class.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # Skip freezing nil objects 7 | class NilClass < NoFreeze; end 8 | 9 | end # Freezer 10 | end # IceNine 11 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/no_freeze.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # A freezer class that does not freeze anything 7 | class NoFreeze < self 8 | 9 | # Pass through the object without freezing it 10 | # 11 | # @example 12 | # object = IceNine::Freezer::NoFreeze.deep_freeze(object) 13 | # object.frozen? # => false 14 | # 15 | # @param [Object] object 16 | # @param [RecursionGuard] _recursion_guard 17 | # 18 | # @return [Object] 19 | def self.guarded_deep_freeze(object, _recursion_guard) 20 | object 21 | end 22 | 23 | end # NoFreeze 24 | end # Freezer 25 | end # IceNine 26 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/numeric.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # Skip freezing Numeric objects 7 | class Numeric < NoFreeze; end 8 | 9 | end # Freezer 10 | end # IceNine 11 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/object.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # A freezer class for handling Object instances 7 | class Object < self 8 | 9 | # Deep Freeze an object 10 | # 11 | # @example 12 | # object = IceNine.deep_freeze(Object.new) 13 | # 14 | # @param [Object] object 15 | # @param [RecursionGuard] recursion_guard 16 | # 17 | # @return [Object] 18 | def self.guarded_deep_freeze(object, recursion_guard) 19 | return object unless object.respond_to?(:freeze) 20 | 21 | object.freeze 22 | freeze_instance_variables(object, recursion_guard) 23 | object 24 | end 25 | 26 | # Handle freezing the object's instance variables 27 | # 28 | # @param [Object] object 29 | # @param [RecursionGuard] recursion_guard 30 | # 31 | # @return [undefined] 32 | # 33 | # @api private 34 | def self.freeze_instance_variables(object, recursion_guard) 35 | object.instance_variables.each do |ivar_name| 36 | Freezer.guarded_deep_freeze( 37 | object.instance_variable_get(ivar_name), 38 | recursion_guard 39 | ) 40 | end 41 | end 42 | 43 | private_class_method :freeze_instance_variables 44 | 45 | end # Object 46 | 47 | BasicObject = Object 48 | end # Freezer 49 | end # IceNine 50 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/struct.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # A freezer class for handling Struct objects 7 | class Struct < Array; end 8 | 9 | end # Freezer 10 | end # IceNine 11 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/symbol.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # Skip freezing Symbol objects 7 | class Symbol < NoFreeze; end 8 | 9 | end # Freezer 10 | end # IceNine 11 | -------------------------------------------------------------------------------- /lib/ice_nine/freezer/true_class.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | class Freezer 5 | 6 | # Skip freezing true objects 7 | class TrueClass < NoFreeze; end 8 | 9 | end # Freezer 10 | end # IceNine 11 | -------------------------------------------------------------------------------- /lib/ice_nine/support/recursion_guard.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | 5 | # Protect against infinite recursion 6 | # 7 | # @private 8 | class RecursionGuard 9 | 10 | # Protects against infinite recursion by never yielding with the same 11 | # object more than once. 12 | class ObjectSet < self 13 | 14 | # Initialize a recursion guard 15 | # 16 | # @return [undefined] 17 | def initialize 18 | @object_ids = {} 19 | end 20 | 21 | # Guard against recursively calling a block with the same object 22 | # 23 | # @example 24 | # recursion_guard = IceNine::RecursionGuard::ObjectSet.new 25 | # recursion_guard.guard(object) do 26 | # logic_which_may_be_recursively_called_with_object(recursion_guard) 27 | # end 28 | # 29 | # @param [Object] object 30 | # 31 | # @return [Object] 32 | def guard(object) 33 | caller_object_id = object.__id__ 34 | return object if @object_ids.key?(caller_object_id) 35 | @object_ids[caller_object_id] = nil 36 | yield 37 | end 38 | 39 | end # ObjectSet 40 | 41 | # Protects against infinite recursion by not yielding with frozen objects 42 | class Frozen < self 43 | 44 | # Guard against recursively calling a block with the same frozen object 45 | # 46 | # @param [Object] object 47 | # 48 | # @return [Object] 49 | def guard(object) 50 | return object if object.frozen? 51 | yield 52 | end 53 | 54 | end # Frozen 55 | 56 | end # RecursionGuard 57 | end # IceNine 58 | -------------------------------------------------------------------------------- /lib/ice_nine/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module IceNine 4 | 5 | # Gem version 6 | VERSION = '0.11.2'.freeze 7 | 8 | end # IceNine 9 | -------------------------------------------------------------------------------- /spec/integration/ice_nine/class_methods/deep_freeze_bang_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | require 'delegate' 6 | 7 | describe IceNine, '.deep_freeze!' do 8 | subject { object.deep_freeze!(value) } 9 | 10 | let(:object) { IceNine } 11 | 12 | context 'with a shallowly frozen value' do 13 | let(:value) { %w[a b].freeze } 14 | 15 | it 'does not deep freeze' do 16 | expect(subject.select(&:frozen?)).to be_empty 17 | end 18 | end 19 | 20 | it_should_behave_like 'IceNine.deep_freeze' 21 | end 22 | -------------------------------------------------------------------------------- /spec/integration/ice_nine/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | require 'delegate' 6 | 7 | describe IceNine, '.deep_freeze' do 8 | subject { object.deep_freeze(value) } 9 | 10 | let(:object) { IceNine } 11 | 12 | context 'with a shallowly frozen value' do 13 | let(:value) { ['a', %w[b c]].freeze } 14 | 15 | it 'does a deep freeze' do 16 | expect(subject.select(&:frozen?)).to eql(value) 17 | end 18 | end 19 | 20 | it_should_behave_like 'IceNine.deep_freeze' 21 | end 22 | -------------------------------------------------------------------------------- /spec/shared/array_deep_freeze.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | shared_examples 'IceNine::Freezer::Array.deep_freeze' do 4 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 5 | 6 | it 'freezes each entry' do 7 | expect(subject.select(&:frozen?)).to eql(subject.to_a) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/shared/hash_deep_freeze.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | shared_examples 'IceNine::Freezer::Hash.deep_freeze' do 4 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 5 | 6 | it 'freezes each key' do 7 | expect(subject.keys.select(&:frozen?)).to eql(subject.keys) 8 | end 9 | 10 | it 'freezes each value' do 11 | expect(subject.values.select(&:frozen?)).to eql(subject.values) 12 | end 13 | 14 | if RUBY_VERSION >= '1.9' && RUBY_ENGINE == 'rbx' 15 | it 'does not freeze the state' do 16 | expect(subject.instance_variable_get(:@state)).to_not be_frozen 17 | end 18 | 19 | it 'does not freeze the entries' do 20 | expect(subject.instance_variable_get(:@entries)).to_not be_frozen 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/shared/ice_nine_deep_freeze.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | shared_examples 'IceNine.deep_freeze' do 4 | context 'with an Object' do 5 | let(:value) { Object.new } 6 | 7 | before do 8 | value.instance_eval { @a = '1' } 9 | end 10 | 11 | it 'returns the object' do 12 | should be(value) 13 | end 14 | 15 | it 'freezes the object' do 16 | expect { subject }.to change(value, :frozen?).from(false).to(true) 17 | end 18 | 19 | it 'freezes the instance variables in the Object' do 20 | expect(subject.instance_variable_get(:@a)).to be_frozen 21 | end 22 | 23 | context 'with a circular reference' do 24 | before do 25 | value.instance_eval { @self = self } 26 | end 27 | 28 | it 'returns the object' do 29 | should be(value) 30 | end 31 | 32 | it 'freezes the object' do 33 | expect { subject }.to change(value, :frozen?).from(false).to(true) 34 | end 35 | 36 | it 'freezes the instance variables in the Object' do 37 | expect(subject.instance_variable_get(:@a)).to be_frozen 38 | end 39 | end 40 | end 41 | 42 | context 'with an Array' do 43 | let(:value) { %w[a] } 44 | 45 | it 'returns the object' do 46 | should be(value) 47 | end 48 | 49 | it 'freezes the object' do 50 | expect { subject }.to change(value, :frozen?).from(false).to(true) 51 | end 52 | 53 | it 'freezes each element in the Array' do 54 | expect(subject.select(&:frozen?)).to eql(subject) 55 | end 56 | 57 | context 'with a circular reference' do 58 | before do 59 | value << value 60 | end 61 | 62 | it 'returns the object' do 63 | should be(value) 64 | end 65 | 66 | it 'freezes the object' do 67 | expect { subject }.to change(value, :frozen?).from(false).to(true) 68 | end 69 | 70 | it 'freezes each element in the Array' do 71 | expect(subject.select(&:frozen?)).to eql(subject) 72 | end 73 | end 74 | end 75 | 76 | context 'with a Hash' do 77 | let(:value) { { Object.new => Object.new } } 78 | 79 | it 'returns the object' do 80 | should be(value) 81 | end 82 | 83 | it 'freezes the object' do 84 | expect { subject }.to change(value, :frozen?).from(false).to(true) 85 | end 86 | 87 | it 'freezes each key in the Hash' do 88 | expect(subject.keys.select(&:frozen?)).to eql(subject.keys) 89 | end 90 | 91 | it 'freezes each value in the Hash' do 92 | expect(subject.values.select(&:frozen?)).to eql(subject.values) 93 | end 94 | 95 | context 'with a circular reference' do 96 | before do 97 | value[value] = value 98 | end 99 | 100 | it 'returns the object' do 101 | should be(value) 102 | end 103 | 104 | it 'freezes the object' do 105 | expect { subject }.to change(value, :frozen?).from(false).to(true) 106 | end 107 | 108 | it 'freezes each key in the Hash' do 109 | expect(subject.keys.select(&:frozen?)).to eql(subject.keys) 110 | end 111 | 112 | it 'freezes each value in the Hash' do 113 | expect(subject.values.select(&:frozen?)).to eql(subject.values) 114 | end 115 | end 116 | end 117 | 118 | context 'with a String' do 119 | let(:value) { '' } 120 | 121 | before do 122 | value.instance_eval { @a = '1' } 123 | end 124 | 125 | it 'returns the object' do 126 | should be(value) 127 | end 128 | 129 | it 'freezes the object' do 130 | expect { subject }.to change(value, :frozen?).from(false).to(true) 131 | end 132 | 133 | it 'freezes the instance variables in the String' do 134 | expect(subject.instance_variable_get(:@a)).to be_frozen 135 | end 136 | 137 | context 'with a circular reference' do 138 | before do 139 | value.instance_eval { @self = self } 140 | end 141 | 142 | it 'returns the object' do 143 | should be(value) 144 | end 145 | 146 | it 'freezes the object' do 147 | expect { subject }.to change(value, :frozen?).from(false).to(true) 148 | end 149 | 150 | it 'freezes the instance variables in the String' do 151 | expect(subject.instance_variable_get(:@a)).to be_frozen 152 | end 153 | end 154 | end 155 | 156 | context 'with a Struct' do 157 | let(:value) { klass.new(%w[ 1 2 ]) } 158 | let(:klass) { Struct.new(:a) } 159 | 160 | it 'returns the object' do 161 | should be(value) 162 | end 163 | 164 | it 'freezes the object' do 165 | expect { subject }.to change(value, :frozen?).from(false).to(true) 166 | end 167 | 168 | it 'freezes each value in the Struct' do 169 | expect(subject.values.select(&:frozen?)).to eql(subject.values) 170 | end 171 | 172 | context 'with a circular reference' do 173 | before do 174 | value.a = value 175 | end 176 | 177 | it 'returns the object' do 178 | should be(value) 179 | end 180 | 181 | it 'freezes the object' do 182 | expect { subject }.to change(value, :frozen?).from(false).to(true) 183 | end 184 | 185 | it 'freezes each value in the Struct' do 186 | expect(subject.values.select(&:frozen?)).to eql(subject.values) 187 | end 188 | end 189 | end 190 | 191 | context 'with an SimpleDelegator' do 192 | let(:value) { SimpleDelegator.new('foo') } 193 | 194 | before do 195 | value.instance_eval { @a = '1' } 196 | end 197 | 198 | it 'returns the object' do 199 | should be(value) 200 | end 201 | 202 | it 'freezes the object' do 203 | expect { subject }.to change(value, :frozen?).from(false).to(true) 204 | end 205 | 206 | it 'freezes the instance variables in the SimpleDelegator' do 207 | expect(subject.instance_variable_get(:@a)).to be_frozen 208 | end 209 | 210 | context 'with a circular reference' do 211 | before do 212 | value.instance_eval { @self = self } 213 | end 214 | 215 | it 'returns the object' do 216 | should be(value) 217 | end 218 | 219 | it 'freezes the object' do 220 | expect { subject }.to change(value, :frozen?).from(false).to(true) 221 | end 222 | 223 | it 'freezes the instance variables in the SimpleDelegator' do 224 | expect(subject.instance_variable_get(:@a)).to be_frozen 225 | end 226 | end 227 | end 228 | 229 | [0.0, 0, 0x7fffffffffffffff, true, false, nil, :symbol].each do |value| 230 | context "with a #{value.class}" do 231 | let(:value) { value } 232 | 233 | it 'returns the object' do 234 | should be(value) 235 | end 236 | 237 | it 'does not freeze the object' do 238 | expect { subject }.to_not change(value, :frozen?).from(value.frozen?) 239 | end 240 | end 241 | end 242 | end 243 | -------------------------------------------------------------------------------- /spec/shared/no_freeze_deep_freeze.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | shared_examples 'IceNine::Freezer::NoFreeze.deep_freeze' do 4 | before do 5 | value.instance_eval { @a = '1' } unless value.frozen? 6 | end 7 | 8 | it 'returns the object' do 9 | should be(value) 10 | end 11 | 12 | it 'does not freeze the object' do 13 | expect { subject }.to_not change(value, :frozen?).from(value.frozen?) 14 | end 15 | 16 | it 'does not freeze instance variables' do 17 | if subject.instance_variable_defined?(:@a) 18 | expect(subject.instance_variable_get(:@a)).to_not be_frozen 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/shared/object_deep_freeze.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | shared_examples 'IceNine::Freezer::Object.deep_freeze' do 4 | before do 5 | value.instance_eval { @a = '1' } 6 | end 7 | 8 | it 'returns the object' do 9 | should be(value) 10 | end 11 | 12 | it 'freezes the object' do 13 | expect { subject }.to change(value, :frozen?).from(false).to(true) 14 | end 15 | 16 | it 'freezes instance variables' do 17 | expect(subject.instance_variable_get(:@a)).to be_frozen 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/shared/range_deep_freeze.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | shared_examples 'IceNine::Freezer::Range.deep_freeze' do 4 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 5 | 6 | it 'freeze the first element' do 7 | expect(subject.begin).to be_frozen 8 | end 9 | 10 | it 'freeze the last element' do 11 | expect(subject.end).to be_frozen 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | if ENV['COVERAGE'] == 'true' 4 | require 'simplecov' 5 | 6 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 7 | SimpleCov::Formatter::HTMLFormatter, 8 | ] 9 | 10 | SimpleCov.start do 11 | command_name 'spec:unit' 12 | 13 | add_filter 'config' 14 | add_filter 'spec' 15 | add_filter 'vendor' 16 | 17 | minimum_coverage 100 18 | end 19 | end 20 | 21 | require 'ice_nine' 22 | 23 | Dir[Pathname(__dir__).join('shared/**/*.rb')].each(&Kernel.method(:require)) 24 | 25 | RSpec.configure do |config| 26 | config.expect_with :rspec do |expect_with| 27 | expect_with.syntax = :expect 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/support/config_alias.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rbconfig' 4 | 5 | ::Config = RbConfig unless defined?(::Config) 6 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/class_methods/deep_freeze_bang_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine, '.deep_freeze!' do 7 | subject { object.deep_freeze!(value) } 8 | 9 | let(:object) { IceNine } 10 | let(:value) { Object.new } 11 | 12 | context 'when the object is not frozen' do 13 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 14 | end 15 | 16 | context 'when the object is frozen' do 17 | before do 18 | value.instance_eval { @a = '1' } 19 | value.freeze 20 | end 21 | 22 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine, '.deep_freeze' do 7 | subject { object.deep_freeze(value) } 8 | 9 | let(:object) { IceNine } 10 | let(:value) { Object.new } 11 | 12 | context 'when the object is not frozen' do 13 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 14 | end 15 | 16 | context 'when the object is frozen' do 17 | before do 18 | value.instance_eval { @a = '1' } 19 | value.freeze 20 | end 21 | 22 | it 'returns the object' do 23 | should be(value) 24 | end 25 | 26 | it 'leaves the object frozen' do 27 | expect { subject }.not_to change(value, :frozen?).from(true) 28 | end 29 | 30 | it 'freezes instance variables' do 31 | expect(subject.instance_variable_get(:@a)).to be_frozen 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/core_ext/object/deep_freeze_bang_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | require 'ice_nine/core_ext/object' 6 | 7 | describe IceNine::CoreExt::Object, '#deep_freeze!' do 8 | subject { value.deep_freeze! } 9 | 10 | let(:value) { Object.new.extend(IceNine::CoreExt::Object) } 11 | 12 | context 'when the object is not frozen' do 13 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 14 | end 15 | 16 | context 'when the object is frozen' do 17 | before do 18 | value.instance_eval { @a = '1' } 19 | value.freeze 20 | end 21 | 22 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/core_ext/object/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | require 'ice_nine/core_ext/object' 6 | 7 | describe IceNine::CoreExt::Object, '#deep_freeze' do 8 | subject { value.deep_freeze } 9 | 10 | let(:value) { Object.new.extend(IceNine::CoreExt::Object) } 11 | 12 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/array/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine::Freezer::Array, '.deep_freeze' do 7 | subject { object.deep_freeze(value) } 8 | 9 | let(:object) { described_class } 10 | 11 | context 'with an Array object' do 12 | let(:value) { %w[a] } 13 | 14 | context 'without a circular reference' do 15 | it_behaves_like 'IceNine::Freezer::Array.deep_freeze' 16 | end 17 | 18 | context 'with a circular reference' do 19 | before { value << value } 20 | 21 | it_behaves_like 'IceNine::Freezer::Array.deep_freeze' 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/class_methods/deep_freeze_bang_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine::Freezer, '.deep_freeze!' do 7 | subject { object.deep_freeze!(value) } 8 | 9 | let(:object) { IceNine::Freezer } 10 | let(:value) { Object.new } 11 | 12 | context 'when the object is not frozen' do 13 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 14 | end 15 | 16 | context 'when the object is frozen' do 17 | before do 18 | value.instance_eval { @a = '1' } 19 | value.freeze 20 | end 21 | 22 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine::Freezer, '.deep_freeze' do 7 | subject { object.deep_freeze(value) } 8 | 9 | let(:object) { IceNine::Freezer } 10 | let(:value) { Object.new } 11 | 12 | context 'when the object is not frozen' do 13 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 14 | end 15 | 16 | context 'when the object is frozen' do 17 | before do 18 | value.instance_eval { @a = '1' } 19 | value.freeze 20 | end 21 | 22 | it 'returns the object' do 23 | should be(value) 24 | end 25 | 26 | it 'leaves the object frozen' do 27 | expect { subject }.not_to change(value, :frozen?).from(true) 28 | end 29 | 30 | it 'freezes instance variables' do 31 | expect(subject.instance_variable_get(:@a)).to be_frozen 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/class_methods/element_reader_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer' 5 | require 'ice_nine/freezer/object' 6 | require 'ice_nine/freezer/array' 7 | require 'ice_nine/freezer/struct' 8 | 9 | describe IceNine::Freezer, '.[]' do 10 | subject { object[mod] } 11 | 12 | let(:object) { described_class } 13 | let(:freezer) { object::Object } 14 | 15 | describe 'when the module matches a descendant' do 16 | let(:freezer) { Class.new(object) } 17 | let(:mod) { Class } 18 | 19 | before do 20 | object.const_set(mod.name, freezer) 21 | end 22 | 23 | after do 24 | object.send(:remove_const, mod.name) 25 | end 26 | 27 | it 'returns the freezer' do 28 | should be(freezer) 29 | end 30 | end 31 | 32 | describe 'when the module matches a descendant inside a namespace' do 33 | let(:namespace) { Class.new(object) } 34 | let(:freezer) { Class.new(object) } 35 | let(:mod) { Application::User } 36 | 37 | before :all do 38 | module ::Application 39 | class User; end 40 | end 41 | end 42 | 43 | after :all do 44 | ::Application.send(:remove_const, :User) 45 | Object.send(:remove_const, :Application) 46 | end 47 | 48 | around do |example| 49 | namespace.const_set(:User, freezer) 50 | object.const_set(:Application, namespace) 51 | 52 | example.run 53 | 54 | namespace.send(:remove_const, :User) 55 | object.send(:remove_const, :Application) 56 | end 57 | 58 | it 'returns the freezer' do 59 | should be(freezer) 60 | end 61 | end 62 | 63 | describe 'when the module is a struct' do 64 | let(:mod) { Struct.new(:a) } 65 | let(:freezer) { IceNine::Freezer::Struct } 66 | 67 | it 'returns the freezer' do 68 | should be(freezer) 69 | end 70 | end 71 | 72 | describe 'when the module does not match a descendant' do 73 | let(:mod) { Object } 74 | 75 | it 'returns the freezer' do 76 | should be(freezer) 77 | end 78 | end 79 | 80 | describe 'when the module is an anonymous class' do 81 | let(:mod) { Class.new } 82 | 83 | it 'returns the freezer' do 84 | should be(freezer) 85 | end 86 | end 87 | 88 | describe 'when the module is an anonymous module' do 89 | let(:mod) { Module.new } 90 | 91 | it 'returns the freezer' do 92 | should be_nil 93 | end 94 | end 95 | 96 | describe 'when the module is under a freezer namespace' do 97 | let(:mod) { Hash::Test } 98 | let(:freezer) { IceNine::Freezer::Hash } 99 | 100 | around do |example| 101 | class Hash::Test; end 102 | example.run 103 | Hash.send(:remove_const, :Test) 104 | end 105 | 106 | it 'returns the freezer' do 107 | should be(freezer) 108 | end 109 | end 110 | 111 | describe 'when the module has a name of a freezer in another namespace' do 112 | let(:mod) { Mash::State } 113 | let(:freezer) { Class.new(IceNine::Freezer::Hash) } 114 | 115 | before :all do 116 | module ::Mash 117 | class State; end 118 | end 119 | end 120 | 121 | after :all do 122 | ::Mash.send(:remove_const, :State) 123 | Object.send(:remove_const, :Mash) 124 | end 125 | 126 | around do |example| 127 | object.const_set(:Mash, freezer) 128 | 129 | example.run 130 | 131 | object.send(:remove_const, :Mash) 132 | end 133 | 134 | it 'returns the freezer' do 135 | should be(freezer) 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/false_class/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer' 5 | require 'ice_nine/freezer/no_freeze' 6 | require 'ice_nine/freezer/false_class' 7 | 8 | describe IceNine::Freezer::FalseClass, '.deep_freeze' do 9 | subject { object.deep_freeze(value) } 10 | 11 | let(:object) { described_class } 12 | 13 | context 'with a false object' do 14 | let(:value) { false } 15 | 16 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/hash/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine::Freezer::Hash, '.deep_freeze' do 7 | subject { object.deep_freeze(value) } 8 | 9 | let(:object) { described_class } 10 | 11 | context 'with a Hash object having a default proc' do 12 | let(:value) do 13 | Hash.new {}.update(Object.new => Object.new) 14 | end 15 | 16 | it_behaves_like 'IceNine::Freezer::Hash.deep_freeze' 17 | 18 | it 'freezes the default proc' do 19 | expect(subject.default_proc).to be_frozen 20 | end 21 | end 22 | 23 | context 'with a Hash object having a default value' do 24 | let(:value) do 25 | Hash.new('').update(Object.new => Object.new) 26 | end 27 | 28 | it_behaves_like 'IceNine::Freezer::Hash.deep_freeze' 29 | 30 | it 'freezes the default value' do 31 | expect(subject.default).to be_frozen 32 | end 33 | 34 | context 'that is a circular reference' do 35 | before { value.default = value } 36 | 37 | it_behaves_like 'IceNine::Freezer::Hash.deep_freeze' 38 | 39 | it 'freezes the default value' do 40 | expect(subject.default).to be_frozen 41 | end 42 | end 43 | end 44 | 45 | context 'with a Hash object containing itself as a key' do 46 | let(:value) do 47 | value = {} 48 | value[value] = '1' 49 | value 50 | end 51 | 52 | it_behaves_like 'IceNine::Freezer::Hash.deep_freeze' 53 | end 54 | 55 | context 'with a Hash object containing itself as a value' do 56 | let(:value) do 57 | value = {} 58 | value['a'] = value 59 | value 60 | end 61 | 62 | it_behaves_like 'IceNine::Freezer::Hash.deep_freeze' 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/module/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer' 5 | require 'ice_nine/freezer/no_freeze' 6 | require 'ice_nine/freezer/module' 7 | 8 | describe IceNine::Freezer::Module, '.deep_freeze' do 9 | subject { object.deep_freeze(value) } 10 | 11 | let(:object) { described_class } 12 | 13 | context 'with a Module object' do 14 | let(:value) { Module.new } 15 | 16 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/nil_class/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer' 5 | require 'ice_nine/freezer/no_freeze' 6 | require 'ice_nine/freezer/nil_class' 7 | 8 | describe IceNine::Freezer::NilClass, '.deep_freeze' do 9 | subject { object.deep_freeze(value) } 10 | 11 | let(:object) { described_class } 12 | 13 | context 'with a nil object' do 14 | let(:value) { nil } 15 | 16 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/no_freeze/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer/no_freeze' 5 | 6 | describe IceNine::Freezer::NoFreeze, '.deep_freeze' do 7 | subject { object.deep_freeze(value) } 8 | 9 | let(:object) { described_class } 10 | let(:value) { double('value') } 11 | 12 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 13 | end 14 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/numeric/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer' 5 | require 'ice_nine/freezer/no_freeze' 6 | require 'ice_nine/freezer/numeric' 7 | require 'bigdecimal' 8 | 9 | describe IceNine::Freezer::Numeric, '.deep_freeze' do 10 | subject { object.deep_freeze(value) } 11 | 12 | let(:object) { described_class } 13 | 14 | [0.0, 0, 0x7fffffffffffffff, BigDecimal('0')].each do |value| 15 | context "with a #{value.class} object" do 16 | let(:value) { value } 17 | 18 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/object/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine::Freezer::Object, '.deep_freeze' do 7 | subject { object.deep_freeze(value) } 8 | 9 | let(:object) { described_class } 10 | 11 | context 'with an Object' do 12 | let(:value) { Object.new } 13 | 14 | context 'without a circular reference' do 15 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 16 | end 17 | 18 | context 'with a circular reference' do 19 | before do 20 | value.instance_eval { @b = self } 21 | end 22 | 23 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 24 | end 25 | end 26 | 27 | context 'with an Object which undefs :freeze' do 28 | let(:value) { UndefFreeze.new } 29 | 30 | before do 31 | klass = Class.new do 32 | undef :freeze 33 | end 34 | 35 | stub_const('UndefFreeze', klass) 36 | end 37 | 38 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/struct/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | 6 | describe IceNine::Freezer::Struct, '.deep_freeze' do 7 | subject { object.deep_freeze(value) } 8 | 9 | let(:object) { described_class } 10 | 11 | context 'with a Struct' do 12 | let(:value) { klass.new('1') } 13 | let(:klass) { Struct.new(:a) } 14 | 15 | it_behaves_like 'IceNine::Freezer::Array.deep_freeze' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/symbol/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer' 5 | require 'ice_nine/freezer/no_freeze' 6 | require 'ice_nine/freezer/symbol' 7 | 8 | describe IceNine::Freezer::Symbol, '.deep_freeze' do 9 | subject { object.deep_freeze(value) } 10 | 11 | let(:object) { described_class } 12 | 13 | context 'with a Symbol object' do 14 | let(:value) { :symbol } 15 | 16 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/freezer/true_class/class_methods/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/freezer' 5 | require 'ice_nine/freezer/object' 6 | require 'ice_nine/freezer/no_freeze' 7 | require 'ice_nine/freezer/true_class' 8 | 9 | describe IceNine::Freezer::TrueClass, '.deep_freeze' do 10 | subject { object.deep_freeze(value) } 11 | 12 | let(:object) { described_class } 13 | 14 | context 'with a true object' do 15 | let(:value) { true } 16 | 17 | it_behaves_like 'IceNine::Freezer::NoFreeze.deep_freeze' 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/recursion_guard/frozen/guard_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/support/recursion_guard' 5 | 6 | describe IceNine::RecursionGuard::Frozen, '#guard' do 7 | subject { object.guard(object_arg) { return_value } } 8 | 9 | let(:object) { IceNine::RecursionGuard::Frozen.new } 10 | let(:object_arg) { Object.new } 11 | let(:return_value) { double('return_value') } 12 | 13 | context 'when the object_arg is not frozen' do 14 | it 'returns the expected value' do 15 | should be(return_value) 16 | end 17 | end 18 | 19 | context 'when the object_arg is frozen' do 20 | before do 21 | object_arg.freeze 22 | end 23 | 24 | it 'returns the expected value' do 25 | should be(object_arg) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/unit/ice_nine/recursion_guard/object_set/guard_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine/support/recursion_guard' 5 | 6 | describe IceNine::RecursionGuard::ObjectSet, '#guard' do 7 | let(:object) { IceNine::RecursionGuard::ObjectSet.new } 8 | let(:object_arg1) { 'similar_but_not_equal_id' } 9 | let(:object_arg2) { 'similar_but_not_equal_id' } 10 | let(:return_value) { double('return_value') } 11 | 12 | context 'when the block is not recursive' do 13 | subject do 14 | object.guard(object_arg1) { return_value } 15 | object.guard(object_arg2) { return_value } 16 | end 17 | 18 | it 'returns the expected value' do 19 | should be(return_value) 20 | end 21 | end 22 | 23 | context 'when the block is recursive' do 24 | subject do 25 | object.guard(object_arg1) do 26 | object.guard(object_arg2) do 27 | expect(subject).to be(object_arg1) 28 | return_value 29 | end 30 | expect(subject).to be(object_arg1) 31 | return_value 32 | end 33 | end 34 | 35 | it 'returns the expected value' do 36 | should be(return_value) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/unit/object/deep_freeze_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'spec_helper' 4 | require 'ice_nine' 5 | require 'ice_nine/core_ext/object' 6 | 7 | describe Object, '#deep_freeze' do 8 | subject { value.deep_freeze } 9 | 10 | let(:value) { described_class.new } 11 | 12 | it_behaves_like 'IceNine::Freezer::Object.deep_freeze' 13 | end 14 | --------------------------------------------------------------------------------