:toc: macro :toclevels: 5 :figure-caption!: = Refinements Refinements are a collection primitive Ruby objects enhancements without needing to resort to hard to debug link:https://www.alchemists.io/articles/ruby_antipatterns/#_monkey_patches[monkey patches]. These refinements give you additional syntactic sugar to develop clean and concise implementations while using less code. By refining our code we can acquire the functionality we wish the core primitives had! toc::[] == Features Enhances the following objects: * Array * BigDecimal * DateTime * Hash * IO * LogDevice * Logger * Pathname * String * StringIO * Structs == Requirements . https://www.ruby-lang.org[Ruby]. . A solid understanding of link:https://www.alchemists.io/articles/ruby_refinements[Ruby refinements and lexical scope]. == Setup To install, run: [source,bash] ---- gem install refinements ---- Add the following to your Gemfile file: [source,ruby] ---- gem "refinements" ---- == Usage === Requires If all refinements are not desired, add the following to your `+Gemfile+` instead: [source,ruby] ---- gem "refinements", require: false ---- ...then require the specific refinement, as needed. Example: [source,ruby] ---- require "refinements/arrays" require "refinements/big_decimals" require "refinements/date_times" require "refinements/hashes" require "refinements/ios" require "refinements/pathnames" require "refinements/strings" require "refinements/string_ios" require "refinements/structs" require "refinements/symbols" require "refinements/log_devices" require "refinements/loggers" ---- === Using Much like including/extending a module, you’ll need to modify your object(s) to use the refinement(s): [source,ruby] ---- class Example using Refinements::Arrays using Refinements::BigDecimals using Refinements::DateTimes using Refinements::Hashes using Refinements::IOs using Refinements::Pathnames using Refinements::Strings using Refinements::StringIOs using Refinements::Structs using Refinements::Symbols using Refinements::LogDevices using Refinements::Loggers end ---- === Examples The following sections demonstrate how each refinement enriches your objects with new capabilities. ==== Array ===== #combinatorial? Answers if an array is equal to another array when the elements are equal but in any order and/or subset. [source,ruby] ---- example = %w[a b c] example.combinatorial? %w[a b c] # true example.combinatorial? %w[c a b] # true example.combinatorial? %w[c] # true example.combinatorial? %w[c b] # true example.combinatorial? %w[x] # false example.combinatorial? %w[z b c] # false example.combinatorial? %w[a b c d] # false example.combinatorial? [] # false ---- ===== #compress Removes `nil` and empty objects without mutating itself. [source,ruby] ---- object = Object.new example = [1, "blueberry", nil, "", [], {}, object] example.compress # [1, "blueberry", object] example # [1, "blueberry", nil, "", [], {}, object] ---- ===== #compress! Removes `nil` and empty values while mutating itself. [source,ruby] ---- object = Object.new example = [1, "blueberry", nil, "", [], {}, object] example.compress # [1, "blueberry", object] example # [1, "blueberry", object] ---- ===== #excluding Removes given array or elements without mutating itself. [source,ruby] ---- [1, 2, 3, 4, 5].excluding [4, 5] # [1, 2, 3] [1, 2, 3, 4, 5].excluding 4, 5 # [1, 2, 3] ---- ===== #filter_find Answers the first element which evaluates to true from a filtered collection. [source,ruby] ---- handlers = [ ->(object) { object if object == :b }, proc { false }, ->(object) { object if object == :a } ] handlers.filter_find # Enumerator::Lazy handlers.filter_find { |handler| handler.call :a } # :a handlers.filter_find { |handler| handler.call :x } # nil ---- ===== #including Adds given array or elements without mutating itself. [source,ruby] ---- [1, 2, 3].including [4, 5] # [1, 2, 3, 4, 5] [1, 2, 3].including 4, 5 # [1, 2, 3, 4, 5] ---- ===== #intersperse Inserts additional elements or array between all members of given array. [source,ruby] ---- [1, 2, 3].intersperse :a # [1, :a, 2, :a, 3] [1, 2, 3].intersperse :a, :b # [1, :a, :b, 2, :a, :b, 3] [1, 2, 3].intersperse %i[a b c] # [1, :a, :b, :c, 2, :a, :b, :c, 3] ---- ===== #many? Answers true if an array has more than one element. Can take a block which evaluates as truthy or falsey. [source,ruby] ---- [1, 2].many? # true [1, 2, 3].many?(&:odd?) # true [1].many? # false [].many? # false ---- ===== #maximum Answers the maximum extracted value from a collection of objects. [source,ruby] ---- Point = Struct.new :x, :y, keyword_init: true points = [Point[x: 1, y: 2], Point[x: 0, y: 1], Point[x: 2, y: 3]] points.maximum(:x) # 2 points.maximum(:y) # 3 ---- ===== #mean Answers mean/average all elements within an array. [source,ruby] ---- [].mean # 0 [5].mean # 5 [1, 2, 3].mean # 2 [1.25, 1.5, 1.75].mean # 1.5 ---- ===== #minimum Answers the minimum extracted value from a collection of objects. [source,ruby] ---- Point = Struct.new :x, :y, keyword_init: true points = [Point[x: 1, y: 2], Point[x: 0, y: 1], Point[x: 2, y: 3]] points.minimum(:x) # 0 points.minimum(:y) # 1 ---- ===== #pad Answers new array padded with given value up to a maximum size. Useful in situations where an array needs to be a specific size with padded values. [source,ruby] ---- [1].pad 0 # [1] [1].pad 0, max: 3 # [1, 0, 0] [1, 2].pad 3, max: 3 # [1, 2, 3] ---- ===== #ring Answers a circular array which can enumerate before, current, after elements. [source,ruby] ---- example = [1, 2, 3] example.ring # "#" example.ring { |(before, current, after)| puts "#{before} #{current} #{after}" } # [3 1 2] # [1 2 3] # [2 3 1] ---- ===== #supplant Answers mutated array where first target element found is replaced by single or multiple elements. [source,ruby] ---- %i[a b a].supplant :a, :z # [:z, :b, :a] %i[a b a].supplant :a, :z, :y # [:z, :y, :b, :a] %i[a b a].supplant :a, %i[z y] # [[:z, :y], :b, :a] ---- ===== #supplant_if Answers mutated array where all target elements are replaced by single or multiple elements. ⚠️ Be aware that this can be an expensive operation on large arrays. [source,ruby] ---- %i[a b a].supplant_if :a, :z # [:z, :b, :z] %i[a b a].supplant_if :a, :z, :y # [:z, :y, :b, :z, :y] %i[a b a].supplant_if :a, %i[z y] # [[:z, :y], :b, [:z, :y]] ---- ===== #to_sentence Answers a sentence using `", "` as the default delimiter and `"and"` as the default conjunction. Useful when building documentation, answering human readable error messages, etc. [source,ruby] ---- [].to_sentence # "" ["test"].to_sentence # "test" ["a", :b].to_sentence # "a and b" [1, "a", :b, 2.0, /\w+/].map(&:inspect).to_sentence # 1, "a", :b, 2.0, and /\w+/ %w[one two three].to_sentence # "one, two, and three" %w[eins zwei drei].to_sentence delimiter: " ", conjunction: "und" # "eins zwei und drei" ---- ==== Big Decimal ===== #inspect Allows one to inspect a big decimal with numeric representation. [source,ruby] ---- BigDecimal.new("5.0E-10").inspect # "#" ---- ==== DateTime ===== .utc Answers new DateTime object for current UTC date/time. [source,ruby] ---- DateTime.utc # "#" ---- ==== Hash ===== .infinite Answers new hash where missing keys, even deeply nested, answer an empty hash. [source,ruby] ---- example = Hash.infinite example[:a] # {} example[:a][:b][:c] # {} ---- ===== .with_default Answers new hash where every top-level missing key has the same default value. [source,ruby] ---- example = Hash.with_default "" example[:a] # "" example = Hash.with_default [] example[:b] # [] ---- ===== #compress Removes `nil` and empty objects without mutating itself. [source,ruby] ---- object = Object.new example = {a: 1, b: "blueberry", c: nil, d: "", e: [], f: {}, g: object} example.compress # {a: 1, b: "blueberry", g: object} example # {a: 1, b: "blueberry", c: nil, d: "", e: [], f: {}, g: object} ---- ===== #compress! Removes `nil` and empty objects while mutating itself. [source,ruby] ---- object = Object.new example = {a: 1, b: "blueberry", c: nil, d: "", e: [], f: {}, g: object} example.compress! # {a: 1, b: "blueberry", g: object} example # {a: 1, b: "blueberry", g: object} ---- ===== #deep_merge Merges deeply nested hashes together without mutating itself. [source,ruby] ---- example = {a: "A", b: {one: "One", two: "Two"}} example.deep_merge b: {one: 1} # {a: "A", b: {one: 1, two: "Two"}} example # {a: "A", b: {one: "One", two: "Two"}} ---- ===== #deep_merge! Merges deeply nested hashes together while mutating itself. [source,ruby] ---- example = {a: "A", b: {one: "One", two: "Two"}} example.deep_merge! b: {one: 1} # {a: "A", b: {one: 1, two: "Two"}} example # {a: "A", b: {one: 1, two: "Two"}} ---- ===== #deep_stringify_keys Answers string keys of a nested hash without mutating itself. Does not handle nested arrays, though. [source,ruby] ---- example = {a: {b: 2}} example.deep_stringify_keys # {"a" => {"b" => 1}} example # {a: {b: 2}} ---- ===== #deep_stringify_keys! Answers string keys of nested hash while mutating itself. Does not handle nested arrays, though. [source,ruby] ---- example = {a: {b: 2}} example.deep_stringify_keys! # {"a" => {"b" => 1}} example # {"a" => {"b" => 1}} ---- ===== #deep_symbolize_keys Symbolizes keys of nested hash without mutating itself. Does not handle nested arrays, though. [source,ruby] ---- example = {"a" => {"b" => 2}} example.deep_symbolize_keys # {a: {b: 1}} example # {"a" => {"b" => 2}} ---- ===== #deep_symbolize_keys! Symbolizes keys of nested hash while mutating itself. Does not handle nested arrays, though. [source,ruby] ---- example = {"a" => {"b" => 2}} example.deep_symbolize_keys! # {a: {b: 1}} example # {a: {b: 1}} ---- ===== #fetch_value Fetches value for exiting or missing key. Behavior is identical to `#fetch` except when the value of the key is `nil` you'll get the default value instead. This eliminates the need for using an _or_ expression `example.fetch(:desired_key) || "default_value"`. [source,ruby] ---- {a: "test"}.fetch_value :a, "default" # "test" {a: "test"}.fetch_value :a # "test" {a: nil}.fetch_value :a, "default" # "default" {}.fetch_value(:a) { "default" } # "default" {}.fetch_value :a # KeyError {a: "test"}.fetch_value # ArgumentError ---- ===== #flatten_keys Flattens nested keys as top-level keys without mutating itself. Does not handle nested arrays, though. [source,ruby] ---- {a: {b: 1}}.flatten_keys prefix: :test # {test_a_b: 1} {a: {b: 1}}.flatten_keys delimiter: :| # {:"a|b" => 1} {a: {b: 1}}.flatten_keys cast: :to_s # {"a_b" => 1} {"a" => {"b" => 1}}.flatten_keys cast: :to_sym # {a_b: 1} example = {a: {b: 1}} example.flatten_keys # {a_b: 1} example # {a: {b: 1}} ---- ===== #flatten_keys! Flattens nested keys as top-level keys while mutating itself. Does not handle nested arrays, though. [source,ruby] ---- example = {a: {b: 1}} example.flatten_keys! # {a_b: 1} example # {a_b: 1} ---- ===== #many? Answers true if a hash has more than one element. Can take a block which evaluates as truthy or falsey. [source,ruby] ---- {a: 1, b: 2}.many? # true {a: 1, b: 2, c: 2}.many? { |_key, value| value == 2 } # true {a: 1}.many? # false {}.many? # false ---- ===== #recurse Recursively iterates over the hash and any hash value by applying the given block to it. Does not handle nested arrays, though. [source,ruby] ---- example = {"a" => {"b" => 1}} example.recurse(&:symbolize_keys) # {a: {b: 1}} example.recurse(&:invert) # {{"b" => 1} => "a"} ---- ===== #stringify_keys Converts keys to strings without mutating itself. [source,ruby] ---- example = {a: 1, b: 2} example.stringify_keys # {"a" => 1, "b" => 2} example # {a: 1, b: 2} ---- ===== #stringify_keys! Converts keys to strings while mutating itself. [source,ruby] ---- example = {a: 1, b: 2} example.stringify_keys! # {"a" => 1, "b" => 2} example # {"a" => 1, "b" => 2} ---- ===== #symbolize_keys Converts keys to symbols without mutating itself. [source,ruby] ---- example = {"a" => 1, "b" => 2} example.symbolize_keys # {a: 1, b: 2} example # {"a" => 1, "b" => 2} ---- ===== #symbolize_keys! Converts keys to symbols while mutating itself. [source,ruby] ---- example = {"a" => 1, "b" => 2} example.symbolize_keys! # {a: 1, b: 2} example # {a: 1, b: 2} ---- ===== #transform_with Transforms key/value pairs based on specific operations where each operation (i.e. function that responds to the `call` message). Does not mutate itself. You can transform multiple values at once: [source,ruby] ---- example = {name: "Jayne Doe", email: ""} example.transform_with name: -> value { value.delete_suffix " Doe" }, email: -> value { value.tr "<>", "" } # {name: "Jayne", email: "jd@example.com"} ---- Invalid keys are ignored: [source,ruby] ---- example.transform_with bogus: -> value { value.tr "<>", "" } # {email: ""} ---- Nil values are skipped: [source,ruby] ---- {email: nil}.transform_with email: -> value { value.tr "<>", "" } # {email: nil} ---- The original object will not be mutated: [source,ruby] ---- example # {name: "Jayne Doe", email: ""} ---- ===== #transform_with! Transforms key/value pairs based on specific operations where each operation (i.e. function that responds to the `call` message). Mutates itself. You can transform multiple values at once: [source,ruby] ---- example = {name: "Jayne Doe", email: ""} example.transform_with! name: -> value { value.delete_suffix " Doe" }, email: -> value { value.tr "<>", "" } # {name: "Jayne", email: "jd@example.com"} ---- Invalid keys are ignored: [source,ruby] ---- example.transform_with! bogus: -> value { value.tr "<>", "" } # {email: ""} ---- Nil values are skipped: [source,ruby] ---- {email: nil}.transform_with! email: -> value { value.tr "<>", "" } # {email: nil} ---- The original object will be mutated: [source,ruby] ---- example # {name: "Jayne", email: "jd@example.com"} ---- ===== #use Passes each hash value as a block argument for further processing. [source,ruby] ---- example = {unit: "221B", street: "Baker Street", city: "London", country: "UK"} example.use { |unit, street| "#{unit} #{street}" } # "221B Baker Street" ---- ==== IO ===== .void Answers an IO stream which points to `/dev/null` in order to ignore any reads or writes to the stream. When given a block, the stream will automatically close upon block exit. When not given a block, you'll need to close the stream manually. [source,ruby] ---- io = IO.void # "#" io = IO.void { |void| void.write "nevermore" } # "#" ---- ===== #redirect Redirects current stream to other stream when given a block. Without a block, the original stream is answered instead. [source,ruby] ---- io = IO.new IO.sysopen(Pathname("test.txt").to_s, "w+") other = IO.new IO.sysopen(Pathname("other.txt").to_s, "w+") io.redirect other # "#" io.redirect(other) { |stream| stream.write "test" } # "#" ---- ===== #reread Answers full stream by rewinding to beginning of stream and reading all content. [source,ruby] ---- io = IO.new IO.sysopen(Pathname("test.txt").to_s, "w+") io.write "This is a test." io.reread # "This is a test." io.reread 4 # "This" buffer = "".dup io.reread(buffer: buffer) # "This is a test." buffer # "This is a test." ---- ===== #squelch Temporarily ignores any reads/writes for code executed within a block. Answers itself without any arguments or when given a block. [source,ruby] ---- io = IO.new IO.sysopen(Pathname("test.txt").to_s, "w+") io.squelch # "#" io.squelch { io.write "Test" } # "#" io.reread # "" ---- ==== LogDevice ===== #reread Answers previously written content by rewinding to beginning of device. [source,ruby] ---- # With File. device = Logger::LogDevice.new "test.log" device.write "Test." device.reread # "Test." # With StringIO. device = Logger::LogDevice.new StringIO.new device.write "Test." device.reread # "Test." # With STDOUT. device = Logger::LogDevice.new $stdout device.write "Test." device.reread # "" ---- ==== Logger ===== #reread Answers previously written content by rewinding to beginning of log. [source,ruby] ---- # With File. logger = Logger.new "test.log" logger.write "Test." logger.reread # "Test." # With StringIO. logger = Logger.new StringIO.new logger.write "Test." logger.reread # "Test." # With STDOUT. logger = Logger.new $stdout logger.write "Test." logger.reread # "" ---- ==== Pathname ===== Pathname Enhances the `Kernel` conversion function which casts `nil` into a pathname in order to avoid: `TypeError (no implicit conversion of nil into String)`. The pathname remains invalid but at least you have an instance of `Pathname`, which behaves like a _Null Object_, that can be used to construct a valid path. [source,ruby] ---- Pathname(nil) # Pathname("") ---- ===== .home Answers user home directory. [source,ruby] ---- Pathname.home # Pathname "/Users/demo" ---- ===== .make_temp_dir Wraps `Dir.mktmpdir` with the following behavior (see link:https://rubyapi.org/o/Dir.mktmpdir#method-c-mktmpdir[Dir.mktmpdir] for details): * *Without Block* - Answers a newly created Pathname instance which is not automatically cleaned up. * *With Block* Yields a Pathname instance, answers result of given block, and automatically cleans up temporary directory after block exits. The following examples use truncated temporary directories for illustration purposes only. In reality, these paths will be longer depending on which operating system you are using. [source,ruby] ---- Pathname.make_temp_dir # Pathname:/var/folders/T/temp-20200101-16940-r8 Pathname.make_temp_dir prefix: "prefix-" # Pathname:/var/folders/T/prefix-20200101-16940-r8 Pathname.make_temp_dir suffix: "-suffix" # Pathname:/var/folders/T/temp-20200101-16940-r8-suffix Pathname.make_temp_dir prefix: "prefix-", suffix: "-suffix" # Pathname:/var/folders/T/prefix-20200101-16940-r8-suffix Pathname.make_temp_dir root: "/example" # Pathname:/example/temp-20200101-16940-r8 Pathname.make_temp_dir { "I am a block result" } # "I am a block result" Pathname.make_temp_dir { |path| path.join "sub_dir" } # Pathname:/var/folders/T/temp-20200101-16940-r8/sub_dir ---- ===== .require_tree Requires all files in given root path and corresponding nested tree structure. All files are sorted before being required to ensure consistent behavior. Example: [source,ruby] ---- # Before Dir[File.join(__dir__, "support/shared_contexts/**/*.rb")].sort.each { |path| require path } # After Pathname.require_tree __dir__, "support/shared_contexts/**/*.rb" ---- The following are further examples of potential usage: [source,ruby] ---- # Requires all files in root directory and below. Pathname.require_tree __dir__ # Requires all files in `/test/**/*.rb` and below. Pathname.require_tree "/test" # Requires all files in RSpec shared examples directory structure. Pathname.require_tree Bundler.root.join("spec"), "support/shared_examples/**/*.rb" ---- ===== .root Answers operating system root path. [source,ruby] ---- Pathname.root # Pathname "/" ---- ===== #change_dir Wraps `Dir.chdir` behavior by changing to directory of current path. See link:https://rubyapi.org/o/Dir.chdir#method-c-chdir[Dir.chdir] for details. [source,ruby] ---- current = Pathname.pwd # "$HOME/demo" (Present Working Directory) custom = current.join("test").make_dir # Pathname "$HOME/demo/test" custom.change_dir # "$HOME/demo/test" (Present Working Directory) current.change_dir # "$HOME/demo" (Present Working Directory) custom.change_dir { "example" } # "example" custom.change_dir { |path| path } # Pathname "$HOME/demo/test" Pathname.pwd # "$HOME/demo" (Present Working Directory) ---- ===== #copy Copies file from current location to new location while answering itself so it can be chained. [source,ruby] ---- Pathname("input.txt").copy Pathname("output.txt") # Pathname("input.txt") ---- ===== #deep_touch Has all of the same functionality as the `#touch` method while being able to create ancestor directories no matter how deeply nested the file might be. [source,ruby] ---- Pathname("a/b/c/d.txt").touch # Pathname("a/b/c/d.txt") Pathname("a/b/c/d.txt").touch Time.now - 1 # Pathname("a/b/c/d.txt") ---- ===== #delete Deletes file or directory and answers itself so it can be chained. [source,ruby] ---- # When path exists. Pathname("/example.txt").touch.delete # Pathname("/example") # When path doesn't exist. Pathname("/example.txt").delete # Errno::ENOENT ---- ===== #delete_prefix Deletes a path prefix and answers new pathname. [source,ruby] ---- Pathname("a/path/example-test.rb").delete_prefix("example-") # Pathname("a/path/test.rb") Pathname("example-test.rb").delete_prefix("example-") # Pathname("test.rb") Pathname("example-test.rb").delete_prefix("miss") # Pathname("example-test.rb") ---- ===== #delete_suffix Deletes a path suffix and answers new pathname. [source,ruby] ---- Pathname("a/path/test-example.rb").delete_suffix("-example") # Pathname("a/path/test.rb") Pathname("test-example.rb").delete_suffix("-example") # Pathname("test.rb") Pathname("test-example.rb").delete_suffix("miss") # Pathname("test-example.rb") ---- ===== #directories Answers all directories or filtered directories for current path. [source,ruby] ---- Pathname("/example").directories # [Pathname("a"), Pathname("b")] Pathname("/example").directories "a*" # [Pathname("a")] Pathname("/example").directories flag: File::FNM_DOTMATCH # [Pathname(".."), Pathname(".")] ---- ===== #empty Empties a directory of children (i.e. folders, nested folders, or files) or clears an existing file of contents. If a directory or file doesn't exist, it will be created. [source,ruby] ---- directory = Pathname("test").make_path file = directory.join("test.txt").write("example") file.empty.read # "" directory.empty.children # [] ---- ===== #extensions Answers file extensions as an array. [source,ruby] ---- Pathname("example.txt.erb").extensions # [".txt", ".erb"] ---- ===== #files Answers all files or filtered files for current path. [source,ruby] ---- Pathname("/example").files # [Pathname("a.txt"), Pathname("a.png")] Pathname("/example").files "*.png" # [Pathname("a.png")] Pathname("/example").files flag: File::FNM_DOTMATCH # [Pathname(".ruby-version")] ---- ===== #gsub Same behavior as `String#gsub` but answers a path with patterns replaced with desired substitutes. [source,ruby] ---- Pathname("/a/path/some/path").gsub("path", "test") # Pathname("/a/test/some/test") Pathname("/%placeholder%/some/%placeholder%").gsub("%placeholder%", "test") # Pathname("/test/some/test") ---- ===== #make_ancestors Ensures all ancestor directories are created for a path. [source,ruby] ---- Pathname("/one/two").make_ancestors # Pathname("/one/two") Pathname("/one").exist? # true Pathname("/one/two").exist? # false ---- ===== #make_dir Provides alternative `#mkdir` behavior by always answering itself (even when directory exists) and not throwing errors when directory does exist in order to ensure the pathname can be chained. [source,ruby] ---- Pathname("/one").make_dir # Pathname("/one") Pathname("/one").make_dir.make_dir # Pathname("/one") ---- ===== #make_path Provides alternative `#mkpath` behavior by always answering itself (even when full path exists) and not throwing errors when directory does exist in order to ensure the pathname can be chained. [source,ruby] ---- Pathname("/one/two/three").make_path # Pathname("/one/two/three") Pathname("/one/two/three").make_path.make_path # Pathname("/one/two/three") ---- ===== #name Answers file name without extension. [source,ruby] ---- Pathname("example.txt").name # Pathname("example") ---- ===== #relative_parent Answers relative path from parent directory. This is a complement to `#relative_path_from`. [source,ruby] ---- Pathname("/one/two/three").relative_parent("/one") # Pathname "two" ---- ===== #remove_dir Provides alternative `#rmdir` behavior by always answering itself (even when full path exists) and not throwing errors when directory does exist in order to ensure the pathname can be chained. [source,ruby] ---- Pathname("/test").make_dir.remove_dir.exist? # false Pathname("/test").remove_dir # Pathname("/test") Pathname("/test").remove_dir.remove_dir # Pathname("/test") ---- ===== #remove_tree Provides alternative `#rmtree` behavior by always answering itself (even when full path exists) and not throwing errors when directory does exist in order to ensure the pathname can be chained. [source,ruby] ---- parent_path = Pathname "/one" child_path = parent_path.join "two" child_path.make_path parent_path.remove_tree # Pathname "/one" child_path.exist? # false parent_path.exist? # false child_path.make_path child_path.remove_tree # Pathname "/one/two" child_path.exist? # false parent_path.exist? # true ---- ===== #rewrite When given a block, it provides the contents of the recently read file for manipulation and immediate writing back to the same file. [source,ruby] ---- Pathname("/test.txt").rewrite # Pathname("/test.txt") Pathname("/test.txt").rewrite { |body| body.sub "[token]", "example" } # Pathname("/test.txt") ---- ===== #touch Updates access and modification times for an existing path by defaulting to current time. When path doesn't exist, it will be created as a file. [source,ruby] ---- Pathname("example").touch # Pathname("example") Pathname("example").touch Time.now - 1 # Pathname("example") Pathname("example.txt").touch # Pathname("example.txt") Pathname("example.txt").touch Time.now - 1 # Pathname("example.txt") ---- ===== #write Writes to file and answers itself so it can be chained. See `IO.write` for details on additional options. [source,ruby] ---- Pathname("example.txt").write "test" # Pathname("example.txt") Pathname("example.txt").write "test", offset: 1 # Pathname("example.txt") Pathname("example.txt").write "test", mode: "a" # Pathname("example.txt") ---- ==== String ===== #blank? Answers `true`/`false` based on whether string is blank, ``, `\n`, `\t`, and/or `\r`. [source,ruby] ---- " \n\t\r".blank? # true ---- ===== #camelcase Answers a camel cased string. [source,ruby] ---- "this_is_an_example".camelcase # "ThisIsAnExample" ---- ===== #down Answers string with only first letter down cased. [source,ruby] ---- "EXAMPLE".down # "eXAMPLE" ---- ===== #first Answers first character of a string or first set of characters if given a number. [source,ruby] ---- "example".first # "e" "example".first 4 # "exam" ---- ===== #indent Answers string indented by two spaces by default. [source,ruby] ---- "example".indent # " example" "example".indent 0 # "example" "example".indent -1 # "example" "example".indent 2 # " example" "example".indent 3, padding: " " # " example" ---- ===== #last Answers last character of a string or last set of characters if given a number. [source,ruby] ---- "instant".last # "t" "instant".last 3 # "ant" ---- ===== #pluralize Answers plural form of self when given a suffix to add. The plural form of the word can be dynamically calculated when given a count and a replacement pattern (i.e. string or regular expression) can be supplied for further specificity. Usage is based on link:https://en.wikipedia.org/wiki/English_plurals[plurals in English] which may or may not work well in other languages. [source,ruby] ---- "apple".pluralize "s" # apples "apple".pluralize "s", count: 0 # apples "apple".pluralize "s", count: 1 # apple "apple".pluralize "s", count: -1 # apple "apple".pluralize "s", count: 2 # apples "apple".pluralize "s", count: -2 # apples "cactus".pluralize "i", replace: "us" # cacti "cul-de-sac".pluralize "ls", replace: "l" # culs-de-sac ---- ===== #singularize Answers singular form of self when given a suffix to remove (can be a string or a regular expression). The singular form of the word can be dynamically calculated when given a count and a replacement string can be supplied for further specificity. Usage is based on link:https://en.wikipedia.org/wiki/English_plurals[plurals in English] which may or may not work well in other languages. [source,ruby] ---- "apples".singularize "s" # apple "apples".singularize "s", count: 0 # apples "apples".singularize "s", count: 1 # apple "apples".singularize "s", count: -1 # apple "apples".singularize "s", count: 2 # apples "apples".singularize "s", count: -2 # apples "cacti".singularize "i", replace: "us" # cactus "culs-de-sac".singularize "ls", replace: "l" # cul-de-sac ---- ===== #snakecase Answers a snake cased string. [source,ruby] ---- "ThisIsAnExample".snakecase # "this_is_an_example" ---- ===== #squish Removes leading, in body, and trailing whitespace, including any tabs or newlines, without mutating itself. Processes ASCII and unicode whitespace as well. [source,ruby] ---- "one two three".squish # "one two three" " one two \n \t three ".squish # "one two three" ---- ===== #titleize Answers a title string with proper capitalization of each word. [source,ruby] ---- "ThisIsAnExample".titleize # "This Is An Example" ---- ===== #to_bool Answers string as a boolean. [source,ruby] ---- "true".to_bool # true "yes".to_bool # true "1".to_bool # true "".to_bool # false "example".to_bool # false ---- ===== #up Answers string with only first letter capitalized. [source,ruby] ---- "example".up # "Example" ---- ==== String IO ===== #reread Answers full string by rewinding to beginning of string and reading all content. [source,ruby] ---- io = StringIO.new io.write "This is a test." io.reread # "This is a test." io.reread 4 # "This" buffer = "".dup io.reread(buffer: buffer) # "This is a test." buffer # "This is a test." ---- ==== Struct ===== .keyworded? ⚠️ Will be removed in the next major version. Use `.keyword_init?` instead. Answers whether a struct was constructed with keyword or positional arguments. [source,ruby] ---- Struct.new(:a, keyword_init: true).keyworded? # true Struct.new(:a).keyworded? # false ---- ===== .with_keywords Answers a struct instance with given keyword arguments regardless of whether the struct was constructed with positional or keyword arguments. [source,ruby] ---- Example = Struct.new :a, :b, :c Example.with_keywords a: 1, b: 2, c: 3 # # Example.with_keywords a: 1 # # Example.with_keywords c: 1 # # Example = Struct.new :a, :b, :c, keyword_init: true Example.with_keywords a: 1, b: 2, c: 3 # # Example.with_keywords a: 1 # # Example.with_keywords c: 1 # # ---- ===== .with_positions Answers a struct instance with given positional arguments regardless of whether the struct was constructed with positional or keyword arguments. [source,ruby] ---- Example = Struct.new :a, :b, :c Example.with_positions 1, 2, 3 # # Example.with_positions 1 # # Example = Struct.new :a, :b, :c, keyword_init: true Example.with_positions 1, 2, 3 # # Example.with_positions 1 # # ---- ===== #merge Merges multiple attributes without mutating itself and supports any object that responds to `#to_h`. Works regardless of whether the struct is constructed with positional or keyword arguments. [source,ruby] ---- example = Struct.new("Example", :a, :b, :c).new 1, 2, 3 other = Struct.new("Other", :a, :b, :c).new 7, 8, 9 example.merge a: 10 # # example.merge a: 10, c: 30 # # example.merge a: 10, b: 20, c: 30 # # example.merge other # # example # # ---- ===== #merge! Merges multiple attributes while mutating itself and supports any object that responds to `#to_h`. Works regardless of whether the struct is constructed with positional or keyword arguments. [source,ruby] ---- example = Struct.new("Example", :a, :b, :c).new 1, 2, 3 other = Struct.new("Other", :a, :b, :c).new 7, 8, 9 example.merge! a: 10 # # example.merge! a: 10, c: 30 # # example.merge! other # # example.merge! a: 10, b: 20, c: 30 # # example # # ---- ===== #revalue Transforms values without mutating itself. An optional hash can be supplied to target specific attributes. In the event that a block isn't supplied, the struct will answer itself since there is nothing to operate on. Behavior is the same regardless of whether the struct is constructed using positional or keyword arguments. Works regardless of whether the struct is constructed with positional or keyword arguments. [source,ruby] ---- example = Struct.new("Example", :a, :b, :c).new 1, 2, 3 example.revalue { |value| value * 2 } # # example.revalue(c: 2) { |previous, current| previous + current } # # example.revalue c: 2 # # example.revalue # # example # # ---- ===== #revalue! Transforms values while mutating itself. An optional hash can be supplied to target specific attributes. In the event that a block isn't supplied, the struct will answer itself since there is nothing to operate on. Behavior is the same regardless of whether the struct is constructed using positional or keyword arguments. Works regardless of whether the struct is constructed with positional or keyword arguments. [source,ruby] ---- one = Struct.new("One", :a, :b, :c).new 1, 2, 3 one.revalue! { |value| value * 2 } # # one # # two = Struct.new("Two", :a, :b, :c).new 1, 2, 3 two.revalue!(c: 2) { |previous, current| previous + current } # # two # # three = Struct.new("Three", :a, :b, :c).new 1, 2, 3 three.revalue! c: 2 # # three.revalue! # # three # # ---- ===== #transmute Transmutes given enumerable by using the foreign key map and merging those key values into the current struct while not mutating itself. Works regardless of whether the struct is constructed with positional or keyword arguments. [source,ruby] ---- a = Struct.new("A", :a, :b, :c).new 1, 2, 3 b = Struct.new("B", :x, :y, :z).new 7, 8, 9 c = {r: 10, s: 20, t: 30} a.transmute b, a: :x, b: :y, c: :z # # a.transmute b, b: :y # # a.transmute c, c: :t # # a # # ---- ===== #transmute! Transmutes given enumerable by using the foreign key map and merging those key values into the current struct while mutating itself. Works regardless of whether the struct is constructed with positional or keyword arguments. [source,ruby] ---- a = Struct.new("A", :a, :b, :c).new 1, 2, 3 b = Struct.new("B", :x, :y, :z).new 7, 8, 9 c = {r: 10, s: 20, t: 30} a.transmute! b, a: :x, b: :y, c: :z # # a.transmute! b, b: :y # # a.transmute! c, c: :t # # a # # ---- ==== Symbol ===== #call Enhances symbol-to-proc by allowing you to send additional arguments and/or a block. This only works with public methods in order to not break encapsulation. [source,ruby] ---- %w[clue crow cow].map(&:tr.call("c", "b")) # ["blue", "brow", "bow"] [%w[a b c], %w[c a b]].map(&:index.call { |element| element == "b" }) # [1, 2] %w[1.outside 2.inside].map(&:sub.call(/\./) { |bullet| bullet + " " }) # ["1. outside", "2. inside"] [1, 2, 3].map(&:to_s.call) # ["1", "2", "3"] ---- ⚠️ Use of `#call` without any arguments or block should be avoided in order to not incur extra processing costs since the original symbol-to-proc call can used instead. == Development To contribute, run: [source,bash] ---- git clone https://github.com/bkuhlmann/refinements cd refinements bin/setup ---- You can also use the IRB console for direct access to all objects: [source,bash] ---- bin/console ---- == Tests To test, run: [source,bash] ---- bundle exec rake ---- == link:https://www.alchemists.io/policies/license[License] == link:https://www.alchemists.io/policies/security[Security] == link:https://www.alchemists.io/policies/code_of_conduct[Code of Conduct] == link:https://www.alchemists.io/policies/contributions[Contributions] == link:https://www.alchemists.io/projects/refinements/versions[Versions] == link:https://www.alchemists.io/community[Community] == Credits * Built with link:https://www.alchemists.io/projects/gemsmith[Gemsmith]. * Engineered by link:https://www.alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].