ivar 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.augment-guidelines +5 -3
  3. data/.devcontainer/devcontainer.json +28 -20
  4. data/.devcontainer/post-create.sh +18 -0
  5. data/.editorconfig +35 -0
  6. data/.rubocop.yml +6 -0
  7. data/.standard.yml +1 -1
  8. data/.vscode/extensions.json +3 -1
  9. data/.vscode/launch.json +25 -0
  10. data/.vscode/settings.json +38 -2
  11. data/CHANGELOG.md +83 -1
  12. data/README.md +272 -207
  13. data/Rakefile +1 -1
  14. data/VERSION.md +44 -0
  15. data/examples/check_all_block_example.rb +84 -0
  16. data/examples/check_all_example.rb +42 -0
  17. data/examples/inheritance_with_kwarg_init.rb +156 -0
  18. data/examples/inheritance_with_positional_init.rb +142 -0
  19. data/examples/mixed_positional_and_kwarg_init.rb +125 -0
  20. data/examples/require_check_all_example.rb +23 -0
  21. data/examples/sandwich_inheritance.rb +1 -1
  22. data/examples/sandwich_with_accessors.rb +78 -0
  23. data/examples/sandwich_with_block_values.rb +54 -0
  24. data/examples/sandwich_with_checked.rb +0 -1
  25. data/examples/sandwich_with_checked_once.rb +0 -1
  26. data/examples/sandwich_with_initial_values.rb +52 -0
  27. data/examples/sandwich_with_ivar_block.rb +6 -9
  28. data/examples/sandwich_with_ivar_macro.rb +4 -4
  29. data/examples/sandwich_with_kwarg_init.rb +78 -0
  30. data/examples/sandwich_with_positional_init.rb +50 -0
  31. data/examples/sandwich_with_shared_values.rb +54 -0
  32. data/hooks/README.md +42 -0
  33. data/hooks/install.sh +12 -0
  34. data/hooks/pre-commit +54 -0
  35. data/ivar.gemspec +5 -4
  36. data/lib/ivar/check_all.rb +7 -0
  37. data/lib/ivar/check_all_manager.rb +72 -0
  38. data/lib/ivar/check_policy.rb +29 -0
  39. data/lib/ivar/checked/class_methods.rb +19 -0
  40. data/lib/ivar/checked/instance_methods.rb +35 -0
  41. data/lib/ivar/checked.rb +17 -24
  42. data/lib/ivar/declaration.rb +30 -0
  43. data/lib/ivar/explicit_declaration.rb +56 -0
  44. data/lib/ivar/explicit_keyword_declaration.rb +24 -0
  45. data/lib/ivar/explicit_positional_declaration.rb +19 -0
  46. data/lib/ivar/macros.rb +48 -111
  47. data/lib/ivar/manifest.rb +124 -0
  48. data/lib/ivar/policies.rb +13 -1
  49. data/lib/ivar/project_root.rb +59 -0
  50. data/lib/ivar/targeted_prism_analysis.rb +144 -0
  51. data/lib/ivar/validation.rb +6 -29
  52. data/lib/ivar/version.rb +1 -1
  53. data/lib/ivar.rb +141 -9
  54. data/script/console +11 -0
  55. data/script/de-lint +2 -0
  56. data/script/de-lint-unsafe +2 -0
  57. data/script/lint +2 -0
  58. data/script/release +134 -0
  59. data/script/setup +8 -0
  60. data/script/test +2 -0
  61. metadata +41 -5
  62. data/examples/sandwich_with_kwarg.rb +0 -45
  63. data/lib/ivar/auto_check.rb +0 -77
  64. data/lib/ivar/prism_analysis.rb +0 -102
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+ require "ivar"
5
+
6
+ # Define a class before enabling check_all
7
+ class BeforeClass
8
+ def initialize
9
+ @name = "before"
10
+ end
11
+
12
+ def to_s
13
+ "Name: #{@naem}" # Intentional typo to demonstrate lack of checking
14
+ end
15
+ end
16
+
17
+ # Define classes that will be used within the block
18
+ class WithinBlockClass
19
+ def initialize
20
+ @name = "within block"
21
+ end
22
+
23
+ def to_s
24
+ "Name: #{@naem}" # Intentional typo to demonstrate lack of checking yet
25
+ end
26
+ end
27
+
28
+ module WithinBlockModule
29
+ class NestedClass
30
+ def initialize
31
+ @name = "nested"
32
+ end
33
+
34
+ def to_s
35
+ "Name: #{@naem}" # Intentional typo to demonstrate lack of checking yet
36
+ end
37
+ end
38
+ end
39
+
40
+ # Only classes loaded within this block will have Ivar::Checked included
41
+ Ivar.check_all do
42
+ # Load the classes by referencing them
43
+ puts "Loading WithinBlockClass: #{WithinBlockClass}"
44
+ puts "Loading WithinBlockModule::NestedClass: #{WithinBlockModule::NestedClass}"
45
+
46
+ # We could also define anonymous classes here
47
+ @anonymous_class = Class.new do
48
+ def initialize
49
+ @name = "anonymous"
50
+ end
51
+
52
+ def to_s
53
+ "Name: #{@naem}" # Intentional typo to demonstrate checking
54
+ end
55
+ end
56
+ end
57
+
58
+ # Define a class after the check_all block
59
+ class AfterClass
60
+ def initialize
61
+ @name = "after"
62
+ end
63
+
64
+ def to_s
65
+ "Name: #{@naem}" # Intentional typo to demonstrate lack of checking
66
+ end
67
+ end
68
+
69
+ # Create instances of each class
70
+ puts "Creating BeforeClass instance:"
71
+ before = BeforeClass.new
72
+ puts before
73
+
74
+ puts "\nCreating WithinBlockClass instance:"
75
+ within = WithinBlockClass.new
76
+ puts within
77
+
78
+ puts "\nCreating WithinBlockModule::NestedClass instance:"
79
+ nested = WithinBlockModule::NestedClass.new
80
+ puts nested
81
+
82
+ puts "\nCreating AfterClass instance:"
83
+ after = AfterClass.new
84
+ puts after
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+ require "ivar"
5
+
6
+ # Enable checking for all classes and modules defined in the project
7
+ Ivar.check_all
8
+
9
+ # Now any class or module defined in the project will have Ivar::Checked included
10
+ class Sandwich
11
+ # No need to include Ivar::Checked manually
12
+
13
+ def initialize
14
+ @bread = "wheat"
15
+ @cheese = "muenster"
16
+ @condiments = ["mayo", "mustard"]
17
+ end
18
+
19
+ def to_s
20
+ "A #{@bread} sandwich with #{@chese} and #{@condiments.join(", ")}" # Intentional typo in @cheese
21
+ end
22
+ end
23
+
24
+ # Create a sandwich - this will automatically check instance variables
25
+ sandwich = Sandwich.new
26
+ puts sandwich
27
+
28
+ # Define another class to demonstrate that it also gets Ivar::Checked
29
+ class Drink
30
+ def initialize
31
+ @type = "soda"
32
+ @size = "medium"
33
+ end
34
+
35
+ def to_s
36
+ "A #{@sise} #{@type}" # Intentional typo in @size
37
+ end
38
+ end
39
+
40
+ # Create a drink - this will also automatically check instance variables
41
+ drink = Drink.new
42
+ puts drink
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ivar"
4
+
5
+ # Base class for all food items
6
+ class Food
7
+ include Ivar::Checked
8
+
9
+ # Declare common properties with keyword initialization and defaults
10
+ ivar :@name, init: :kwarg, value: "Unknown Food"
11
+ ivar :@calories, init: :kwarg, value: 0
12
+ ivar :@vegetarian, init: :kwarg, value: false
13
+ ivar :@vegan, init: :kwarg, value: false
14
+
15
+ # Declare extra_info
16
+ ivar :@extra_info
17
+
18
+ def initialize(extra_info: nil)
19
+ # The declared variables are already initialized from keyword arguments or defaults
20
+ @extra_info = :extra_info
21
+ end
22
+
23
+ def to_s
24
+ result = "#{@name} (#{@calories} calories)"
25
+ result += ", vegetarian" if @vegetarian
26
+ result += ", vegan" if @vegan
27
+ result += ", #{@extra_info}" if @extra_info
28
+ result
29
+ end
30
+ end
31
+
32
+ # Sandwich class that inherits from Food
33
+ class Sandwich < Food
34
+ # Override name with a more specific default
35
+ ivar :@name, value: "Sandwich"
36
+
37
+ # Declare sandwich-specific properties
38
+ ivar :@bread, init: :kwarg, value: "white"
39
+ ivar :@fillings, init: :kwarg, value: []
40
+
41
+ # Override vegetarian with a different default
42
+ ivar :@vegetarian, value: true
43
+
44
+ def initialize(condiments: [], **kwargs)
45
+ # Pass any remaining kwargs to parent
46
+ super(**kwargs)
47
+
48
+ # Initialize fillings if needed
49
+ @fillings ||= []
50
+
51
+ # Add condiments to fillings
52
+ @fillings += condiments
53
+ end
54
+
55
+ def to_s
56
+ result = super
57
+ result += " on #{@bread} bread"
58
+ result += " with #{@fillings.join(", ")}" unless @fillings.empty?
59
+ result
60
+ end
61
+ end
62
+
63
+ # VeganSandwich class that inherits from Sandwich
64
+ class VeganSandwich < Sandwich
65
+ # Override defaults for vegan properties
66
+ ivar :@name, value: "Vegan Sandwich"
67
+ ivar :@vegan, value: true
68
+
69
+ # Override bread default
70
+ ivar :@bread, value: "whole grain"
71
+
72
+ # Add vegan-specific properties
73
+ ivar :@plant_protein, init: :kwarg, value: "tofu"
74
+
75
+ def initialize(**kwargs)
76
+ super
77
+
78
+ # Initialize fillings if needed
79
+ @fillings ||= []
80
+
81
+ # Ensure no non-vegan fillings
82
+ @fillings.reject! { |filling| non_vegan_fillings.include?(filling) }
83
+
84
+ # Add plant protein if fillings are empty
85
+ @fillings << @plant_protein if @fillings.empty?
86
+ end
87
+
88
+ def non_vegan_fillings
89
+ ["cheese", "mayo", "ham", "turkey", "roast beef", "tuna"]
90
+ end
91
+
92
+ def to_s
93
+ result = super
94
+ result += " (#{@plant_protein} based)" if @fillings.include?(@plant_protein)
95
+ result
96
+ end
97
+ end
98
+
99
+ # Create a basic food item with defaults
100
+ puts "Basic food with defaults:"
101
+ food = Food.new
102
+ puts food
103
+ # => "Unknown Food (0 calories)"
104
+
105
+ # Create a food item with custom properties
106
+ puts "\nCustom food:"
107
+ custom_food = Food.new(name: "Apple", calories: 95, vegetarian: true, vegan: true)
108
+ puts custom_food
109
+ # => "Apple (95 calories), vegetarian, vegan"
110
+
111
+ # Create a sandwich with defaults
112
+ puts "\nDefault sandwich:"
113
+ sandwich = Sandwich.new
114
+ puts sandwich
115
+ # => "Sandwich (0 calories), vegetarian on white bread"
116
+
117
+ # Create a sandwich with custom properties
118
+ puts "\nCustom sandwich:"
119
+ custom_sandwich = Sandwich.new(
120
+ name: "Club Sandwich",
121
+ calories: 450,
122
+ bread: "sourdough",
123
+ fillings: ["turkey", "bacon", "lettuce", "tomato"],
124
+ condiments: ["mayo", "mustard"],
125
+ extra_info: "triple-decker"
126
+ )
127
+ puts custom_sandwich
128
+ # => "Club Sandwich (450 calories), vegetarian on sourdough bread with turkey, bacon, lettuce, tomato, mayo, mustard, triple-decker"
129
+
130
+ # Create a vegan sandwich with defaults
131
+ puts "\nDefault vegan sandwich:"
132
+ vegan_sandwich = VeganSandwich.new
133
+ puts vegan_sandwich
134
+ # => "Vegan Sandwich (0 calories), vegetarian, vegan on whole grain bread with tofu (tofu based)"
135
+
136
+ # Create a vegan sandwich with custom properties
137
+ puts "\nCustom vegan sandwich:"
138
+ custom_vegan = VeganSandwich.new(
139
+ name: "Mediterranean Vegan",
140
+ calories: 380,
141
+ bread: "pita",
142
+ fillings: ["hummus", "falafel", "lettuce", "tomato", "cucumber"],
143
+ plant_protein: "chickpeas",
144
+ extra_info: "with tahini sauce"
145
+ )
146
+ puts custom_vegan
147
+ # => "Mediterranean Vegan (380 calories), vegetarian, vegan on pita bread with hummus, falafel, lettuce, tomato, cucumber, with tahini sauce"
148
+
149
+ # Try to create a vegan sandwich with non-vegan fillings
150
+ puts "\nVegan sandwich with non-vegan fillings (will be removed):"
151
+ non_vegan_fillings = VeganSandwich.new(
152
+ fillings: ["cheese", "ham", "lettuce", "tomato"],
153
+ plant_protein: "seitan"
154
+ )
155
+ puts non_vegan_fillings
156
+ # => "Vegan Sandwich (0 calories), vegetarian, vegan on whole grain bread with lettuce, tomato, seitan (seitan based)"
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ivar"
4
+
5
+ # Base class for all food items
6
+ class Food
7
+ include Ivar::Checked
8
+
9
+ # Declare common properties with positional initialization and defaults
10
+ ivar :@name, init: :positional, value: "Unknown Food"
11
+ ivar :@calories, init: :positional, value: 0
12
+ ivar :@vegetarian, init: :positional, value: false
13
+ ivar :@vegan, init: :positional, value: false
14
+
15
+ # Declare extra_info
16
+ ivar :@extra_info
17
+
18
+ def initialize(extra_info = nil)
19
+ # The declared variables are already initialized from positional arguments or defaults
20
+ @extra_info = extra_info
21
+ end
22
+
23
+ def to_s
24
+ result = "#{@name} (#{@calories} calories)"
25
+ result += ", vegetarian" if @vegetarian
26
+ result += ", vegan" if @vegan
27
+ result += ", #{@extra_info}" if @extra_info
28
+ result
29
+ end
30
+ end
31
+
32
+ # Sandwich class that inherits from Food
33
+ class Sandwich < Food
34
+ # Override name with a more specific default
35
+ ivar :@name, value: "Sandwich"
36
+
37
+ # Declare sandwich-specific properties
38
+ ivar :@bread, init: :positional, value: "white"
39
+ ivar :@fillings, init: :positional, value: []
40
+
41
+ # Override vegetarian with a different default
42
+ ivar :@vegetarian, value: true
43
+
44
+ def initialize(condiments = [], *args)
45
+ # Pass any remaining args to parent
46
+ super(*args)
47
+
48
+ # Initialize fillings if needed
49
+ @fillings ||= []
50
+
51
+ # Add condiments to fillings
52
+ @fillings += condiments
53
+ end
54
+
55
+ def to_s
56
+ result = super
57
+ result += " on #{@bread} bread"
58
+ result += " with #{@fillings.join(", ")}" unless @fillings.empty?
59
+ result
60
+ end
61
+ end
62
+
63
+ # VeganSandwich class that inherits from Sandwich
64
+ class VeganSandwich < Sandwich
65
+ # Override defaults for vegan properties
66
+ ivar :@name, value: "Vegan Sandwich"
67
+ ivar :@vegan, value: true
68
+
69
+ # Override bread default
70
+ ivar :@bread, value: "whole grain"
71
+
72
+ # Add vegan-specific properties
73
+ ivar :@plant_protein, init: :positional, value: "tofu"
74
+
75
+ def initialize(*args)
76
+ super
77
+
78
+ # Initialize fillings if needed
79
+ @fillings ||= []
80
+
81
+ # Ensure no non-vegan fillings
82
+ @fillings.reject! { |filling| non_vegan_fillings.include?(filling) }
83
+
84
+ # Add plant protein if fillings are empty
85
+ @fillings << @plant_protein if @fillings.empty?
86
+ end
87
+
88
+ def non_vegan_fillings
89
+ ["cheese", "mayo", "ham", "turkey", "roast beef", "tuna"]
90
+ end
91
+ end
92
+
93
+ # Create a basic food item with positional arguments
94
+ # (name, calories, vegetarian, vegan)
95
+ apple = Food.new("Apple", 95, true, true, "Fresh and crisp")
96
+ puts "Food: #{apple}"
97
+ # => "Food: Apple (95 calories), vegetarian, vegan, Fresh and crisp"
98
+
99
+ # Create a sandwich with positional arguments
100
+ # (bread, fillings, name, calories, vegetarian, vegan, extra_info)
101
+ # Note: condiments is a separate parameter not part of the ivar declarations
102
+ turkey_sandwich = Sandwich.new(
103
+ ["mustard", "mayo"], # condiments
104
+ "wheat", # bread
105
+ ["turkey", "lettuce", "tomato"], # fillings
106
+ "Turkey Sandwich", # name
107
+ 450, # calories
108
+ false, # vegetarian
109
+ false, # vegan
110
+ "Classic lunch option" # extra_info
111
+ )
112
+ puts "Sandwich: #{turkey_sandwich}"
113
+ # => "Sandwich: Turkey Sandwich (450 calories), Classic lunch option on wheat bread with turkey, lettuce, tomato, mustard, mayo"
114
+
115
+ # Create a vegan sandwich with positional arguments
116
+ # (plant_protein, bread, fillings, name, calories, vegetarian, vegan, extra_info)
117
+ vegan_sandwich = VeganSandwich.new(
118
+ ["hummus", "mustard"], # condiments
119
+ "tempeh", # plant_protein
120
+ "rye", # bread
121
+ ["lettuce", "tomato", "avocado"], # fillings
122
+ "Tempeh Sandwich", # name
123
+ 350, # calories
124
+ true, # vegetarian
125
+ true, # vegan
126
+ "High protein option" # extra_info
127
+ )
128
+ puts "Vegan Sandwich: #{vegan_sandwich}"
129
+ # => "Vegan Sandwich: Tempeh Sandwich (350 calories), vegetarian, vegan, High protein option on rye bread with lettuce, tomato, avocado, hummus, mustard"
130
+
131
+ # Create items with default values
132
+ default_food = Food.new
133
+ default_sandwich = Sandwich.new
134
+ default_vegan = VeganSandwich.new
135
+
136
+ puts "\nDefaults:"
137
+ puts "Default Food: #{default_food}"
138
+ # => "Default Food: Unknown Food (0 calories)"
139
+ puts "Default Sandwich: #{default_sandwich}"
140
+ # => "Default Sandwich: Sandwich (0 calories), vegetarian on white bread"
141
+ puts "Default Vegan Sandwich: #{default_vegan}"
142
+ # => "Default Vegan Sandwich: Vegan Sandwich (0 calories), vegetarian, vegan on whole grain bread with tofu"
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ivar"
4
+
5
+ class Recipe
6
+ include Ivar::Checked
7
+
8
+ # Declare instance variables with positional initialization
9
+ ivar :@name, init: :positional, value: "Unnamed Recipe"
10
+ ivar :@servings, init: :positional, value: 4
11
+
12
+ # Declare instance variables with keyword initialization
13
+ ivar :@prep_time, init: :kwarg, value: 15
14
+ ivar :@cook_time, init: :kwarg, value: 30
15
+ ivar :@difficulty, init: :kwarg, value: "medium"
16
+
17
+ # Regular instance variables
18
+ ivar :@ingredients, value: []
19
+ ivar :@instructions, value: []
20
+
21
+ def initialize(ingredients = [], instructions = [])
22
+ # At this point, @name and @servings are set from positional args
23
+ # @prep_time, @cook_time, and @difficulty are set from keyword args
24
+ @ingredients = ingredients unless ingredients.empty?
25
+ @instructions = instructions unless instructions.empty?
26
+ end
27
+
28
+ def total_time
29
+ @prep_time + @cook_time
30
+ end
31
+
32
+ def to_s
33
+ result = "#{@name} (Serves: #{@servings})\n"
34
+ result += "Prep: #{@prep_time} min, Cook: #{@cook_time} min, Difficulty: #{@difficulty}\n"
35
+ result += "\nIngredients:\n"
36
+ @ingredients.each { |ingredient| result += "- #{ingredient}\n" }
37
+ result += "\nInstructions:\n"
38
+ @instructions.each_with_index { |instruction, i| result += "#{i + 1}. #{instruction}\n" }
39
+ result
40
+ end
41
+ end
42
+
43
+ class DessertRecipe < Recipe
44
+ # Additional positional parameters
45
+ ivar :@dessert_type, init: :positional, value: "cake"
46
+ # Additional keyword parameters
47
+ ivar :@sweetness, init: :kwarg, value: "medium"
48
+ ivar :@calories_per_serving, init: :kwarg, value: 300
49
+
50
+ def initialize(special_equipment = [], *args, **kwargs)
51
+ @special_equipment = special_equipment
52
+ super(*args, **kwargs)
53
+ end
54
+
55
+ def to_s
56
+ result = super
57
+ result += "\nDessert Type: #{@dessert_type}\n"
58
+ result += "Sweetness: #{@sweetness}, Calories: #{@calories_per_serving} per serving\n"
59
+ result += "\nSpecial Equipment:\n"
60
+ @special_equipment.each { |equipment| result += "- #{equipment}\n" } unless @special_equipment.empty?
61
+ result
62
+ end
63
+ end
64
+
65
+ # Create a basic recipe with positional and keyword arguments
66
+ pasta_recipe = Recipe.new(
67
+ "Spaghetti Carbonara", # name (positional)
68
+ 2, # servings (positional)
69
+ [ # ingredients (regular parameter)
70
+ "200g spaghetti",
71
+ "100g pancetta",
72
+ "2 large eggs",
73
+ "50g pecorino cheese",
74
+ "50g parmesan",
75
+ "Freshly ground black pepper"
76
+ ],
77
+ [ # instructions (regular parameter)
78
+ "Cook the spaghetti in salted water.",
79
+ "Fry the pancetta until crispy.",
80
+ "Whisk the eggs and cheese together.",
81
+ "Drain pasta, mix with pancetta, then quickly mix in egg mixture.",
82
+ "Season with black pepper and serve immediately."
83
+ ],
84
+ prep_time: 10, # prep_time (keyword)
85
+ cook_time: 15, # cook_time (keyword)
86
+ difficulty: "easy" # difficulty (keyword)
87
+ )
88
+
89
+ puts "Basic Recipe:\n#{pasta_recipe}\n\n"
90
+
91
+ # Create a dessert recipe with positional and keyword arguments
92
+ chocolate_cake = DessertRecipe.new(
93
+ ["Stand mixer", "9-inch cake pans", "Cooling rack"], # special_equipment (regular parameter)
94
+ "chocolate", # dessert_type (positional)
95
+ "Chocolate Layer Cake", # name (positional)
96
+ 12, # servings (positional)
97
+ [ # ingredients (regular parameter)
98
+ "2 cups all-purpose flour",
99
+ "2 cups sugar",
100
+ "3/4 cup unsweetened cocoa powder",
101
+ "2 tsp baking soda",
102
+ "1 tsp salt",
103
+ "2 large eggs",
104
+ "1 cup buttermilk",
105
+ "1/2 cup vegetable oil",
106
+ "2 tsp vanilla extract",
107
+ "1 cup hot coffee"
108
+ ],
109
+ [ # instructions (regular parameter)
110
+ "Preheat oven to 350°F (175°C).",
111
+ "Mix dry ingredients in a large bowl.",
112
+ "Add eggs, buttermilk, oil, and vanilla; beat for 2 minutes.",
113
+ "Stir in hot coffee (batter will be thin).",
114
+ "Pour into greased and floured cake pans.",
115
+ "Bake for 30-35 minutes.",
116
+ "Cool completely before frosting."
117
+ ],
118
+ prep_time: 25, # prep_time (keyword)
119
+ cook_time: 35, # cook_time (keyword)
120
+ difficulty: "medium", # difficulty (keyword)
121
+ sweetness: "high", # sweetness (keyword)
122
+ calories_per_serving: 450 # calories_per_serving (keyword)
123
+ )
124
+
125
+ puts "Dessert Recipe:\n#{chocolate_cake}"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add lib directory to load path
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
+
6
+ # This automatically enables Ivar.check_all
7
+ require "ivar/check_all"
8
+
9
+ # Now all classes and modules defined in the project will have Ivar::Checked included
10
+ class Sandwich
11
+ def initialize
12
+ @bread = "wheat"
13
+ @cheese = "muenster"
14
+ end
15
+
16
+ def to_s
17
+ "A #{@bread} sandwich with #{@chese}" # Intentional typo in @cheese
18
+ end
19
+ end
20
+
21
+ # Create a sandwich - this will automatically check instance variables
22
+ sandwich = Sandwich.new
23
+ puts sandwich
@@ -24,7 +24,7 @@ class SpecialtySandwich < BaseSandwich
24
24
 
25
25
  def to_s
26
26
  result = "#{base_to_s} with #{@condiments.join(", ")}"
27
- result += " and #{@special_sause}" # Typo here
27
+ result += " and #{@special_sause}" # Intentional typo in @special_sauce
28
28
  result
29
29
  end
30
30
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ivar"
4
+
5
+ class SandwichWithAccessors
6
+ include Ivar::Checked
7
+
8
+ # Declare instance variables with accessors
9
+ ivar :@bread, :@cheese, accessor: true, value: "default"
10
+
11
+ # Declare condiments with a reader
12
+ ivar :@condiments, reader: true, value: ["mayo", "mustard"]
13
+
14
+ # Declare pickles with a writer
15
+ ivar :@pickles, writer: true, value: true
16
+
17
+ # Declare a variable without any accessors
18
+ ivar :@side
19
+
20
+ def initialize(options = {})
21
+ # Override defaults if options provided
22
+ @bread = options[:bread] if options[:bread]
23
+ @cheese = options[:cheese] if options[:cheese]
24
+
25
+ # Add extra condiments if provided
26
+ @condiments += options[:extra_condiments] if options[:extra_condiments]
27
+
28
+ # Set pickles based on options
29
+ @pickles = options[:pickles] if options.key?(:pickles)
30
+
31
+ # Set side if provided
32
+ @side = options[:side] if options[:side]
33
+ end
34
+
35
+ def to_s
36
+ result = "A #{@bread} sandwich with #{@cheese}"
37
+ result += " and #{@condiments.join(", ")}" unless @condiments.empty?
38
+ result += " with pickles" if @pickles
39
+ result += " and a side of #{@side}" if defined?(@side) && @side
40
+ result
41
+ end
42
+
43
+ # Custom reader for side since we didn't create an accessor
44
+ attr_reader :side
45
+
46
+ # Custom method to toggle pickles
47
+ def toggle_pickles
48
+ @pickles = !@pickles
49
+ end
50
+ end
51
+
52
+ # Create a sandwich with default values
53
+ sandwich = SandwichWithAccessors.new
54
+ puts "Default sandwich: #{sandwich}"
55
+ puts "Bread: #{sandwich.bread}"
56
+ puts "Cheese: #{sandwich.cheese}"
57
+ puts "Condiments: #{sandwich.condiments.join(", ")}"
58
+ puts "Side: #{sandwich.side.inspect}"
59
+
60
+ # Modify the sandwich using accessors
61
+ sandwich.bread = "rye"
62
+ sandwich.cheese = "swiss"
63
+ sandwich.pickles = false
64
+ puts "\nModified sandwich: #{sandwich}"
65
+
66
+ # Create a sandwich with custom options
67
+ custom = SandwichWithAccessors.new(
68
+ bread: "sourdough",
69
+ cheese: "provolone",
70
+ extra_condiments: ["pesto"],
71
+ pickles: false,
72
+ side: "chips"
73
+ )
74
+ puts "\nCustom sandwich: #{custom}"
75
+
76
+ # Toggle pickles and show the result
77
+ custom.toggle_pickles
78
+ puts "After toggling pickles: #{custom}"