ivar 0.2.0 → 0.4.6
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.
- checksums.yaml +4 -4
- data/.augment-guidelines +5 -3
- data/.devcontainer/devcontainer.json +28 -20
- data/.devcontainer/post-create.sh +18 -0
- data/.editorconfig +35 -0
- data/.rubocop.yml +6 -0
- data/.standard.yml +1 -1
- data/.vscode/extensions.json +3 -1
- data/.vscode/launch.json +25 -0
- data/.vscode/settings.json +38 -2
- data/CHANGELOG.md +99 -1
- data/README.md +272 -207
- data/Rakefile +1 -1
- data/VERSION.md +46 -0
- data/examples/check_all_block_example.rb +84 -0
- data/examples/check_all_example.rb +42 -0
- data/examples/inheritance_with_kwarg_init.rb +156 -0
- data/examples/inheritance_with_positional_init.rb +142 -0
- data/examples/mixed_positional_and_kwarg_init.rb +125 -0
- data/examples/require_check_all_example.rb +23 -0
- data/examples/sandwich_inheritance.rb +1 -1
- data/examples/sandwich_with_accessors.rb +78 -0
- data/examples/sandwich_with_block_values.rb +54 -0
- data/examples/sandwich_with_checked.rb +0 -1
- data/examples/sandwich_with_checked_once.rb +0 -1
- data/examples/sandwich_with_initial_values.rb +52 -0
- data/examples/sandwich_with_ivar_block.rb +6 -9
- data/examples/sandwich_with_ivar_macro.rb +4 -4
- data/examples/sandwich_with_kwarg_init.rb +78 -0
- data/examples/sandwich_with_positional_init.rb +50 -0
- data/examples/sandwich_with_shared_values.rb +54 -0
- data/hooks/README.md +42 -0
- data/hooks/install.sh +12 -0
- data/hooks/pre-commit +54 -0
- data/lib/ivar/check_all.rb +7 -0
- data/lib/ivar/check_all_manager.rb +72 -0
- data/lib/ivar/check_policy.rb +29 -0
- data/lib/ivar/checked/class_methods.rb +19 -0
- data/lib/ivar/checked/instance_methods.rb +35 -0
- data/lib/ivar/checked.rb +17 -24
- data/lib/ivar/declaration.rb +30 -0
- data/lib/ivar/explicit_declaration.rb +56 -0
- data/lib/ivar/explicit_keyword_declaration.rb +24 -0
- data/lib/ivar/explicit_positional_declaration.rb +19 -0
- data/lib/ivar/macros.rb +48 -111
- data/lib/ivar/manifest.rb +124 -0
- data/lib/ivar/policies.rb +13 -1
- data/lib/ivar/project_root.rb +59 -0
- data/lib/ivar/targeted_prism_analysis.rb +144 -0
- data/lib/ivar/validation.rb +6 -29
- data/lib/ivar/version.rb +1 -1
- data/lib/ivar.rb +141 -9
- data/script/console +11 -0
- data/script/de-lint +2 -0
- data/script/de-lint-unsafe +2 -0
- data/script/lint +2 -0
- data/script/release +213 -0
- data/script/setup +8 -0
- data/script/test +2 -0
- metadata +46 -8
- data/examples/sandwich_with_kwarg.rb +0 -45
- data/ivar.gemspec +0 -49
- data/lib/ivar/auto_check.rb +0 -77
- 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}"
|
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}"
|