ruby 101
DESCRIPTION
Rob Cameron, Developer at the Active Network, discusses the awesomeness that is the Ruby programming language.TRANSCRIPT
Ruby 101
Your favoritelanguage
sucks.
Ruby is the greatest
language ever made.
Ruby is the greatest
language ever made.
And if you disagree, you’re wrong.
February 24, 1993
Yukihiro “Matz” Matsumotoまつもとゆきひろ
“I wanted a scripting language that was more powerful than Perl, and more object-oriented than Python. That's why I decided to design my own language.”
“I hope to see Ruby help every programmer in the world to be productive, to enjoy programming, and to be happy.
“That is the primary purpose of the Ruby language.”
“Often people, especially computer engineers, focus on the machines. They think, ‘By doing this, the machine will run faster. By doing this, the machine will run more effectively. By doing this, the machine will something something something.’ They are focusing on machines. But, in fact, we need to focus on humans.”
for (var i=0; i<3; i++) { alert('Hello, world!');}
for (var i=0; i<3; i++) { alert('Hello, world!');}
3.times do puts 'Hello, world!'end
var people = ['Rob','Eugene','Crystal'];for (var i=0; i<people.length; i++) { alert(people[i] + ' loves Ruby');}
var people = ['Rob','Eugene','Crystal'];for (var i=0; i<people.length; i++) { alert(people[i] + ' loves Ruby');}
['Rob','Eugene','Crystal'].each do |name| puts "#{name} loves Ruby"end
var today = new Date();today.setDate(today.getDate() + 2);
var today = new Date();today.setDate(today.getDate() + 2);
2.days.from_now
“Sometimes people jot down pseudo-code on paper. If that pseudo-code runs directly on their computers, it’s best, isn’t it?
“Ruby tries to be like that, like pseudo-code that runs.”
REPL
REPL
Read - Eval - Print - Loop
REPL
Read - Eval - Print - Loop
irb
REPL
Read - Eval - Print - Loop
irbinteractive ruby
rob$
rob$ irb
rob$ irbruby-1.9.2 :001 >
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
=> ["Rob", "Eugene", "Crystal"] ruby-1.9.2 :002 >
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
=> ["Rob", "Eugene", "Crystal"] ruby-1.9.2 :002 >
people.length
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
=> ["Rob", "Eugene", "Crystal"] ruby-1.9.2 :002 >
people.length => 3 ruby-1.9.2 :003 >
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
=> ["Rob", "Eugene", "Crystal"] ruby-1.9.2 :002 >
people.length => 3 ruby-1.9.2 :003 > people.last
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
=> ["Rob", "Eugene", "Crystal"] ruby-1.9.2 :002 >
people.length => 3 ruby-1.9.2 :003 > people.last => "Crystal"
ruby-1.9.2 :004 >
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
=> ["Rob", "Eugene", "Crystal"] ruby-1.9.2 :002 >
people.length => 3 ruby-1.9.2 :003 > people.last => "Crystal"
ruby-1.9.2 :004 > people.reverse
rob$ irbruby-1.9.2 :001 > people = ['Rob','Eugene','Crystal']
=> ["Rob", "Eugene", "Crystal"] ruby-1.9.2 :002 >
people.length => 3 ruby-1.9.2 :003 > people.last => "Crystal"
ruby-1.9.2 :004 > people.reverse => ["Crystal", "Eugene",
"Rob"]
ruby-1.9.2 :005 >
The Basics
count = 42
count = 42
amount = 23.83
count = 42
amount = 23.83
language = 'Ruby'
count = 42
amount = 23.83
language = 'Ruby'
text = "#{language} is the best!"
count = 42
amount = 23.83
language = 'Ruby'
text = "#{language} is the best!"
people = ['Rob','Eugene','Crystal']
count = 42
amount = 23.83
language = 'Ruby'
text = "#{language} is the best!"
people = ['Rob','Eugene','Crystal']
person = { :name => 'Rob', :gender => 'male' }
count = 42
amount = 23.83
language = 'Ruby'
text = "#{language} is the best!"
people = ['Rob','Eugene','Crystal']
person = { :name => 'Rob', :gender => 'male' }
this = true
count = 42
amount = 23.83
language = 'Ruby'
text = "#{language} is the best!"
people = ['Rob','Eugene','Crystal']
person = { :name => 'Rob', :gender => 'male' }
this = true
that = false
count = 42
amount = 23.83
language = 'Ruby'
text = "#{language} is the best!"
people = ['Rob','Eugene','Crystal']
person = { :name => 'Rob', :gender => 'male' }
this = true
that = false
nothing = nil
['Rob','Eugene','Crystal'].each do |name| puts "#{name} loves Ruby"end
['Rob','Eugene','Crystal'].each do |name| puts "#{name} loves Ruby"end
Iterators
['Rob','Eugene','Crystal'].each do |name| puts "#{name} loves Ruby"end
Iterators
each find collect each_with_index reject
inject
$('li').each(function(index) { alert('index='+index);});
$('li').each(function(index) { alert('index='+index);});
an_array.each do |item| puts "item=#{item}"end
Loops?
Conditionals
if person.has_red_hair? Sun.burn personend
if person.has_red_hair? Sun.burn personend
unless car.sugar_in_gas_tank? car.startend
if person.has_red_hair? Sun.burn personend
unless car.sugar_in_gas_tank? car.startend
15.times { wash_hands } if person.ocd?
if person.has_red_hair? Sun.burn personend
unless car.sugar_in_gas_tank? car.startend
15.times { wash_hands } if person.ocd?
car.accelerate unless TrafficLight.is_red?
if just_worked_out and date_tonight take_a_showerend
if just_worked_out and date_tonight take_a_showerend
if love_mom or just_drunk get_a_tattooend
if just_worked_out and date_tonight take_a_showerend
if love_mom or just_drunk get_a_tattooend
if taco_bell? and not pepto_bismol_nearby? get_to_bathroom!end
!
airlines = ['Southwest','United','Delta']
airlines = ['Southwest','United','Delta']airlines.reverse => ['Delta','United','Southwest']
airlines = ['Southwest','United','Delta']airlines.reverse => ['Delta','United','Southwest']puts airlines => ['Southwest','United','Delta']
airlines = ['Southwest','United','Delta']airlines.reverse => ['Delta','United','Southwest']puts airlines => ['Southwest','United','Delta']airlines.reverse! => ['Delta','United','Southwest']
airlines = ['Southwest','United','Delta']airlines.reverse => ['Delta','United','Southwest']puts airlines => ['Southwest','United','Delta']airlines.reverse! => ['Delta','United','Southwest']puts airlines => ['Delta','United','Southwest']
everything is an object
‘Rob’.class
‘Rob’.class => String
‘Rob’.class => String
42.class
‘Rob’.class => String
42.class => Fixnum
‘Rob’.class => String
42.class => Fixnum
(23.45).class
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass => Module
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass => Module
Module.superclass
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass => Module
Module.superclass => Object
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass => Module
Module.superclass => Object
Object.superclass
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass => Module
Module.superclass => Object
Object.superclass => BasicObject
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass => Module
Module.superclass => Object
Object.superclass => BasicObject
BasicObject.superclass
‘Rob’.class => String
42.class => Fixnum
(23.45).class => Float
true.class => TrueClass
nil.class => NilClass
String.class => Class
Class.class => Class
Class.superclass => Module
Module.superclass => Object
Object.superclass => BasicObject
BasicObject.superclass => nil
Class, Object, Module and all other classes are instances of a class Class.Class, Module and Object have a circular dependency as they are in the core of the OO model
Introspection
42.methods
42.methods
[:!, :!=, :!~, :%, :&, :*, :**, :+, :+@, :-, :-@, :/, :<, :<<, :<=, :<=>, :==, :===, :=~, :>, :>=, :>>, :
[], :^, :__id__, :__send__, :abs, :abs2, :angle, :arg, :between?, :ceil, :chr, :class, :clone, :coerce, :conj, :
conjugate, :define_singleton_method, :denominator, :display, :div, :divmod, :downto, :dup, :enum_for,
:eql?, :equal?, :even?, :extend, :fdiv, :floor, :freeze, :frozen?, :gcd, :gcdlcm, :hash, :i, :imag, :imaginar
y, :initialize_clone, :initialize_dup, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_va
riable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :integer?, :is_a?, :
kind_of?, :lcm, :magnitude, :method, :methods, :modulo, :next, :nil?, :nonzero?, :numerator, :object_i
d, :odd?, :ord, :phase, :polar, :pred, :private_methods, :protected_methods, :public_method, :public_
methods, :public_send, :quo, :rationalize, :real, :real?, :rect, :rectangular, :remainder, :respond_to?, :r
espond_to_missing?, :round, :send, :singleton_class, :singleton_method_added, :singleton_methods, :
size, :step, :succ, :taint, :tainted?, :tap, :times, :to_c, :to_enum, :to_f, :to_i, :to_int, :to_r, :to_s, :trunca
te, :trust, :untaint, :untrust, :untrusted?, :upto, :zero?, :|, :~]
42.methods
[:!, :!=, :!~, :%, :&, :*, :**, :+, :+@, :-, :-@, :/, :<, :<<, :<=, :<=>, :==, :===, :=~, :>, :>=, :>>, :
[], :^, :__id__, :__send__, :abs, :abs2, :angle, :arg, :between?, :ceil, :chr, :class, :clone, :coerce, :conj, :
conjugate, :define_singleton_method, :denominator, :display, :div, :divmod, :downto, :dup, :enum_for,
:eql?, :equal?, :even?, :extend, :fdiv, :floor, :freeze, :frozen?, :gcd, :gcdlcm, :hash, :i, :imag, :imaginar
y, :initialize_clone, :initialize_dup, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_va
riable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :integer?, :is_a?, :
kind_of?, :lcm, :magnitude, :method, :methods, :modulo, :next, :nil?, :nonzero?, :numerator, :object_i
d, :odd?, :ord, :phase, :polar, :pred, :private_methods, :protected_methods, :public_method, :public_
methods, :public_send, :quo, :rationalize, :real, :real?, :rect, :rectangular, :remainder, :respond_to?, :r
espond_to_missing?, :round, :send, :singleton_class, :singleton_method_added, :singleton_methods, :
size, :step, :succ, :taint, :tainted?, :tap, :times, :to_c, :to_enum, :to_f, :to_i, :to_int, :to_r, :to_s, :trunca
te, :trust, :untaint, :untrust, :untrusted?, :upto, :zero?, :|, :~]
var method = 'first_name'eval('person.' + method)
var method = 'first_name'eval('person.' + method)
method = 'first_name'person.send(method)
2 + 2
2 + 2
2.+(2)
2 + 2
2.+(2)
2.send('+',2)
Syntactic Sugar
2 == 2
2 == 2
2.==(2)
2 == 2
2.==(2)
2.send('==',2)
Ruby removes the cruft
car = Vehicle.new('honda', 'civic', { :interior => 'beige' })
car = Vehicle.new 'honda', 'civic', :interior => 'beige'
Beautiful code
class Person attr_accessor 'gender','eyes','chin','name' def initialize(gender,eyes,chin) @gender = gender @eyes = eyes @chin = chin endend
class Person attr_accessor 'gender','eyes','chin','name' def initialize(gender,eyes,chin) @gender = gender @eyes = eyes @chin = chin endend
rob = Person.new 'male','blue','square'
class Person attr_accessor 'gender','eyes','chin','name' def initialize(gender,eyes,chin) @gender = gender @eyes = eyes @chin = chin endend
rob = Person.new 'male','blue','square'aimee = Person.new 'female','brown','cleft'
class Person attr_accessor 'gender','eyes','chin','name' def initialize(gender,eyes,chin) @gender = gender @eyes = eyes @chin = chin endend
rob = Person.new 'male','blue','square'aimee = Person.new 'female','brown','cleft'
class Person def +(other_person) gene_pool = [self,other_person] Person.new selection(gene_pool,'gender'), selection(gene_pool,'eyes'), selection(gene_pool,'chin') end def selection(gene_pool,feature) return gene_pool[rand(gene_pool.size)].send feature end private 'selection'end
baby = rob + aimee
baby = rob + aimeebaby.name = 'Jack'
baby = rob + aimeebaby.name = 'Jack'
baby.name=('Jack')
baby = rob + aimeebaby.name = 'Jack'
baby.name=('Jack')
baby.inspect
baby = rob + aimeebaby.name = 'Jack'
baby.name=('Jack')
baby.inspect
#<Person:0x0000010083fdf0 @gender="male", @eyes="blue", @chin="cleft", @name="Jack">
“When I am working on a problem I never think about beauty. I think only how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong.”
— R. Buckminster Fuller
return gene_pool[rand(gene_pool.size)].send feature
return gene_pool[rand(gene_pool.size)].send feature
class Array def random return self[rand(self.length)] endend
return gene_pool[rand(gene_pool.size)].send feature
class Array def random return self[rand(self.length)] endend
return gene_pool.random.send feature
unless [].respond_to? 'random' class Array def random return self[rand(self.length)] end endend
MonkeyPatching!!1!one!!!
David Heinemeier Hansson (DHH)“Why Ruby”
http://confreaks.net/system/assets/datas/694/original/431-rubyconf2010-keynote-why-ruby-small.mp4?1291226733
“UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things.” — Doug Gwyn
“There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code.” — Anonymous
class Array def random return self[rand(self.length)] endend
class Person attr_accessor 'gender','eyes','chin','name' def initialize(gender,eyes,chin) @gender = gender @eyes = eyes @chin = chin end
def +(other_person) gene_pool = [self,other_person] Person.new selection(gene_pool,'gender'), selection(gene_pool,'eyes'), selection(gene_pool,'chin') end def selection(gene_pool,feature) return gene_pool.random.send feature end private 'selection'end
using System;using System.Collections.Concurrent;using System.Collections.Specialized;using System.ComponentModel.Composition;using System.Diagnostics;using System.IO;using System.IO.Compression;using System.Net;using System.Text.RegularExpressions;using System.Threading;using System.Linq;using Newtonsoft.Json;using NLog;using Raven.Abstractions.Data;using Raven.Abstractions.Exceptions;using Raven.Abstractions.MEF;using Raven.Http.Abstractions;using Raven.Http.Exceptions;using Raven.Http.Extensions;using Formatting = Newtonsoft.Json.Formatting;namespace Raven.Http{ public abstract class HttpServer : IDisposable { private const int MaxConcurrentRequests = 192; protected readonly IResourceStore DefaultResourceStore; protected readonly IRavenHttpConfiguration DefaultConfiguration; private readonly ThreadLocal<string> currentTenantId = new ThreadLocal<string>(); private readonly ThreadLocal<IResourceStore> currentDatabase = new ThreadLocal<IResourceStore>(); private readonly ThreadLocal<IRavenHttpConfiguration> currentConfiguration = new ThreadLocal<IRavenHttpConfiguration>(); protected readonly ConcurrentDictionary<string, IResourceStore> ResourcesStoresCache = new ConcurrentDictionary<string, IResourceStore>(StringComparer.InvariantCultureIgnoreCase); private readonly ConcurrentDictionary<string, DateTime> databaseLastRecentlyUsed = new ConcurrentDictionary<string, DateTime>();
public int NumberOfRequests { get { return Thread.VolatileRead(ref physicalRequestsCount); } } [ImportMany] public OrderedPartCollection<AbstractRequestResponder> RequestResponders { get; set; } public IRavenHttpConfiguration Configuration { get { return DefaultConfiguration; } } public abstract Regex TenantsQuery { get; } private HttpListener listener; private static readonly Logger logger = LogManager.GetCurrentClassLogger(); private int reqNum; // concurrent requests // we set 1/4 aside for handling background tasks private readonly SemaphoreSlim concurretRequestSemaphore = new SemaphoreSlim(MaxConcurrentRequests); private Timer databasesCleanupTimer; private int physicalRequestsCount; public bool HasPendingRequests
{ get { return concurretRequestSemaphore.CurrentCount != MaxConcurrentRequests; } }protected HttpServer(IRavenHttpConfiguration configuration, IResourceStore resourceStore) {
DefaultResourceStore = resourceStore; DefaultConfiguration = configuration; configuration.Container.SatisfyImportsOnce(this); foreach (var requestResponder in RequestResponders) { requestResponder.Value.Initialize(() => currentDatabase.Value, () => currentConfiguration.Value, () => currentTenantId.Value, this); } } #region IDisposable Members public void Dispose() { databasesCleanupTimer.Dispose(); if (listener != null && listener.IsListening) listener.Stop(); foreach (var documentDatabase in ResourcesStoresCache) { documentDatabase.Value.Dispose(); } } #endregion public void Start() { listener = new HttpListener(); string virtualDirectory = DefaultConfiguration.VirtualDirectory; if (virtualDirectory.EndsWith("/") == false) virtualDirectory = virtualDirectory + "/"; listener.Prefixes.Add("http://" + (DefaultConfiguration.HostName ?? "+") + ":" + DefaultConfiguration.Port + virtualDirectory);
switch (DefaultConfiguration.AnonymousUserAccessMode) { case AnonymousUserAccessMode.None: listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication; break; case AnonymousUserAccessMode.All: listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication | AuthenticationSchemes.Anonymous; listener.AuthenticationSchemeSelectorDelegate = request => {if(request.RawUrl.StartsWith("/admin",StringComparison.InvariantCultureIgnoreCase))
return AuthenticationSchemes.IntegratedWindowsAuthentication; return AuthenticationSchemes.Anonymous; }; break; case AnonymousUserAccessMode.Get: listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication | AuthenticationSchemes.Anonymous; listener.AuthenticationSchemeSelectorDelegate = request => { return IsGetRequest(request.HttpMethod, request.Url.AbsolutePath) ? AuthenticationSchemes.Anonymous | AuthenticationSchemes.IntegratedWindowsAuthentication : AuthenticationSchemes.IntegratedWindowsAuthentication; }; break; default: throw new ArgumentException("Cannot understand access mode: " + DefaultConfiguration.AnonymousUserAccessMode); } databasesCleanupTimer = new Timer(CleanupDatabases, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); listener.Start(); listener.BeginGetContext(GetContext, null); }
private void CleanupDatabases(object state) { var databasesToCleanup = databaseLastRecentlyUsed .Where(x=>(DateTime.Now - x.Value).TotalMinutes > 10) .Select(x=>x.Key) .ToArray(); foreach (var db in databasesToCleanup) { DateTime _; databaseLastRecentlyUsed.TryRemove(db, out _); IResourceStore database; if(ResourcesStoresCache.TryRemove(db, out database)) database.Dispose(); } } private void GetContext(IAsyncResult ar) { IHttpContext ctx; try { ctx = new HttpListenerContextAdpater(listener.EndGetContext(ar), DefaultConfiguration); //setup waiting for the next request listener.BeginGetContext(GetContext, null); } catch (InvalidOperationException) { // can't get current request / end new one, probably // listner shutdown return; } catch (HttpListenerException) { // can't get current request / end new one, probably // listner shutdown return; }
if (concurretRequestSemaphore.Wait(TimeSpan.FromSeconds(5)) == false) { HandleTooBusyError(ctx); return; } try { Interlocked.Increment(ref physicalRequestsCount); HandleActualRequest(ctx); } finally
{ concurretRequestSemaphore.Release(); } } public void HandleActualRequest(IHttpContext ctx) { var sw = Stopwatch.StartNew(); bool ravenUiRequest = false; try { ravenUiRequest = DispatchRequest(ctx); } catch (Exception e) { HandleException(ctx, e); if (ShouldLogException(e))logger.WarnException("Error on request", e); } finally { try { FinalizeRequestProcessing(ctx, sw, ravenUiRequest); } catch (Exception e) { logger.ErrorException("Could not finalize request properly", e); }
protected virtual bool ShouldLogException(Exception exception) { return true; } private void FinalizeRequestProcessing(IHttpContext ctx, Stopwatch sw, bool ravenUiRequest)
{ LogHttpRequestStatsParams logHttpRequestStatsParam = null; try{ logHttpRequestStatsParam = new LogHttpRequestStatsParams(
sw, ctx.Request.Headers, ctx.Request.HttpMethod, ctx.Response.StatusCode,
ctx.Request.Url.PathAndQuery); } catch (Exception e){ logger.WarnException("Could not gather information to log request stats",
e); } ctx.FinalizeResonse(); sw.Stop(); if (ravenUiRequest || logHttpRequestStatsParam == null) return;LogHttpRequestStats(logHttpRequestStatsParam); ctx.OutputSavedLogItems(logger); }private void LogHttpRequestStats(LogHttpRequestStatsParams logHttpRequestStatsParams) { // we filter out requests for the UI because they fill the log with information // we probably don't care about them anyway. That said, we do output them if they take too // long.
if (logHttpRequestStatsParams.Headers["Raven-Timer-Request"] == "true" && logHttpRequestStatsParams.Stopwatch.ElapsedMilliseconds <= 25) return; var curReq = Interlocked.Increment(ref reqNum); logger.Debug("Request #{0,4:#,0}: {1,-7} - {2,5:#,0} ms - {5,-10} - {3} - {4}",
curReq, logHttpRequestStatsParams.HttpMethod, logHttpRequestStatsParams.Stopwatch.ElapsedMilliseconds, logHttpRequestStatsParams.ResponseStatusCode, logHttpRequestStatsParams.RequestUri, currentTenantId.Value); } private void HandleException(IHttpContext ctx, Exception e) { try { if (e is BadRequestException) HandleBadRequest(ctx, (BadRequestException)e); else if (e is ConcurrencyException) HandleConcurrencyException(ctx, (ConcurrencyException)e); else if (TryHandleException(ctx, e)) return; else HandleGenericException(ctx, e); } catch (Exception) { logger.ErrorException("Failed to properly handle error, further error handling is ignored", e); } } protected abstract bool TryHandleException(IHttpContext ctx, Exception exception);
private static void HandleTooBusyError(IHttpContext ctx) { ctx.Response.StatusCode = 503; ctx.Response.StatusDescription = "Service Unavailable"; SerializeError(ctx, new { Url = ctx.Request.RawUrl, Error = "The server is too busy, could not acquire transactional access" }); } private static void HandleGenericException(IHttpContext ctx, Exception e) { ctx.Response.StatusCode = 500; ctx.Response.StatusDescription = "Internal Server Error"; SerializeError(ctx, new { Url = ctx.Request.RawUrl, Error = e.ToString() }); } private static void HandleBadRequest(IHttpContext ctx, BadRequestException e) { ctx.SetStatusToBadRequest(); SerializeError(ctx, new { Url = ctx.Request.RawUrl, e.Message, Error = e.Message }); }
private static void HandleConcurrencyException(IHttpContext ctx, ConcurrencyException e) { ctx.Response.StatusCode = 409; ctx.Response.StatusDescription = "Conflict"; SerializeError(ctx, new { Url = ctx.Request.RawUrl, e.ActualETag, e.ExpectedETag, Error = e.Message }); } protected static void SerializeError(IHttpContext ctx, object error) { var sw = new StreamWriter(ctx.Response.OutputStream); new JsonSerializer().Serialize(new JsonTextWriter(sw) { Formatting = Formatting.Indented, }, error); sw.Flush(); } private bool DispatchRequest(IHttpContext ctx) { if (AssertSecurityRights(ctx) == false) return false; SetupRequestToProperDatabase(ctx); CurrentOperationContext.Headers.Value = ctx.Request.Headers; try { OnDispatchingRequest(ctx); if (DefaultConfiguration.HttpCompression) AddHttpCompressionIfClientCanAcceptIt(ctx);
AddAccessControlAllowOriginHeader(ctx); foreach (var requestResponderLazy in RequestResponders) { var requestResponder = requestResponderLazy.Value; if (requestResponder.WillRespond(ctx)) { requestResponder.Respond(ctx); return requestResponder.IsUserInterfaceRequest; } } ctx.SetStatusToBadRequest(); if (ctx.Request.HttpMethod == "HEAD") return false; ctx.Write( @"<html> <body> <h1>Could not figure out what to do</h1> <p>Your request didn't match anything that Raven knows to do, sorry...</p> </body></html>"); } finally { CurrentOperationContext.Headers.Value = new NameValueCollection(); currentDatabase.Value = DefaultResourceStore; currentConfiguration.Value = DefaultConfiguration; } return false; } protected virtual void OnDispatchingRequest(IHttpContext ctx){}
private void SetupRequestToProperDatabase(IHttpContext ctx) { var requestUrl = ctx.GetRequestUrlForTenantSelection(); var match = TenantsQuery.Match(requestUrl); if (match.Success == false) { currentTenantId.Value = Constants.DefaultDatabase; currentDatabase.Value = DefaultResourceStore; currentConfiguration.Value = DefaultConfiguration; } else { var tenantId = match.Groups[1].Value; IResourceStore resourceStore; if(TryGetOrCreateResourceStore(tenantId, out resourceStore)) { databaseLastRecentlyUsed.AddOrUpdate(tenantId, DateTime.Now, (s, time) => DateTime.Now);
if (string.IsNullOrEmpty(Configuration.VirtualDirectory) == false && Configuration.VirtualDirectory != "/"){ ctx.AdjustUrl(Configuration.VirtualDirectory +
match.Value); } else {ctx.AdjustUrl(match.Value); }
currentTenantId.Value = tenantId; currentDatabase.Value = resourceStore; currentConfiguration.Value = resourceStore.Configuration; } else { throw new BadRequestException("Could not find a database named: " + tenantId); } } }
protected abstract bool TryGetOrCreateResourceStore(string name, out IResourceStore database); private void AddAccessControlAllowOriginHeader(IHttpContext ctx) { if (string.IsNullOrEmpty(DefaultConfiguration.AccessControlAllowOrigin)) return; ctx.Response.AddHeader("Access-Control-Allow-Origin", DefaultConfiguration.AccessControlAllowOrigin); } private static void AddHttpCompressionIfClientCanAcceptIt(IHttpContext ctx) { var acceptEncoding = ctx.Request.Headers["Accept-Encoding"]; if (string.IsNullOrEmpty(acceptEncoding)) return; // gzip must be first, because chrome has an issue accepting deflate data // when sending it json text if ((acceptEncoding.IndexOf("gzip", StringComparison.InvariantCultureIgnoreCase) != -1)) { ctx.SetResponseFilter(s => new GZipStream(s, CompressionMode.Compress, true)); ctx.Response.AddHeader("Content-Encoding","gzip"); } else if (acceptEncoding.IndexOf("deflate", StringComparison.InvariantCultureIgnoreCase) != -1) { ctx.SetResponseFilter(s => new DeflateStream(s, CompressionMode.Compress, true)); ctx.Response.AddHeader("Content-Encoding", "deflate"); } } private bool AssertSecurityRights(IHttpContext ctx) { if (DefaultConfiguration.AnonymousUserAccessMode == AnonymousUserAccessMode.None && IsInvalidUser(ctx)) {ctx.SetStatusToUnauthorized(); return false; }
IHttpRequest httpRequest = ctx.Request; if (DefaultConfiguration.AnonymousUserAccessMode == AnonymousUserAccessMode.Get && IsInvalidUser(ctx) && IsGetRequest(httpRequest.HttpMethod, httpRequest.Url.AbsolutePath) == false ) { ctx.SetStatusToUnauthorized(); return false; } return true; } protected virtual bool IsGetRequest(string httpMethod, string requestPath) {
return (httpMethod == "GET" || httpMethod == "HEAD"); } private static bool IsInvalidUser(IHttpContext ctx) { return (ctx.User == null || ctx.User.Identity == null || ctx.User.Identity.IsAuthenticated == false); } public void ResetNumberOfRequests() {
Interlocked.Exchange(ref reqNum, 0); Interlocked.Exchange(ref physicalRequestsCount, 0); } }}
517 lines of code
webserver = TCPServer.new('127.0.0.1', 3000)while (session = webserver.accept) session.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n" request = session.gets filename = request.match(/\/(.*?) /)[1] filename = 'index.html' if filename == '' begin session.print File.read(filename) rescue Errno::ENOENT session.print 'File not found' end session.closeend
“There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.” — C.A.R. Hoare
class Person def +(other_person) gene_pool = [self,other_person] Person.new selection(gene_pool,'gender'), selection(gene_pool,'eyes'), selection(gene_pool,'chin') end def selection(gene_pool,feature) return gene_pool[rand(gene_pool.size)].send feature end private 'selection'end
class Person def +(other_person) gene_pool = [self,other_person] Person.new selection(gene_pool,'gender'), selection(gene_pool,'eyes'), selection(gene_pool,'chin') end def selection(gene_pool,feature) return gene_pool[rand(gene_pool.size)].send feature end private 'selection'end
class Person def +(other_person) gene_pool = [self,other_person] Person.new selection(gene_pool,'gender'), selection(gene_pool,'eyes'), selection(gene_pool,'chin') end def selection(gene_pool,feature) return gene_pool[rand(gene_pool.size)].send feature end private 'selection'end
Implicit return
Any statement in Ruby returns the value of the
last evaluated expression
ruby-1.9.2 :001 > puts 'Hello, world!'
ruby-1.9.2 :001 > puts 'Hello, world!'Hello, world! => nil ruby-1.9.2 :002 >
ruby-1.9.2 :001 > puts 'Hello, world!'Hello, world! => nil ruby-1.9.2 :002 > puts('Hello, world!')
ruby-1.9.2 :001 > puts 'Hello, world!'Hello, world! => nil ruby-1.9.2 :002 > puts('Hello, world!')Hello, world! => nil ruby-1.9.2 :003 >
Blabber
Talk to your servers via IM
gems
rob$
rob$ gem install rails
rob$ gem install railsrob$ gem install httparty json
require 'rubygems'require 'json'require 'httparty'
rob$ irbruby-1.9.2 :001 >
rob$ irbruby-1.9.2 :001 > HTTParty
rob$ irbruby-1.9.2 :001 > HTTPartyNameError: uninitialized constant Object::HTTParty from (irb):1 from /Users/rob/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'ruby-1.9.2 :002 >
rob$ irbruby-1.9.2 :001 > HTTPartyNameError: uninitialized constant Object::HTTParty from (irb):1 from /Users/rob/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'ruby-1.9.2 :002 > require 'httparty'
rob$ irbruby-1.9.2 :001 > HTTPartyNameError: uninitialized constant Object::HTTParty from (irb):1 from /Users/rob/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'ruby-1.9.2 :002 > require 'httparty' => true ruby-1.9.2 :003 >
rob$ irbruby-1.9.2 :001 > HTTPartyNameError: uninitialized constant Object::HTTParty from (irb):1 from /Users/rob/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'ruby-1.9.2 :002 > require 'httparty' => true ruby-1.9.2 :003 > HTTParty
rob$ irbruby-1.9.2 :001 > HTTPartyNameError: uninitialized constant Object::HTTParty from (irb):1 from /Users/rob/.rvm/rubies/ruby-1.9.2-p180/bin/irb:16:in `<main>'ruby-1.9.2 :002 > require 'httparty' => true ruby-1.9.2 :003 > HTTParty => HTTParty ruby-1.9.2 :004 >
Questions?
Workshop
Command line interface to the Active.com Search
API
It should:
It should:
• Present a list of channels and let the user select one
It should:
• Present a list of channels and let the user select one
• Prompt for (optional) keywords
It should:
• Present a list of channels and let the user select one
• Prompt for (optional) keywords• Present the top 10 results
It should:
• Present a list of channels and let the user select one
• Prompt for (optional) keywords• Present the top 10 results• Let the user select one result
It should:
• Present a list of channels and let the user select one
• Prompt for (optional) keywords• Present the top 10 results• Let the user select one result • Present the title, location, start
date and asset ID of the selected event
Example
What you need to know
Required Gems
require 'json'require 'httparty'
Search API Endpoint
http://api.amp.active.com/search? v=json& k=keywords& m=meta:channel%3DRunning& api_key=wuhmn9ye94xn3xnteudxsavw
HTTP call
HTTParty.get(url)
Parse JSON
JSON.parse(text)
HTTP call
HTTParty.get(url)
Get user inputuser_input = gets
Get user inputuser_input = gets
Remove newlinesuser_input.chomp
Get user inputuser_input = gets
Print to STDOUTprint 'text'puts 'text'
Remove newlinesuser_input.chomp
Iterate through array with index
my_array.each_with_index do |item,i| puts “#{item} is at position #{i}”end
Exitexit 0
Iterate through array with index
my_array.each_with_index do |item,i| puts “#{item} is at position #{i}”end
Define methodsdef method_name(first, second) # codeend
Create classes
class ClassName def initialize # code endend
Questions?
require 'json'require 'httparty'
HTTParty.get(url)
JSON.parse(text)
user_input = gets
print 'text'puts 'text'
user_input.chomp
my_array.each_with_index do |item,i| puts “#{item} is as position #{i}”end
http://api.amp.active.com/search? v=json& k=keywords& m=meta:channel%3DRunning& api_key=wuhmn9ye94xn3xnteudxsavw
exit 0
“I am rarely happier than when spending an entire day programming my computer to perform automatically a task that would otherwise take me a good ten seconds to do by hand.” — Douglas Adams
Learning a new language
EGO
“Would you rather be a king of fools or a
student of masters?”
The End