Category:I704 Ruby: Difference between revisions

From ICO wiki
Jump to navigationJump to search
Dhamidi (talk | contribs)
No edit summary
Dhamidi (talk | contribs)
No edit summary
 
(11 intermediate revisions by the same user not shown)
Line 62: Line 62:


* 10 points for proper use of version control
* 10 points for proper use of version control
* 10 points for a having a README describing your project


* 10 points for adhering to common Ruby coding standards
* 10 points for adhering to common Ruby coding standards
* 10 points for structuring your project as a Rubygem


* 20 points for answering 80% of the questions correctly
* 20 points for answering 80% of the questions correctly


The student needs at least 60 points in order to pass the course.
The student needs at least 60 points in order to pass the course.
== Proper use of version control ==
'''Point value:''' 10
These points are awarded if:
# you keep your code in a git repository
# a single commit represents a single logical step in the development process (e.g. adding a test and the code to make it pass, fixing a single bug, refactoring one area of the code, etc)
# most commit messages follow the guidelines for good commit messages (see reference material below)
'''Reference material'''
* [https://chris.beams.io/posts/git-commit/ How to write a Git Commit Message] (see "The Seven Rules")
* [http://nuclearsquid.com/writings/git-add/ How to select a subset of all changes for your next commit]
== Having a README ==
'''Point value:''' 10
These points are awarded if:
# your project has a file called README.md in your project's root directory
# the README.md file contains a description of: what your program does, how to install it and how to run it
== Adhering to common Ruby coding standards ==
'''Point value:''' 10
These points are awarded if:
# running <code>rubocop</code> with the default configuration in your project produces no errors
'''Reference material'''
* [http://rubocop.readthedocs.io/en/latest/basic_usage/#basic-usage Rubocop Basic Usage].  '''Hint:'''  You can run Rubocop with the <code>--auto-correct</code> option to automatically fix some of the problems Rubocop discovered.
== Providing automated tests ==
'''Point value:''' 20
These points are awarded if:
# running <code>bundle exec rake test</code> runs your test suite
# your test suite has no failing tests
# your test suite contains at least one meaningful test, testing your program's logic
'''Reference material'''
* [https://launchschool.com/blog/assert-yourself-an-introduction-to-minitest Introduction to MiniTest]
== Structuring your project as a Rubygem ==
'''Point value:''' 10
These points are awarded if:
# your project directory structure matches the structure created by <code>bundle gem NAME_OF_YOUR_PROJECT</code>
# additional dependencies are specified in the <code>.gemspec</code> file of your project
If your project is using a framework with strong conventions, such as Ruby on Rails, these points are awarded if you place your code according to the conventions of the framework.
'''Reference material'''
* [https://code.tutsplus.com/tutorials/gem-creation-with-bundler--net-25281 Creating a gem with Bundler]
== Answering 80% of the questions correctly ==
'''Point value:''' 20
You will be asked 3 to 5 questions about your project, testing your understanding of what is happening in the code.
These points are awarded if:
# you answer 80% or more of the questions correctly


= 2017-02-02: Lecture and Lab =
= 2017-02-02: Lecture and Lab =
Line 233: Line 313:
│   ├── console
│   ├── console
│   └── setup
│   └── setup
├── exe
│   └── hello-world
├── Gemfile
├── Gemfile
├── Gemfile.lock
├── Gemfile.lock
Line 250: Line 332:
     └── test_helper.rb
     └── test_helper.rb


5 directories, 14 files
6 directories, 15 files
</pre>
</pre>
* exectuables that users of your gem should be able to run go into the <code>exe</code> directory
* add dependencies in your <code>.gemspec</code> file.  After adding a dependency, run <code>bundle install</code> to install the dependency on your computer.


'''Rule of thumb''': put your code into the <code>lib/YOUR_PROJECT_NAME</code> directory, one file per class (like <code>bank_account.rb</code> in the example above)
'''Rule of thumb''': put your code into the <code>lib/YOUR_PROJECT_NAME</code> directory, one file per class (like <code>bank_account.rb</code> in the example above)
= 2017-03-02 Lecture & Lab =
'''Word count example'''
<syntaxhighlight lang="ruby">
text = "Burning Rangers is a 1998 3D action video game developed by Sonic Team and published by Sega for the Sega Saturn. The game is set in a futuristic society threatened by frequent fires. Players control one of an elite group of firefighters, the Burning Rangers, who extinguish the fires and rescue civilians from burning buildings. Most of the tasks the players complete are centred around collecting energy crystals used to transport civilians to safety. Development began shortly after the release of Christmas Nights in November 1996, when Yuji Naka started working on a game focused on saving people rather than killing them. Sonic Team chose the themes of firefighting and heroism. Burning Rangers received mostly positive reviews, especially for the game's soundtrack and audio. Responses to the graphics were mixed; while some critics asserted that the game had the best visuals on the Saturn, others faulted its poor collision detection and occasional glitching. The game was among the final five Saturn titles released in America."
words = text.split(' ')
words_without_punctuation = words.map do |word|
  word.sub(/[.,;?!:]/, '').downcase
end
word_count = words_without_punctuation.reduce({}) do |result, word|
  if result.key?(word)
    result[word] = result[word] + 1
    # also possible: result[word] += 1
  else
    result[word] = 1
  end
  result
end
puts word_count
</syntaxhighlight>
= 2017-03-09 lecture =
== Using locks ==
<syntaxhighlight lang="ruby">
require 'thread'
# [1, 2, ..., 100]
# [t1, t2, ..., t100]
# Lock / Mutex (mutual exclusion)
# Queue
lock = Mutex.new
threads = (1..100).map do |number|
  Thread.new do
    sleep 0.5
    lock.lock
    puts number
    lock.unlock
    lock.synchronize do
      if number == 1
        sleep 5
      end
      puts number
    end
  end
end
threads.each do |thread|
  thread.join
end
</syntaxhighlight>
== Using a queue ==
<syntaxhighlight lang="ruby">
require 'thread'
# Queue
queue = Queue.new
printer = Thread.new do
  loop do
    number = queue.pop
    puts number
  end
end
threads = (1..100).map do |number|
  Thread.new do
    sleep 0.5
    queue.push number
  end
end
threads.each do |thread|
  thread.join
end
</syntaxhighlight>
== Using fork ==
<syntaxhighlight lang="ruby">
shared_variable = 'untouched'
child_process_id = fork do
  puts "Running inside fork"
  puts "My process ID is #{$$}"
  shared_variable = 'touched'
end
Process.wait(child_process_id)
puts "In the parent #{$$}: shared_variable is #{shared_variable}"
</syntaxhighlight>


= Reference material: making HTTP requests in Ruby =
= Reference material: making HTTP requests in Ruby =
Line 268: Line 458:
# Now you can work with the HTML returned by the server
# Now you can work with the HTML returned by the server


</syntaxhighlight>
= 2017-03-16 Lecture =
<syntaxhighlight lang="ruby">
require 'pry'
##
#
#  NEVER RESCUE FROM Exception
#
#  HAVE AN ERROR CLASS FOR APPLICATION AND HAVE ALL APPLICATION ERRORS
#  INHERIT FROM IT.
##
# Options for error handling:
#
# 1. raise an exception
# 2. return a value describing the error
module ErrorHandling
  class Error < StandardError
  end
  class InvalidNumberError < Error
    def initialize(input)
      @input = input
    end
    attr_reader :input
  end
  class DivisionByZeroAttempted < Error
  end
  class WrongDayOfTheWeek < Error
  end
  class Application
    def run
      raise WrongDayOfTheWeek unless Date.today.monday?
      print("Enter a number: ")
      a = read_number
      print("Enter another number: ")
      b = read_number
      if b.zero?
        raise DivisionByZeroAttempted
      else
        puts "a / b = #{a / b}"
      end
    end
    def read_number
      input = $stdin.gets.chomp
      begin
        Float(input)
      rescue ArgumentError
        raise InvalidNumberError.new(input)
      end
    end
  end
end
</syntaxhighlight>
== Using return values for indicating errors ==
<syntaxhighlight lang="ruby">
require 'pry'
module ErrorHandling
  class Application
    def run
      print("Enter a number: ")
      a = read_number
      until a != :invalid_input
        if a == :invalid_input
          puts "Not a number!"
        end
        a = read_number
      end
      print("Enter another number: ")
      b = read_number
      if b == :invalid_input
        puts "Not a number!"
        return
      end
      if b.zero?
        puts "You cannot divide by zero!"
        return
      end
      puts "a / b = #{a/b}"
    end
    def read_number
      input = $stdin.gets.chomp
      begin
        Float(input)
      rescue ArgumentError
        :invalid_input
      end
    end
  end
end
</syntaxhighlight>
= 2017-03-23 Lecture =
== Using Struct ==
<syntaxhighlight lang="ruby">
module Blog
  Post = Struct.new(:id, :title, :body_text) do
    def initialize(id:, title: 'no title', body_text: 'no body')
      super(id, title, body_text)
    end
    def validate
      errors = {}
      errors[:title] = :empty if title.empty?
      errors[:id] = :empty if id.empty?
      errors
    end
  end
end
first_article = Blog::Post.new(
  id: 'another-post'
)
puts first_article
puts first_article.validate
</syntaxhighlight>
</syntaxhighlight>



Latest revision as of 12:21, 23 March 2017

About this course

This course teaches the Ruby programming language. By the end of the course you'll hopefully have a good understanding of:

  • the basics of Ruby,
  • tools commonly used in the Ruby ecosystem,
  • written a few small Ruby applications,
  • know about unit testing,
  • know how to use third-party code (Ruby gems),
  • know how to write web applications using Ruby.

Lecture Recordings

You can find recordings of the lectures here:

https://echo360.e-ope.ee/ess/portal/section/b02ab032-010b-4111-a53d-2f5a47db1fdd

About yourself

To help me make this course interesting for you and meet your expectations, please fill out this survey if you haven't done so already:

http://bit.ly/2jxwrs8

Reference material

Here you find a list of useful links to things that have been mentioned or discussed during the lectures:

Editors/IDEs

Grading

Students develop several small projects during the lectures and independent study. At the end of the course students pick one of their projects and are assigned a feature request to implement in their project and a set of questions about the code in their project.

Points are awarded for the following:

  • 50 points for a working implementation of the feature request
  • 40 points for an implementation of the feature request that works only for expected inputs
  • 20 points for a running unfinished implementation (i.e. feature not fully implemented, but the program still runs)
  • 10 points for an unfinished implementation (i.e. feature not fully implement and the program does not run).
  • 20 points for providing automated tests for their implementation
  • 10 points for proper use of version control
  • 10 points for a having a README describing your project
  • 10 points for adhering to common Ruby coding standards
  • 10 points for structuring your project as a Rubygem
  • 20 points for answering 80% of the questions correctly

The student needs at least 60 points in order to pass the course.

Proper use of version control

Point value: 10

These points are awarded if:

  1. you keep your code in a git repository
  2. a single commit represents a single logical step in the development process (e.g. adding a test and the code to make it pass, fixing a single bug, refactoring one area of the code, etc)
  3. most commit messages follow the guidelines for good commit messages (see reference material below)

Reference material

Having a README

Point value: 10

These points are awarded if:

  1. your project has a file called README.md in your project's root directory
  2. the README.md file contains a description of: what your program does, how to install it and how to run it

Adhering to common Ruby coding standards

Point value: 10

These points are awarded if:

  1. running rubocop with the default configuration in your project produces no errors

Reference material

  • Rubocop Basic Usage. Hint: You can run Rubocop with the --auto-correct option to automatically fix some of the problems Rubocop discovered.

Providing automated tests

Point value: 20

These points are awarded if:

  1. running bundle exec rake test runs your test suite
  2. your test suite has no failing tests
  3. your test suite contains at least one meaningful test, testing your program's logic

Reference material

Structuring your project as a Rubygem

Point value: 10

These points are awarded if:

  1. your project directory structure matches the structure created by bundle gem NAME_OF_YOUR_PROJECT
  2. additional dependencies are specified in the .gemspec file of your project

If your project is using a framework with strong conventions, such as Ruby on Rails, these points are awarded if you place your code according to the conventions of the framework.

Reference material


Answering 80% of the questions correctly

Point value: 20

You will be asked 3 to 5 questions about your project, testing your understanding of what is happening in the code.

These points are awarded if:

  1. you answer 80% or more of the questions correctly

2017-02-02: Lecture and Lab

Analyzing bank statements

Given the following contents of a file called input.csv

   transaction_id,date,amount,credit
   1,2017-02-02 12:40,1.30,debit
   2,2017-02-02 12:55,2.50,debit
   3,2017-02-02 13:00,1.00,credit

Goal: find the amount of money left on your bank account.

Steps:

  1. read the data line by line
  2. analyze each line to find out whether it's credit or debit and the amount of money
  3. add all the debit transaction amounts (money lost)
  4. add all the credit transaction amounts (money gained)
  5. Output money gained - money lost

Our code so far:

File.open('input.csv', 'r') do |the_file|
  lines = the_file.readlines.map do |line|
    line.chomp.split(',')
  end
  lines = lines[1..-1]
  debit_total = ''
  lines.each do |line|
    debit_total = debit_total + line[2]
  end
  puts debit_total
end

2017-02-09 Lecture

require 'minitest'
require 'minitest/autorun'

class BankAccount
  class Error < StandardError; end
  class BalanceNegative < Error; end

  def initialize
    @balance = 0
  end

  def deposit!(amount)
    increse_balance!(amount)

    self
  end

  def withdraw!(amount)
    if enough_money?(amount)
      reduce_balance!(amount)
    else
      raise StandardError, 'Not enough money!'
    end

    self
  end

  def balance
    @balance
  end

  private

  def enough_money?(amount)
    balance >= amount
  end

  def reduce_balance!(amount)
    @balance = @balance - amount
  end

  def increse_balance!(amount)
    @balance = @balance + amount
  end

end

class BankAccountTest < Minitest::Test
  def setup
    @account = BankAccount.new
  end

  def test_if_decreses_the_balance_when_withdrawing_money
    @account.deposit!(101).withdraw!(50)
    assert_equal 51, @account.balance
  end

  def test_if_increses_the_balance_when_depositing_money
    @account.deposit!(196583)
    assert_equal 196583, @account.balance
  end

  def test_if_account_does_not_go_in_to_negative
    assert_raises BankAccount::BalanceNegative do
      @account.deposit!(1).withdraw!(20)
    end
  end

  def test_it_raises_bank_account_error_when_withdrawing_too_much_money
    @account.deposit!(1).withdraw!(20)
  rescue BankAccount::Error => err
    assert true, 'everything is ok'
  end
end

2017-02-16 Lecture and Lab: using modules and bundler

Modules in Ruby

  • Modules are a collection of methods that can be included in another class
  • By including the module in a class, objects belonging to that class get access to the methods defined in the module
  • If multiple included modules define the same method, the method from the module that was included last counts

Example

module ModuleExample
  def foo
    'foo'
  end
end

# Including modules binds their methods to the class instances
# Extending modules binds their methods to the class itself

class Person
  include ModuleExample
end

class Book
  extend ModuleExample
end

Person.foo     # => NoMethodError: undefined method `foo' for Person:Class
Person.new.foo # => 'foo'
Book.foo       # => 'foo'
Book.new.foo   # => NoMethodError: undefined method `foo'

Structuring a project with bundler

  • bundler is a Ruby program for managing dependencies in your ruby project
  • bundler also helps you with creating a template for a project
  • redistributable projects/libraries are called "gems" in ruby
  • You can start your own project by running bundle gem YOUR_PROJECT_NAME_HERE. Replace YOUR_PROJECT_NAME_HERE with the lower-cased name of your project.

Example structure of a project, as generated by bundler

$ tree
.
├── bin
│   ├── console
│   └── setup
├── exe
│   └── hello-world
├── Gemfile
├── Gemfile.lock
├── I704.gemspec
├── lib
│   ├── I704
│   │   ├── bank_account.rb
│   │   └── version.rb
│   └── I704.rb
├── LICENSE.txt
├── Rakefile
├── README.md
└── test
    ├── I704
    │   └── bank_account_test.rb
    ├── I704_test.rb
    └── test_helper.rb

6 directories, 15 files


  • exectuables that users of your gem should be able to run go into the exe directory
  • add dependencies in your .gemspec file. After adding a dependency, run bundle install to install the dependency on your computer.


Rule of thumb: put your code into the lib/YOUR_PROJECT_NAME directory, one file per class (like bank_account.rb in the example above)

2017-03-02 Lecture & Lab

Word count example

text = "Burning Rangers is a 1998 3D action video game developed by Sonic Team and published by Sega for the Sega Saturn. The game is set in a futuristic society threatened by frequent fires. Players control one of an elite group of firefighters, the Burning Rangers, who extinguish the fires and rescue civilians from burning buildings. Most of the tasks the players complete are centred around collecting energy crystals used to transport civilians to safety. Development began shortly after the release of Christmas Nights in November 1996, when Yuji Naka started working on a game focused on saving people rather than killing them. Sonic Team chose the themes of firefighting and heroism. Burning Rangers received mostly positive reviews, especially for the game's soundtrack and audio. Responses to the graphics were mixed; while some critics asserted that the game had the best visuals on the Saturn, others faulted its poor collision detection and occasional glitching. The game was among the final five Saturn titles released in America."

words = text.split(' ')

words_without_punctuation = words.map do |word|
  word.sub(/[.,;?!:]/, '').downcase
end

word_count = words_without_punctuation.reduce({}) do |result, word|
  if result.key?(word)
    result[word] = result[word] + 1
    # also possible: result[word] += 1
  else
    result[word] = 1
  end

  result
end

puts word_count

2017-03-09 lecture

Using locks

require 'thread'

# [1, 2, ..., 100]
# [t1, t2, ..., t100]

# Lock / Mutex (mutual exclusion)
# Queue

lock = Mutex.new
threads = (1..100).map do |number|
  Thread.new do
    sleep 0.5

    lock.lock
    puts number
    lock.unlock

    lock.synchronize do
      if number == 1
        sleep 5
      end
      puts number
    end
  end
end

threads.each do |thread|
  thread.join
end

Using a queue

require 'thread'

# Queue

queue = Queue.new

printer = Thread.new do
  loop do
    number = queue.pop
    puts number
  end
end

threads = (1..100).map do |number|
  Thread.new do
    sleep 0.5
    queue.push number
  end
end

threads.each do |thread|
  thread.join
end

Using fork

shared_variable = 'untouched'
child_process_id = fork do
  puts "Running inside fork"
  puts "My process ID is #{$$}"
  shared_variable = 'touched'
end
Process.wait(child_process_id)
puts "In the parent #{$$}: shared_variable is #{shared_variable}"

Reference material: making HTTP requests in Ruby

  1. Require the "net/http" library in your program
  2. Use that library to make HTTP requests for fetching the HTML code behind a website
  3. See https://ruby-doc.org/stdlib-2.3.0/libdoc/net/http/rdoc/Net/HTTP.html for more examples of how to use this library
require 'net/http'
google_html = Net::HTTP.get(URI('http://google.com'))
# "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>302 Moved</TITLE></HEAD><BODY>\n<H1>302 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.ee/?gfe_rd=cr&amp;ei=o6KlWKCGA9Oq8wfoq5OQAg\">here</A>.\r\n</BODY></HTML>\r\n"

# Now you can work with the HTML returned by the server

2017-03-16 Lecture

require 'pry'

##
#
#  NEVER RESCUE FROM Exception
#
#  HAVE AN ERROR CLASS FOR APPLICATION AND HAVE ALL APPLICATION ERRORS
#  INHERIT FROM IT.
##

# Options for error handling:
#
# 1. raise an exception
# 2. return a value describing the error
module ErrorHandling
  class Error < StandardError
  end

  class InvalidNumberError < Error
    def initialize(input)
      @input = input
    end

    attr_reader :input
  end

  class DivisionByZeroAttempted < Error
  end

  class WrongDayOfTheWeek < Error
  end

  class Application
    def run
      raise WrongDayOfTheWeek unless Date.today.monday?

      print("Enter a number: ")
      a = read_number
      print("Enter another number: ")
      b = read_number
      if b.zero?
        raise DivisionByZeroAttempted
      else
        puts "a / b = #{a / b}"
      end
    end

    def read_number
      input = $stdin.gets.chomp
      begin
        Float(input)
      rescue ArgumentError
        raise InvalidNumberError.new(input)
      end
    end
  end
end

Using return values for indicating errors

require 'pry'
module ErrorHandling
  class Application
    def run
      print("Enter a number: ")
      a = read_number
      until a != :invalid_input
        if a == :invalid_input
          puts "Not a number!"
        end
        a = read_number
      end

      print("Enter another number: ")
      b = read_number
      if b == :invalid_input
        puts "Not a number!"
        return
      end

      if b.zero?
        puts "You cannot divide by zero!"
        return
      end

      puts "a / b = #{a/b}"
    end

    def read_number
      input = $stdin.gets.chomp
      begin
        Float(input)
      rescue ArgumentError
        :invalid_input
      end
    end
  end
end

2017-03-23 Lecture

Using Struct

module Blog
  Post = Struct.new(:id, :title, :body_text) do
    def initialize(id:, title: 'no title', body_text: 'no body')
      super(id, title, body_text)
    end

    def validate
      errors = {}
      errors[:title] = :empty if title.empty?
      errors[:id] = :empty if id.empty?
      errors
    end
  end
end

first_article = Blog::Post.new(
  id: 'another-post'
)
puts first_article
puts first_article.validate

Reference material: parsing HTML using Nokogiri

Once you have received some HTML from a web server, you most likely want to analyze it. Use the library nokogiri for this. You can find installation instructions and tutorials here: http://www.nokogiri.org/tutorials/

Reference material: map and reduce

# map: takes a list and a block
#    returns: a list, where each element of the original list has been
#    passed to block
#
# reduce: takes list, an initial value and a block
#    returns: block applied to initial value and each element of the list

numbers = [1, 2, 3]
sum = numbers.reduce(0) do |current_sum, number|
  current_sum + number
end

puts "Sum: #{sum}"

####

numbers = [1, 2, 3]
sum = 0
numbers.each do |number|
  sum = sum + number
end

This category currently contains no pages or media.