Saturday, November 21, 2015

Quick Language Reference: Ruby

# interactive ruby shell:

    # install on linux
    yum -y install ruby ruby-irb

    # install on windows
    use cygwin, install all ruby packages.  includes gem and irb

    # To quit IRB, type quit, exit or just hit Control-D.

    # load file
    # or put ruby code in  ~/.irbrc
    irb -r 'file'

    # dynamically load file
    irb
    require 'file'


# equiv of print_r, var_dump (think "pretty print")

    pp object

# hash tag

    #!/usr/bin/env ruby

# comments

    #

# define/set variable

    a = 1
    b = "some string"

    c = nil

#print

    puts "hi"

# read line

    line = gets


# define method/function

    def hi(name)
        puts "hi #{name}!"
    end


# call function
    hi
    hi("name")

# introspection: print all methods of object

    require 'pp'
    pp 5.methods



# default values, functions in string

    def hi(name = "World")
        puts "Hello #{name.capitalize}!"
    end


# simple class

    class Greeter

      # constructor
      def initialize(name = "World")
        # use meta char @ to access class properties
        @name = name
      end

      def say_hi
        puts "Hi #{@name}!"
      end

      def say_bye
        puts "Bye #{@name}, come back soon."
      end

    end


# instantiate class with .new

    g = Greeter.new("John Smith")
    g.say_hi
    g.say_bye


# list public methods for object:

    # include all ancestors
    Greeter.instance_methods

    # only in immediate class
    Greeter.instance_methods(false)


# test if a method is defined for the object

    # is name defined on the object?
    g.respond_to?("name")


# alter existing class

    #NOTE: The changes will be present in any new objects you create and even available in existing objects of that class.

    class Greeter

        # public getter and setter  
       attr_accessor :name

        # only getter
       attr_reader :thing

    end


# array

    names = ["Albert", "Brenda", "Charles", "Dave", "Englebert"]

     
    # size:
    names.size  
 

# loop over array

    # pipes into bound variable 'name'
     names.each do |name|
 
    puts "Hello #{name}!"
     end

# join

    names.join(", ")


# hash

    H = Hash["a" => 100, "b" => 200]

    puts "#{H['a']}"
    puts "#{H['b']}"

    #This will produce following result:

    #100
    #200


# get hash keys as array

    keys = H.keys
    puts "#{keys}"

# loop over hash

    hash.each do |key, array|
      puts "#{key}-----"
      puts array
    end

    hash.keys.sort.each do |key|
      puts "#{key}-----"
      hash[key].each { |val| puts val }
    end


    puts h.sort.map {|k,v| ["#{k}----"] + v}


# hash methods:

        http://www.tutorialspoint.com/ruby/ruby_hashes.htm


# duck typing.  you can probe capabilities of object

    class Greeter2
        def say_bye
          if @names.nil?
            puts "..."
          elsif @names.respond_to?("join")
            # Join the list elements with commas
            puts "Goodbye #{@names.join(", ")}. "
          else
            puts "Goodbye #{@names}. "
          end
        end
    end


# test if class file is called directly

    if __FILE__ == $0
        # I am filename  

    end


# parse standard input stdin

    #!/usr/bin/env ruby

    chars = 0
    words = 0
    lines = 0

    # Count the stuff

    while line = gets
      chars += line.size
      words += line.split.size
      lines += 1
    end

    # Display the results

    puts "Chars=%s, Words=%s, Lines=%s" % [
      chars, words, lines
    ]


# read all lines

    lines = readlines
    puts "Chars=%d, Words=%d, Lines=%d" % [lines.join.size, lines.join.split.size, lines.size]


# code blocks

    Procs are objects that can be called.
    Sending the call method to the proc object executes the code block.
    The [] operator is a synonym for call.


    {
        |boundvariable|
    }


    do |var|

    end


    block = proc { |x| puts "Called with #{x}" }

    # calling
    block.call(1)
    block.call(2)

    block[222]


# using blocks (notes from Matz and the interwebs)

    Matz: Blocks are basically nameless functions. You may be familiar with the lambda from other languages like Lisp or Python. Basically, you can pass a nameless function to another function, and then that function can invoke the passed-in nameless function. For example, a function could perform iteration by passing one item at a time to the nameless function. This is a common style, called higher order function style, among languages that can handle functions as first class objects. Lisp does it. Python does it.  Even C does it with function pointers. Many other languages do this style of programming. In Ruby, the difference is mainly a different kind of syntax for higher order functions. In other languages, you have to specify explicitly that a function can accept another function as an argument. But in Ruby, any method can be called with a block as an implicit argument. Inside the method, you can call the block using the yield keyword with a value.

    SO: Blocks are not objects, they are syntactic structures; this is why they cannot be assigned to a variable. This is a privilege reserved for objects.
    Blocks are closures -- they're functions that close over the enclosing scope. They don't conceptually "belong to" a given object.

    Blocks that directly follow a method call are implicitly passed to the method.
    The method may call the proc by invoking yield and passing any arguments.
    You can test for the presence of a block using block_given?.
    yield(arg) if block_given?


    def three_times(arg)
      yield(arg)
      yield(arg)
      yield(arg)
    end

    three_times("x") { |val| puts val }
    three_times(22) do |thing|
      puts "Got #{thing}"
    end

    # output

    x
    x
    x
    Got 22
    Got 22
    Got 22


    The block may be explicitly passed using the & operator.
    def three_times(arg, &block)
      block.call(arg)
      block.call(arg)
      block.call(arg)
    end


# lambda vs procs

    There are two slight differences between lambda and Proc.new.

    First, argument checking. The Ruby documentation for lambda states: Equivalent to Proc.new, except the resulting Proc objects check the number of parameters passed when called.. Here is an example to demonstrate this:

    Second, there is a difference in the way returns are handled from the Proc. A return from Proc.new returns from the enclosing method (acting just like a return from a block.


    def try_ret_procnew
        ret = Proc.new { return "Baaam" }
        ret.call
        "This is not reached"
    end

    # prints "Baaam"
    puts try_ret_procnew


    While return from lambda acts more conventionally, returning to its caller:

    def try_ret_lambda
        ret = lambda { return "Baaam" }
        ret.call
        "This is printed"
    end

    # prints "This is printed"
    puts try_ret_lambda


    I would probably recommend using lambda instead of Proc.new when possible.


#  unit testing

    require 'test/unit'
    require 'trio'

    class TestTrio < Test::Unit::TestCase
      def setup
        @trio = Trio.new(1,2,3)
      end

      def test_trio
        assert_equal [1,2,3], @trio.to_a
      end
    end


# database io

    require 'dbi'

    db = DBI.connect("DBI:Pg:poc", 'jim', nil)
    sql = %{
      SELECT name, id
      FROM users
      WHERE id < 4
    }
    db.select_all(sql) do |row|
      puts "User #{row['name']}, id #{row['id']}"
    end
    db.disconnect


# composite pattern, loops

    << is the polymorphic append operator.
    { ... } is a code block. It is automatically passed to the each function.
    |installer| is an argument to the code block.
    list.each { |item| code } is equivalent to the for loop
    do ... end is also code block.


    class MasterInstaller
      def initialize
        @installers = []
      end

      def register(installer)
        @installers << installer
      end

      def install
        for installer in @installers
          installer.install
        end
      end

      def uninstall
        @installers.each { |installer|
          installer.uninstall
        }
      end

      def backup
        @installers.each do |installer|
          installer.backup
        end
      end
    end


# what's the ?

    ? as a method name suffix (e.g. include?) indicates that the method returns a true/false value.

# includes  (load module)

    require: finds the file and loads it (once)
 
    new: sent to a Class object will cause a new instance to be created. Parameters passed to new will automatically be passed to initialize.
 
    Parenthesis are optional if the result is not ambiguous.
 
    ARGV is a list of command line arguments
 
    No interface declaration is needed for installers. As long as they implement the needed methods, the master installer can use them.
 

    # find file, load
    require 'masterinstaller'
    require 'fileinstaller'
    require 'sqlinstaller'

    m = MasterInstaller.new

    m.register(FileInstaller.new("xyz.rb", "bin"))
    m.register SqlInstaller.new(
      "INSERT INTO a_table VALUES(1,2,3);" )

    commands = ['install', 'uninstall', 'backup']
    ARGV.each do |command|
      puts "Command is #{command}"
      m.send(command) if commands.include? command
      puts
    end


# reserved words

 
    BEGIN        do        next    then
    END            else    nill    true
    alias        elsif    not        undef
    and            end        or        unless
    begin        ensure    redo    until
    break        false    rescue    when
    case        for        retry    while
    class        if        return    while
    def            in        self    __FILE__
    defined?    module    super    __LINE__


# gotchas:

 
    No Primitives. Integers and floats are objects
    -1.abs => 1

    No Semi-colons
    nil, not null

    nil is an object
    nil.nil? => true

    nil and false are false
    everything else, including 0, is true

    Picky about spaces
    f (arg) gives warning.
    f(arg) is ok.


    Different type model
    Duck Typing!
    No interfaces needed.

    Expression oriented syntax.
    Almost everything returns a value
    x = if a==0 then 5 else 1 end
    Methods automatically return their last expression.

    Single Inheritance
    But mixins are available

    Per-object methods are allowed (singleton methods)
    Classes are always open (even built in classes)


# messages

    OO in Java is defined in terms of calling functions
    Member functions, static functions, etc.
    OO in Ruby is defined in terms of sending messages
    Sending messages, obj.send(:method_name)

    class Proxy
      def initialize(target)
        @target = target
      end
      def method_missing(sym, *args, &block)
        @target.send(sym, *args, &block)
      end
    end

    class Dog
      def talk; puts "WOOF"; end
    end

    d = Dog.new
    p = Proxy.new(d)

    p.talk

# file

Here, the File.open method takes a block. It then opens a new file (in "append" mode), and yields the open file into the block. When the block completes, Ruby closes the file. Except that Ruby doesn't just close the file when the block completes; it guarantees that the File will be closed, even if executing the block results in a raise. Let's take a look at the implementation of File in Rubinius:

    def append(location, data)
      path = Pathname.new(location)
      raise "Location does not exist" unless path.exist?
   
      File.open(path, "a") do |file|
        file.puts YAML.dump(data)
      end
   
      return data
    end

# modules are called "gems" or "ruby-gems"

    # install with yum
    yum install rubygem-json


# introspection

    Depending on what information you're looking for, try:

    obj.methods
    and if you want just the methods defined for obj (as opposed to getting methods on Object as well)

    obj.methods - Object.methods
    Also interesting is doing stuff like:

    obj.methods.grep /to_/
    To get instance variables, do this:

    obj.instance_variables
    and for class variables:

    obj.class_variables


# list all installed gems

    gem list

# list all remote gems

    gem list rhc --remote --all

No comments: