### Install Value Semantics Gem Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Provides instructions for installing the Value Semantics gem. Users can add the gem to their Gemfile and run bundle, or install it directly using the gem install command. ```ruby gem 'value_semantics' ``` ```bash $ bundle # or $ gem install value_semantics ``` -------------------------------- ### Install Value Semantics Gem Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Instructions for adding the value_semantics gem to your project's Gemfile and installing it using Bundler, or installing it directly via RubyGems. ```ruby gem 'value_semantics' ``` ```shell $ bundle # Or install it yourself as: $ gem install value_semantics ``` -------------------------------- ### Ruby: ValueSemantics Custom Validator Example Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Illustrates how to define and use a custom validator in Ruby with ValueSemantics. The example creates a `DottedQuad` validator to check if a string represents a valid IP address format. It shows the validator module and its integration into a class. ```ruby module DottedQuad def self.===(value) value.split('.').all? do |part| ('0'..'255').cover?(part) end end end class Server include ValueSemantics.for_attributes { address DottedQuad } end Server.new(address: '127.0.0.1') #=> Server.new(address: '127.0.0.999') #=> !!! ``` -------------------------------- ### Ruby: ValueSemantics Coercion with Existing Method Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Demonstrates using an existing method reference for coercion in Ruby with ValueSemantics. The example reuses the `Pathname.method(:new)` to convert values into `Pathname` objects, providing a concise coercion implementation. ```ruby class Document include ValueSemantics.for_attributes { path Pathname, coerce: Pathname.method(:new) } end ``` -------------------------------- ### Define Value Semantics Attributes in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Demonstrates how to correctly define attributes for value objects using the ValueSemantics gem. The first example shows an incorrect syntax that causes a SyntaxError, while the second example shows the correct usage with `def_attr`. ```ruby class Conditional include ValueSemantics.for_attributes { then String else String } end ``` ```ruby class Conditional include ValueSemantics.for_attributes { def_attr :then, String def_attr :else, String } end ``` -------------------------------- ### Ruby: ValueSemantics Coercion with Lambda Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Shows an alternative way to implement coercion in Ruby with ValueSemantics using a lambda function. This example demonstrates coercing a string value into a `Pathname` object via a lambda provided to the `coerce` option. ```ruby class Document include ValueSemantics.for_attributes { path Pathname, coerce: ->(value) { Pathname.new(value) } } end ``` -------------------------------- ### Coercion with Callable Objects in Value Semantics Source: https://context7.com/tomdalling/value_semantics/llms.txt Illustrates advanced coercion techniques in Value Semantics using callable objects like lambdas and procs. This allows for more dynamic and flexible data transformation, demonstrated with examples for coercing strings to integers, converting array-like hashes to actual hashes, and parsing JSON strings. ```ruby require 'value_semantics' require 'json' class ApiResponse include ValueSemantics.for_attributes { # Lambda coercer status_code Integer, coerce: ->(v) { v.to_i } # Method reference as coercer headers HashOf(String => String), coerce: :to_h.to_proc # JSON parsing coercer body coerce: ->(v) { v.is_a?(String) ? JSON.parse(v) : v } } end response = ApiResponse.new( status_code: "200", headers: [["Content-Type", "application/json"], ["X-Request-Id", "abc123"]], body: '{"success": true, "data": [1, 2, 3]}' ) response.status_code #=> 200 response.headers #=> {"Content-Type"=>"application/json", "X-Request-Id"=>"abc123"} response.body #=> {"success"=>true, "data"=>[1, 2, 3]} ``` -------------------------------- ### Ruby: ValueSemantics Coercion with Custom Method Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Demonstrates how to implement attribute coercion in Ruby using ValueSemantics by defining a custom class method `coerce_#{attr}`. The example shows coercing string paths into `Pathname` objects, handling both string and `Pathname` inputs, and invalid inputs. ```ruby require 'pathname' class Document include ValueSemantics.for_attributes { path Pathname, coerce: true } def self.coerce_path(value) if value.is_a?(String) Pathname.new(value) else value end end end Document.new(path: '~/Documents/whatever.doc') #=> Document.new(path: Pathname.new('~/Documents/whatever.doc')) #=> Document.new(path: 42) #=> !!! ``` -------------------------------- ### Custom Validators in Value Semantics Source: https://context7.com/tomdalling/value_semantics/llms.txt Explains how to create custom validators in Value Semantics by implementing the `===` method. This allows for flexible validation logic, including using modules, lambdas, and regular expressions. Examples demonstrate defining custom email format and positive number validators. ```ruby require 'value_semantics' # Custom validator using a module module EmailFormat EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i def self.===(value) value.is_a?(String) && EMAIL_REGEX.match?(value) end end # Custom validator using a lambda PositiveNumber = ->(value) { value.is_a?(Numeric) && value > 0 } class Account include ValueSemantics.for_attributes { email EmailFormat balance PositiveNumber # Regular expressions work as validators too username /\A[a-z][a-z0-9_]{2,19}\z/i } end account = Account.new( email: "user@example.com", balance: 100.50, username: "john_doe" ) account.email #=> "user@example.com" # Invalid email raises exception Account.new(email: "invalid", balance: 100, username: "john") #=> ValueSemantics::InvalidValue: Some attributes of `Account` are invalid: #=> - email: "invalid" ``` -------------------------------- ### Ruby: ValueSemantics Built-in Coercers (Array, Hash) Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Shows the use of built-in coercers in Ruby's ValueSemantics DSL, specifically `ArrayCoercer` and `HashCoercer`. The example demonstrates coercing string arrays into `Pathname` objects and a hash with string keys and string values into symbols and integers. ```ruby require 'pathname' class Config include ValueSemantics.for_attributes { # ArrayCoercer: takes an element coercer paths coerce: ArrayCoercer(Pathname.method(:new)) # HashCoercer: takes a key and value coercer env coerce: HashCoercer( keys: :to_sym.to_proc, values: :to_i.to_proc, ) } end config = Config.new( paths: ['/a', '/b'], env: { 'AAAA' => '1', 'BBBB' => '2' }, ) config.paths #=> config.env #=> ``` -------------------------------- ### Built-in Validators in Value Semantics Source: https://context7.com/tomdalling/value_semantics/llms.txt Demonstrates the use of various built-in validators provided by the Value Semantics gem, including Bool, ArrayOf, HashOf, RangeOf, and Either. It shows how to define these validators for class attributes and provides examples of valid and invalid data, highlighting how invalid values raise exceptions. ```ruby require 'value_semantics' class GameSettings include ValueSemantics.for_attributes { # Bool: only true or false (not truthy/falsy) sound_enabled Bool() # ArrayOf: validates each element in array player_ids ArrayOf(Integer) # HashOf: validates keys and values of hash scores HashOf(String => Integer) # RangeOf: validates range endpoints difficulty_range RangeOf(Integer) # Either: matches any of the given validators theme Either(:dark, :light, :auto) # Composable validators optional_tags Either(ArrayOf(String), nil), default: nil } end settings = GameSettings.new( sound_enabled: true, player_ids: [1, 2, 3], scores: { "player1" => 100, "player2" => 85 }, difficulty_range: (1..10), theme: :dark, optional_tags: ["rpg", "adventure"] ) settings.player_ids #=> [1, 2, 3] settings.scores #=> {"player1"=>100, "player2"=>85} settings.difficulty_range #=> 1..10 # Invalid values raise exceptions GameSettings.new( sound_enabled: "yes", # Must be true/false, not string player_ids: [1, 2, 3], scores: {}, difficulty_range: (1..10), theme: :dark ) #=> ValueSemantics::InvalidValue: Some attributes of `GameSettings` are invalid: #=> - sound_enabled: "yes" ``` -------------------------------- ### Built-in Array and Hash Coercers in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Explains and demonstrates ValueSemantics' built-in `ArrayCoercer` and `HashCoercer`. `ArrayCoercer` applies a given coercer to each element of an array, while `HashCoercer` applies specified coercers to keys and values of a hash. This example shows coercing array elements to `Pathname` and hash keys/values. ```ruby class Config include ValueSemantics.for_attributes { # ArrayCoercer: takes an element coercer paths coerce: ArrayCoercer(Pathname.method(:new)) # HashCoercer: takes a key and value coercer env coerce: HashCoercer( keys: :to_sym.to_proc, values: :to_i.to_proc, ) } end config = Config.new( paths: ['/a', '/b'], env: { 'AAAA' => '1', 'BBBB' => '2' }, ) config.paths #=> [#, #] config.env #=> {:AAAA=>1, :BBBB=>2} ``` -------------------------------- ### Define Value Object with Type Validation (Ruby) Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Demonstrates defining a 'Person' value object with 'name' validated as a String and 'birthday' validated using a regular expression. It shows examples of successful validation and validation failures. ```ruby class Person include ValueSemantics.for_attributes { name String birthday /\d{4}-\d{2}-\d{2}/ } end Person.new(name: 'Tom', birthday: '2000-01-01') # works Person.new(name: 5, birthday: '2000-01-01') #=> !!! Person.new(name: 'Tom', birthday: "1970-01-01") # works Person.new(name: 'Tom', birthday: "hello") #=> !!! ``` -------------------------------- ### Creating Value Objects with ValueSemantics::Struct in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Introduces `ValueSemantics::Struct`, a convenience class for creating new value object classes and including ValueSemantics in a single step, similar to Ruby's built-in `Struct`. This example defines a `Pigeon` class with a default 'name' attribute. ```ruby Pigeon = ValueSemantics::Struct.new do name String, default: "Jannie" end Pigeon.new.name #=> "Jannie" ``` -------------------------------- ### Coercion with coerce: true in Value Semantics Source: https://context7.com/tomdalling/value_semantics/llms.txt Details how to enable automatic type coercion in Value Semantics by defining a `coerce_#{attribute}` class method and setting `coerce: true`. It provides examples for coercing strings to `Pathname` and `Date` objects, and splitting comma-separated strings into an array of strings for tags. ```ruby require 'value_semantics' require 'pathname' require 'date' class Document include ValueSemantics.for_attributes { path Pathname, coerce: true created_at Date, coerce: true tags ArrayOf(String), coerce: true } def self.coerce_path(value) value.is_a?(String) ? Pathname.new(value) : value end def self.coerce_created_at(value) case value when String then Date.parse(value) when Time then value.to_date else value end end def self.coerce_tags(value) case value when String then value.split(',').map(&:strip) when Array then value.map(&:to_s) else value end end end # Strings are coerced to proper types doc = Document.new( path: "~/documents/report.pdf", created_at: "2024-01-15", tags: "work, important, 2024" ) doc.path #=> # doc.created_at #=> # doc.tags #=> ["work", "important", "2024"] # Already-correct types pass through unchanged doc2 = Document.new( path: Pathname.new("/tmp/file.txt"), created_at: Date.today, tags: ["misc"] ) ``` -------------------------------- ### Enable Concise DSL with Monkey Patch Source: https://context7.com/tomdalling/value_semantics/llms.txt Explains how to enable a shorter DSL syntax for defining value classes by adding a `value_semantics` method to all classes. This requires explicitly requiring `value_semantics/monkey_patched` or calling `ValueSemantics.monkey_patch!`. This approach simplifies the definition process for value objects. ```ruby require 'value_semantics/monkey_patched' # Or call: ValueSemantics.monkey_patch! class User value_semantics do username String role Either(:admin, :user, :guest), default: :guest active Bool(), default: true end end user = User.new(username: "admin_user", role: :admin) user.username #=> "admin_user" user.role #=> :admin user.active #=> true ``` -------------------------------- ### Instance Methods: with, to_h, and Equality in Ruby Value Objects Source: https://context7.com/tomdalling/value_semantics/llms.txt Describes the fundamental instance methods provided by Value Semantics objects: `#with` for non-destructive updates, `#to_h` for converting the object to a hash, and implicit equality comparison. ```ruby require 'value_semantics' class Product include ValueSemantics.for_attributes { name String price Float quantity Integer, default: 0 } end product = Product.new(name: "Widget", price: 29.99, quantity: 100) # Non-destructive update with #with discounted = product.with(price: 24.99) restocked = product.with(quantity: product.quantity + 50) product.price #=> 29.99 (unchanged) discounted.price #=> 24.99 restocked.quantity #=> 150 # Convert to hash product.to_h #=> {:name=>"Widget", :price=>29.99, :quantity=>100} ``` -------------------------------- ### Hash-Based Equality and Set Usage (Ruby) Source: https://context7.com/tomdalling/value_semantics/llms.txt Demonstrates hash-based equality for value objects, which is leveraged by `Set` and `Hash` data structures. It shows that value objects with the same attributes produce the same hash code, allowing Sets to correctly identify and store unique value objects. ```ruby require 'set' products = Set.new([product, product2, product3]) products.size #=> 2 (product and product2 are considered equal) ``` -------------------------------- ### Basic Value Object Usage and Features Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Illustrates the basic usage of a value object 'Person' defined with ValueSemantics. It covers attribute access (dot and hash notation), conversion to a Hash, non-destructive updates, equality comparisons, and pattern matching. ```ruby require 'value_semantics' class Person include ValueSemantics.for_attributes { name age default: 31 } end tom = Person.new(name: 'Tom') # Read-only attributes tom.name #=> "Tom" tom[:name] #=> "Tom" # Convert to Hash tom.to_h #=> {:name=>"Tom", :age=>31} # Non-destructive updates tom.with(age: 99) #=> # tom # (unchanged) #=> # # Equality other_tom = Person.new(name: 'Tom', age: 31) tom == other_tom #=> true tom.eql?(other_tom) #=> true tom.hash == other_tom.hash #=> true # Ruby 2.7+ pattern matching case tom in name: "Tom", age: puts age end ``` -------------------------------- ### Equality Comparison for Value Objects (Ruby) Source: https://context7.com/tomdalling/value_semantics/llms.txt Compares two `Product` value objects for equality using `==` and `eql?`. It highlights that objects with identical attribute values are considered equal, while objects with different values are not. This functionality is essential for value object behavior. ```ruby product2 = Product.new(name: "Widget", price: 29.99, quantity: 100) product3 = Product.new(name: "Gadget", price: 29.99, quantity: 100) product == product2 #=> true product.eql?(product2) #=> true product == product3 #=> false ``` -------------------------------- ### Reusing Existing Method for Pathname Coercion in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Demonstrates how to reuse an existing method, such as `Pathname.method(:new)`, as a coercer. This is a clean way to leverage existing functionality for attribute coercion without writing new methods or lambdas. ```ruby class Document include ValueSemantics.for_attributes { path Pathname, coerce: Pathname.method(:new) } end ``` -------------------------------- ### Use Value Object with Attributes and Defaults (Ruby) Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Shows how to use a 'Person' value object with 'name' and 'age' attributes, including a default value for 'age'. It covers reading attributes, converting to a Hash, non-destructive updates, and equality comparisons. ```ruby require 'value_semantics' class Person include ValueSemantics.for_attributes { name age default: 31 } end tom = Person.new(name: 'Tom') # Read-only attributes tom.name #=> tom[:name] #=> # Convert to Hash tom.to_h #=> # Non-destructive updates tom.with(age: 99) #=> tom # (unchanged) #=> # Equality other_tom = Person.new(name: 'Tom', age: 31) tom == other_tom #=> tom.eql?(other_tom) #=> tom.hash == other_tom.hash #=> # Ruby 2.7+ pattern matching case tom in name: "Tom", age: puts age end # outputs: ``` -------------------------------- ### Enable Convenience Method for Value Semantics (Ruby) Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Illustrates how to use the convenience method `value_semantics` for defining value objects, which is an alternative to `ValueSemantics.for_attributes`. This method is enabled by requiring 'value_semantics/monkey_patched' or calling ValueSemantics.monkey_patch!. ```ruby require 'value_semantics/monkey_patched' class Monkey value_semantics do name String age Integer end end ``` ```ruby require 'value_semantics' ValueSemantics.monkey_patch! ``` -------------------------------- ### Define Attribute Defaults with :default and :default_generator Source: https://context7.com/tomdalling/value_semantics/llms.txt Illustrates how to specify default values for attributes using either a static value with the `:default` option or a dynamic value computed at instantiation time with the `:default_generator` option. This is useful for setting initial states or generating unique values like timestamps or UUIDs. ```ruby require 'value_semantics' class Configuration include ValueSemantics.for_attributes { # Static default value timeout Integer, default: 30 retries Integer, default: 3 # Dynamic default (called each time) created_at Time, default_generator: -> { Time.now } request_id String, default_generator: -> { SecureRandom.uuid } } end config1 = Configuration.new config1.timeout #=> 30 config1.created_at #=> 2024-01-15 10:30:45 +0000 sleep(1) config2 = Configuration.new config2.created_at #=> 2024-01-15 10:30:46 +0000 (different time) config2.request_id #=> "a1b2c3d4-..." (different UUID) ``` -------------------------------- ### Coercing Collection Elements with ArrayCoercer and HashCoercer Source: https://context7.com/tomdalling/value_semantics/llms.txt Demonstrates how to use ArrayCoercer and HashCoercer to automatically coerce elements within arrays and key-value pairs in hashes. This is useful for converting input data into specific types, such as Pathname objects or symbols. ```ruby require 'value_semantics' require 'pathname' class ProjectConfig include ValueSemantics.for_attributes { # Coerce each array element with ArrayCoercer source_paths coerce: ArrayCoercer(Pathname.method(:new)) # Coerce hash keys and values with HashCoercer environment coerce: HashCoercer( keys: :to_sym.to_proc, values: :to_s.to_proc ) # Combine with validators include_dirs ArrayOf(Pathname), coerce: ArrayCoercer(Pathname.method(:new)) } end config = ProjectConfig.new( source_paths: ["/src", "/lib", "/app"], environment: { "RAILS_ENV" => :production, "DEBUG" => false }, include_dirs: ["./vendor", "./node_modules"] ) config.source_paths #=> [#, #, #] config.environment #=> {:RAILS_ENV=>"production", :DEBUG=>"false"} config.include_dirs #=> [#, #] ``` -------------------------------- ### Ruby: ValueSemantics Built-in Validators Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Demonstrates the use of built-in ValueSemantics validators in Ruby for common data types and structures. Includes Bool, ArrayOf, HashOf, RangeOf, Either, and composable validators. Shows instantiation with valid data. ```ruby class LightSwitch include ValueSemantics.for_attributes { # Bool: only allows `true` or `false` on? Bool() # ArrayOf: validates elements in an array light_ids ArrayOf(Integer) # HashOf: validates keys/values of a homogeneous hash toggle_stats HashOf(Symbol => Integer) # RangeOf: validates ranges levels RangeOf(Integer) # Either: value must match at least one of a list of validators color Either(Integer, String, nil) # these validators are composable wierd_attr Either(Bool(), ArrayOf(Bool())) } end LightSwitch.new( on?: true, light_ids: [11, 12, 13], toggle_stats: { day: 42, night: 69 }, levels: (0..10), color: "#FFAABB", wierd_attr: [true, false, true, true], ) #=> ``` -------------------------------- ### Error Handling in Value Semantics for Ruby Source: https://context7.com/tomdalling/value_semantics/llms.txt Details the specific exceptions raised by the Value Semantics gem for various error conditions, including missing attributes, invalid attribute values, unrecognized attributes during initialization, and invalid initialization argument types. ```ruby require 'value_semantics' class User include ValueSemantics.for_attributes { name String age Integer role Either(:admin, :user) } end # Missing required attributes begin User.new(name: "Alice") rescue ValueSemantics::MissingAttributes => e puts e.message #=> Some attributes required by `User` are missing: `age`, `role` end # Invalid attribute values begin User.new(name: 123, age: "thirty", role: :guest) rescue ValueSemantics::InvalidValue => e puts e.message #=> Some attributes of `User` are invalid: #=> - name: 123 #=> - age: "thirty" #=> - role: :guest end # Unrecognized attributes begin User.new(name: "Alice", age: 30, role: :admin, unknown: "value") rescue ValueSemantics::UnrecognizedAttributes => e puts e.message #=> `User` does not define attributes: `:unknown` end # Invalid initialization argument begin User.new("not a hash") rescue TypeError => e puts e.message #=> Can not initialize a `User` with a `String` object... ``` -------------------------------- ### Define Value Class with Attributes using ValueSemantics.for_attributes Source: https://context7.com/tomdalling/value_semantics/llms.txt Demonstrates the primary API for defining value classes by including a module generated by `ValueSemantics.for_attributes`. This method allows specifying attribute types, default values, and optional coercers. The generated module provides attribute readers, equality comparison, hash conversion, and non-destructive updates. ```ruby require 'value_semantics' class Person include ValueSemantics.for_attributes { name String age Integer, default: 0 email Either(String, nil), default: nil } end # Create instances with keyword arguments person = Person.new(name: "Alice", age: 30, email: "alice@example.com") #=> # # Access attributes as methods person.name #=> "Alice" person.age #=> 30 person[:name] #=> "Alice" # Convert to hash person.to_h #=> {:name=>"Alice", :age=>30, :email=>"alice@example.com"} # Non-destructive updates (returns new object) updated = person.with(age: 31) #=> # person.age #=> 30 (unchanged) # Equality based on attributes other = Person.new(name: "Alice", age: 30, email: "alice@example.com") person == other #=> true person.eql?(other) #=> true person.hash == other.hash #=> true # Ruby 2.7+ pattern matching case person in name: "Alice", age: puts "Alice is #{age} years old" end #=> Alice is 30 years old ``` -------------------------------- ### Using def_attr for Reserved Words and DSL Conflicts in Ruby Source: https://context7.com/tomdalling/value_semantics/llms.txt Explains the use of the `def_attr` DSL method to define attributes whose names conflict with Ruby reserved words or existing DSL methods. This ensures that such attributes can be properly defined and accessed. ```ruby require 'value_semantics' class Conditional include ValueSemantics.for_attributes { # Reserved words require def_attr def_attr :then, String def_attr :else, String def_attr :end, String, default: "endif" # Regular attributes work normally condition String } end cond = Conditional.new( condition: "x > 0", then: "positive", else: "non-positive" ) cond.condition #=> "x > 0" cond.then #=> "positive" cond.else #=> "non-positive" cond.end #=> "endif" ``` -------------------------------- ### Set Default Attribute Values in Ruby using ValueSemantics Source: https://github.com/tomdalling/value_semantics/blob/master/README.md This Ruby code demonstrates how to set default values for attributes using the `value_semantics` gem. It shows two methods: `:default` for a static value and `:default_generator` for a callable that produces the default value, such as `Time.now`. ```ruby class Cat include ValueSemantics.for_attributes { paws Integer, default: 4 born_at Time, default_generator: ->{ Time.now } } end Cat.new ``` -------------------------------- ### Coercing Nested Value Objects with .coercer Source: https://context7.com/tomdalling/value_semantics/llms.txt Illustrates how to automatically coerce nested value objects from hash representations using the `.coercer` class method. This simplifies the initialization of complex data structures, including nested arrays of value objects. ```ruby require 'value_semantics' class Address include ValueSemantics.for_attributes { street String city String zip String } end class ContactInfo include ValueSemantics.for_attributes { email String phone Either(String, nil), default: nil } end class Person include ValueSemantics.for_attributes { name String address Address, coerce: Address.coercer contact ContactInfo, coerce: ContactInfo.coercer } end class Organization include ValueSemantics.for_attributes { name String # Nested array of value objects members ArrayOf(Person), coerce: ArrayCoercer(Person.coercer) } end # Create from nested hashes (works with string keys too) org = Organization.new( name: "Acme Corp", members: [ { "name" => "Alice", "address" => { "street" => "123 Main St", "city" => "Boston", "zip" => "02101" }, "contact" => { "email" => "alice@acme.com", "phone" => "555-1234" } }, { name: "Bob", address: { street: "456 Oak Ave", city: "Cambridge", zip: "02139" }, contact: { email: "bob@acme.com" } } ] ) org.members.first.name #=> "Alice" org.members.first.address.city #=> "Boston" org.members.first.contact.email #=> "alice@acme.com" org.members.last.contact.phone #=> nil (default) ``` -------------------------------- ### Ruby: ValueSemantics Coercion with Custom Class Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Illustrates coercion in Ruby using ValueSemantics by employing a custom class instance with a `call` method. The `MyPathCoercer` class is defined to convert values into `Pathname` objects, and then instantiated for the `coerce` option. ```ruby class MyPathCoercer def call(value) Pathname.new(value) end end class Document include ValueSemantics.for_attributes { path Pathname, coerce: MyPathCoercer.new } end ``` -------------------------------- ### Create Value Class with ValueSemantics::Struct Source: https://context7.com/tomdalling/value_semantics/llms.txt Introduces `ValueSemantics::Struct`, a convenience class that combines the functionality of `ValueSemantics.for_attributes` with the ease of Ruby's built-in `Struct`. It allows defining value classes in a single step using a block, supporting all DSL features like default values and coercers. ```ruby require 'value_semantics' # Create a class with ValueSemantics in one step Point = ValueSemantics::Struct.new do x Integer y Integer end point = Point.new(x: 10, y: 20) point.x #=> 10 point.y #=> 20 point.to_h #=> {:x=>10, :y=>20} # Supports all DSL features Rectangle = ValueSemantics::Struct.new do origin Point, coerce: Point.coercer width Integer, default: 100 height Integer, default: 100 end rect = Rectangle.new(origin: { x: 0, y: 0 }) rect.origin #=> # rect.width #=> 100 ``` -------------------------------- ### Enable Monkey Patching for Value Semantics in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md This snippet demonstrates how to enable the convenience monkey patching feature for ValueSemantics. This allows defining value attributes more concisely. It can be enabled globally by calling `ValueSemantics.monkey_patch!` or by specifying `require: 'value_semantics/monkey_patched'` in the Gemfile. ```ruby require 'value_semantics' ValueSemantics.monkey_patch! ``` ```ruby gem 'value_semantics', '~> 3.3', require: 'value_semantics/monkey_patched' ``` -------------------------------- ### Define Value Object with Attributes and Coercion (Ruby) Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Demonstrates how to define a value object class 'Person' using ValueSemantics.for_attributes. It includes attributes 'name' and 'birthday', with 'birthday' supporting coercion from a string to a Date object. ```ruby class Person include ValueSemantics.for_attributes { name String, default: "Anon Emous" birthday Either(Date, nil), coerce: true } def self.coerce_birthday(value) if value.is_a?(String) Date.parse(value) else value end end end Person.new(name: "Tom", birthday: "2020-12-25") #=> Person.new(birthday: Date.today) #=> Person.new(birthday: nil) #=> ``` -------------------------------- ### Define Value Object with Default and Default Generator (Ruby) Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Shows how to define a 'Cat' value object with attributes 'paws' having a fixed default value and 'born_at' using a default generator lambda to capture the current time. ```ruby class Cat include ValueSemantics.for_attributes { paws Integer, default: 4 born_at Time, default_generator: ->{ Time.now } } end Cat.new #=> ``` -------------------------------- ### Coercing String to Pathname in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Demonstrates how to use the `coerce: true` option to automatically convert string values to `Pathname` objects for the 'path' attribute. A custom class method `coerce_path` handles the conversion, allowing string inputs to be transformed into `Pathname` objects. If coercion fails, the validation step will catch the invalid type. ```ruby require 'pathname' class Document include ValueSemantics.for_attributes { path Pathname, coerce: true } def self.coerce_path(value) if value.is_a?(String) Pathname.new(value) else value end end end Document.new(path: '~/Documents/whatever.doc') #=> #> Document.new(path: Pathname.new('~/Documents/whatever.doc')) #=> #> Document.new(path: 42) #=> !!! ValueSemantics::InvalidValue: Some attributes of `Document` are invalid: #=* - path: 42 ``` -------------------------------- ### Handle Invalid Attribute Names with def_attr in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Demonstrates how to use `def_attr` when attribute names conflict with Ruby syntax. This allows for attributes like 'then' or 'else'. ```ruby # This will work class Conditional include ValueSemantics.for_attributes { def_attr :then, String def_attr :else, String } end ``` -------------------------------- ### Custom Validator for IP Addresses in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md Defines a custom validator module `DottedQuad` to check if a string represents a valid IPv4 address. It's then used to validate the 'address' attribute of a 'Server' class, ensuring each part of the IP is between '0' and '255'. Invalid values raise a `ValueSemantics::InvalidValue` error. ```ruby module DottedQuad def self.===(value) value.split('.').all? do |part| ('0'..'255').cover?(part) end end end class Server include ValueSemantics.for_attributes { address DottedQuad } end Server.new(address: '127.0.0.1') #=> # Server.new(address: '127.0.0.999') #=> !!! ValueSemantics::InvalidValue: Some attributes of `Server` are invalid: #=* - address: "127.0.0.999" ``` -------------------------------- ### Define Value Semantics Struct in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Creates a new class with value semantics, similar to Ruby's standard Struct. It defines attributes with optional types and default values. ```ruby Pigeon = ValueSemantics::Struct.new do name String, default: "Jannie" end Pigeon.new.name #=> ``` -------------------------------- ### Validate Attribute Types with ValueSemantics in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md This Ruby snippet illustrates attribute validation using the `value_semantics` gem. It shows how to specify expected types for attributes like `name` (String) and use regular expressions for validation, like `birthday` matching a date format. Invalid values trigger a `ValueSemantics::InvalidValue` exception. ```ruby class Person include ValueSemantics.for_attributes { name String birthday /\d{4}-\d{2}-\d{2}/ } end Person.new(name: 'Tom', birthday: '2000-01-01') # works Person.new(name: 5, birthday: '2000-01-01') #=> !!! ValueSemantics::InvalidValue: Some attributes of `Person` are invalid: #=* - name: 5 Person.new(name: 'Tom', birthday: "1970-01-01") # works Person.new(name: 'Tom', birthday: "hello") #=> !!! ValueSemantics::InvalidValue: Some attributes of `Person` are invalid: #=* - birthday: "hello" ``` -------------------------------- ### Ruby: ValueSemantics Nested Object Coercion Source: https://github.com/tomdalling/value_semantics/blob/master/README.eceval.md Illustrates nested coercion in Ruby using ValueSemantics, where value objects are embedded within others. It shows how to use `.coercer` class method for nested structures and combines it with `ArrayCoercer` to handle arrays of nested objects, including ignoring undefined attributes. ```ruby class CrabClaw include ValueSemantics.for_attributes { size Either(:big, :small) } end class Crab include ValueSemantics.for_attributes { left_claw CrabClaw, coerce: CrabClaw.coercer right_claw CrabClaw, coerce: CrabClaw.coercer } end class Ocean include ValueSemantics.for_attributes { crabs ArrayOf(Crab), coerce: ArrayCoercer(Crab.coercer) } end ocean = Ocean.new( crabs: [ { 'left_claw' => { 'size' => :small }, 'right_claw' => { 'size' => :small }, voiced_by: 'Samuel E. Wright', # this attr will be ignored }, { 'left_claw' => { 'size' => :big }, 'right_claw' => { 'size' => :big }, } ] ) ocean.crabs.first #=> ocean.crabs.first.right_claw.size #=> ``` -------------------------------- ### Define Value Attributes with Monkey Patching in Ruby Source: https://github.com/tomdalling/value_semantics/blob/master/README.md This Ruby code snippet shows the concise syntax for defining value attributes using the monkey-patched ValueSemantics DSL. It declares attributes `name` of type `String` and `age` of type `Integer` within a class. ```ruby require 'value_semantics/monkey_patched' class Monkey value_semantics do name String age Integer end end ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.