Class: Rubydojo::Lesson

Inherits:
Object
  • Object
show all
Defined in:
lib/rubydojo/lessons.rb

Constant Summary collapse

LOADED_LESSONS =
[
  new(
    id: "variables",
    title: "Variables & Constants",
    level: "Level 1: Ruby Essentials",
    description: "Understand the backbone of Ruby storage: local variables, constants, and basic methods.",
    explanation: <<~MARKDOWN,
    code_template: <<~RUBY,
    validation_code: <<~RUBY,
      raise "Local variable 'age' must be defined" unless binding.local_variable_defined?(:age)
      user_age = binding.local_variable_get(:age)
      raise "Variable 'age' should be 25, got \#{user_age.inspect}" unless user_age == 25
      
      raise "Constant 'PLANET' must be defined" unless self.class.const_defined?(:PLANET) || binding.eval("defined?(PLANET)")
      planet_val = binding.eval("PLANET")
      raise "Constant 'PLANET' should be 'Earth', got \#{planet_val.inspect}" unless planet_val == "Earth"
      
      raise "Method 'greet' is not defined" unless respond_to?(:greet)
      greet_test = greet("Jayesh")
      raise "Method 'greet' should return 'Hello, Jayesh!', got \#{greet_test.inspect}" unless greet_test == "Hello, Jayesh!"
    RUBY
    hint: "Make sure you use double quotes for strings, and remember that methods in Ruby implicitly return the last statement."
  ),

  new(
    id: "arrays",
    title: "Collections & Arrays",
    level: "Level 2: Collections",
    description: "Master the structure of Ruby lists (Arrays) and how to query, append, and slice them.",
    explanation: <<~MARKDOWN,
    code_template: <<~RUBY,
    validation_code: <<~RUBY,
      raise "Method 'first_and_last' is not defined" unless respond_to?(:first_and_last)
      test_arr1 = [10, 20, 30, 40]
      res1 = first_and_last(test_arr1)
      raise "first_and_last([10, 20, 30, 40]) should return [10, 40], got \#{res1.inspect}" unless res1 == [10, 40]
      
      raise "Method 'add_element' is not defined" unless respond_to?(:add_element)
      test_arr2 = ["a", "b"]
      res2 = add_element(test_arr2, "c")
      raise "add_element(['a', 'b'], 'c') should return ['a', 'b', 'c'], got \#{res2.inspect}" unless res2 == ["a", "b", "c"]
    RUBY
    hint: "You can access the first element with arr[0] or arr.first, and the last with arr[-1] or arr.last. Use << to append."
  ),

  new(
    id: "iterators",
    title: "Enumerables & Iterators",
    level: "Level 2: Collections",
    description: "Learn how to use blocks to iterate, map, select, and reduce collections in a powerful way.",
    explanation: <<~MARKDOWN,
    code_template: <<~RUBY,
    validation_code: <<~RUBY,
      raise "Method 'double_numbers' is not defined" unless respond_to?(:double_numbers)
      res1 = double_numbers([2, 5, 10])
      raise "double_numbers([2, 5, 10]) should return [4, 10, 20], got \#{res1.inspect}" unless res1 == [4, 10, 20]
      
      raise "Method 'filter_even' is not defined" unless respond_to?(:filter_even)
      res2 = filter_even([1, 2, 3, 4, 5, 6])
      raise "filter_even([1, 2, 3, 4, 5, 6]) should return [2, 4, 6], got \#{res2.inspect}" unless res2 == [2, 4, 6]
    RUBY
    hint: "Remember to call map on the numbers array inside double_numbers: `numbers.map { |n| ... }`."
  ),

  new(
    id: "classes",
    title: "Object-Oriented Ruby",
    level: "Level 3: OOP",
    description: "Understand classes, instance variables, initializers, and attribute readers/writers.",
    explanation: <<~MARKDOWN,
    code_template: <<~RUBY,
    validation_code: <<~RUBY,
      raise "Class Developer is not defined" unless defined?(Developer) && Developer.is_a?(Class)
      dev = Developer.new("Jayesh", "Rails Intern")
      raise "Developer name is not readable" unless dev.respond_to?(:name)
      raise "Developer name is not writable" unless dev.respond_to?(:name=)
      raise "Developer role is not readable" unless dev.respond_to?(:role)
      
      raise "Developer name should be 'Jayesh', got \#{dev.name.inspect}" unless dev.name == "Jayesh"
      raise "Developer role should be 'Rails Intern', got \#{dev.role.inspect}" unless dev.role == "Rails Intern"
      
      dev.name = "Alex"
      raise "greet method not defined" unless dev.respond_to?(:greet)
      greeting = dev.greet
      expected = "Hello, I am Alex and I work as a Rails Intern!"
      raise "greet should return '\#{expected}', got \#{greeting.inspect}" unless greeting == expected
    RUBY
    hint: "Don't forget to use `attr_accessor :name, :role` at the top of your class, and interpolate variables in your greeting: `\"Hello, I am \#{name}...\"`."
  ),

  new(
    id: "blocks_procs_lambdas",
    title: "Blocks, Procs & Lambdas",
    level: "Level 3: OOP & Functions",
    description: "Understand closures in Ruby: how blocks yield code, and the differences between Procs and Lambdas.",
    explanation: <<~MARKDOWN,
    code_template: <<~RUBY,
    validation_code: <<~RUBY,
      raise "Method 'run_twice' is not defined" unless respond_to?(:run_twice)
      counter = 0
      run_twice { counter += 1 }
      raise "run_twice should execute the block twice, counted: \#{counter}" unless counter == 2
      
      # Test if run_twice handles no block given case
      begin
        run_twice
      rescue LocalJumpError => e
        raise "run_twice should check block_given? before yielding, otherwise it raises LocalJumpError!"
      end
      
      raise "format_usd is not defined" unless binding.local_variable_defined?(:format_usd) || binding.eval("defined?(format_usd)")
      fmt = binding.eval("format_usd")
      raise "format_usd should be a Lambda or Proc" unless fmt.is_a?(Proc)
      
      # Check return format
      res1 = fmt.call(10)
      raise "format_usd(10) should return '$10.00', got \#{res1.inspect}" unless res1 == "$10.00"
      res2 = fmt.call(4.5)
      raise "format_usd(4.5) should return '$4.50', got \#{res2.inspect}" unless res2 == "$4.50"
    RUBY
    hint: "To format a float with 2 decimal places in Ruby, use string formatting: `'%.2f' % value`."
  ),

  new(
    id: "metaprogramming",
    title: "Metaprogramming Basics",
    level: "Level 4: Advanced Ruby",
    description: "Learn Ruby's magical powers: calling methods dynamically using send, and defining them using define_method.",
    explanation: <<~MARKDOWN,
    code_template: <<~RUBY,
    validation_code: <<~RUBY,
      raise "Class SmartDevice is not defined" unless defined?(SmartDevice) && SmartDevice.is_a?(Class)
      device = SmartDevice.new
      raise "turn_on method should be defined on SmartDevice" unless device.respond_to?(:turn_on)
      raise "turn_off method should be defined on SmartDevice" unless device.respond_to?(:turn_off)
      
      val_on = device.turn_on
      val_off = device.turn_off
      raise "turn_on should return 'Device is ON', got \#{val_on.inspect}" unless val_on == "Device is ON"
      raise "turn_off should return 'Device is OFF', got \#{val_off.inspect}" unless val_off == "Device is OFF"
      
      raise "Method 'call_turn_on' is not defined" unless respond_to?(:call_turn_on)
      res = call_turn_on(device)
      raise "call_turn_on(device) should return 'Device is ON', got \#{res.inspect}" unless res == "Device is ON"
    RUBY
    hint: "Inside define_method, status will be :on or :off. You can convert it to string and format it: `'Device is ' + status.to_s.upcase`."
  ),

  new(
    id: "rails_idioms",
    title: "Rails Ruby Idioms & ActiveSupport",
    level: "Level 5: Ruby in Rails",
    description: "Learn how Rails extends Ruby under the hood and master core ActiveSupport helpers like blank?, presence, and Safe Navigation.",
    explanation: <<~MARKDOWN,
    code_template: <<~RUBY,
    validation_code: <<~RUBY,
      raise "Method 'clean_params' is not defined" unless respond_to?(:clean_params)
      raise "clean_params should return nil for empty hash" unless clean_params({}).nil?
      raise "clean_params should return nil for nil" unless clean_params(nil).nil?
      raise "clean_params should return the parameters if present" unless clean_params({ name: "Jayesh" }) == { name: "Jayesh" }
      
      raise "Method 'safe_user_name' is not defined" unless respond_to?(:safe_user_name)
      
      # Create mock user struct
      UserMock = Struct.new(:name)
      u1 = UserMock.new("Jayesh")
      u2 = UserMock.new("")
      u3 = UserMock.new(nil)
      u4 = nil
      
      res1 = safe_user_name(u1)
      raise "safe_user_name(u1) should return 'JAYESH', got \#{res1.inspect}" unless res1 == "JAYESH"
      
      res2 = safe_user_name(u2)
      raise "safe_user_name(u2) should return 'ANONYMOUS', got \#{res2.inspect}" unless res2 == "ANONYMOUS"
      
      res3 = safe_user_name(u3)
      raise "safe_user_name(u3) should return 'ANONYMOUS', got \#{res3.inspect}" unless res3 == "ANONYMOUS"
      
      res4 = safe_user_name(u4)
      raise "safe_user_name(u4) should return 'ANONYMOUS', got \#{res4.inspect}" unless res4 == "ANONYMOUS"
    RUBY
    hint: "Remember: `user&.name` returns nil safely if user is nil. You can check if the name is blank with `&.blank?`."
  )
]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id:, title:, level:, description:, explanation:, code_template:, validation_code:, hint:) ⇒ Lesson

Returns a new instance of Lesson.



5
6
7
8
9
10
11
12
13
14
# File 'lib/rubydojo/lessons.rb', line 5

def initialize(id:, title:, level:, description:, explanation:, code_template:, validation_code:, hint:)
  @id = id
  @title = title
  @level = level
  @description = description
  @explanation = explanation
  @code_template = code_template
  @validation_code = validation_code
  @hint = hint
end

Instance Attribute Details

#code_templateObject (readonly)

Returns the value of attribute code_template.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def code_template
  @code_template
end

#descriptionObject (readonly)

Returns the value of attribute description.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def description
  @description
end

#explanationObject (readonly)

Returns the value of attribute explanation.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def explanation
  @explanation
end

#hintObject (readonly)

Returns the value of attribute hint.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def hint
  @hint
end

#idObject (readonly)

Returns the value of attribute id.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def id
  @id
end

#levelObject (readonly)

Returns the value of attribute level.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def level
  @level
end

#titleObject (readonly)

Returns the value of attribute title.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def title
  @title
end

#validation_codeObject (readonly)

Returns the value of attribute validation_code.



3
4
5
# File 'lib/rubydojo/lessons.rb', line 3

def validation_code
  @validation_code
end

Class Method Details

.allObject



16
17
18
# File 'lib/rubydojo/lessons.rb', line 16

def self.all
  @all ||= LOADED_LESSONS
end

.find(id) ⇒ Object



20
21
22
# File 'lib/rubydojo/lessons.rb', line 20

def self.find(id)
  all.find { |l| l.id.to_s == id.to_s }
end

.next_lesson(current_id) ⇒ Object



24
25
26
27
28
# File 'lib/rubydojo/lessons.rb', line 24

def self.next_lesson(current_id)
  current_index = all.index { |l| l.id.to_s == current_id.to_s }
  return nil unless current_index && current_index < all.size - 1
  all[current_index + 1]
end