Práctica: Estadísticas de Visitas al Acortador de URLs

En esta práctica extendemos la anterior 46.9 con información estadística acerca de las visitas. Se pide que se presenten gráficos estadísticos (barras, etc.) del número de visitas por día, por país, etc.

La información de las visitas se guardará en una tabla Visit.

Cada objeto Visit representará una visita a la URL corta y contendrá información acerca de la visita:

La Tabla Principal

Un objeto de la tabla principal tiene asociados varios objetos Visit.
class ShortenedUrl
  include DataMapper::Resource

  property :id, Serial
  property :short, Text
  property :url, Text

  has n, :visits
end

Obteniendo el País para una IP dada

Queremos encontrar el país desde el que viene cada vistante. Cada vez que se crea un objeto Visit y que se le asocia con el objeto de la tabla principal intentaremos obtener la dirección IP y a partir de ella la información sobre el país.

Veamos una sesión con Pry para obtener el país asociado con una IP:

[1] pry(main)> require 'restclient'
=> true
[2] pry(main)> require 'xmlsimple'
=> true

[3] pry(main)> xml = RestClient.get "http://api.hostip.info/get_xml.php?ip=193.145.124.21";
[4] pry(main)> puts xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<HostipLookupResultSet version="1.0.1" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.hostip.info/api/hostip-1.0.1.xsd">
 <gml:description>This is the Hostip Lookup Service</gml:description>
 <gml:name>hostip</gml:name>
 <gml:boundedBy>
  <gml:Null>inapplicable</gml:Null>
 </gml:boundedBy>
 <gml:featureMember>
  <Hostip>
   <ip>193.145.124.21</ip>
   <gml:name>(Unknown city)</gml:name>
   <countryName>SPAIN</countryName>
   <countryAbbrev>ES</countryAbbrev>
   <!-- Co-ordinates are unavailable -->
  </Hostip>
 </gml:featureMember>
</HostipLookupResultSet>
=> nil
[5] pry(main)> xml.class
=> String

[10] pry(main)> cc = XmlSimple.xml_in(xml.to_s)
=> {"version"=>"1.0.1",
 "xmlns:gml"=>"http://www.opengis.net/gml",
 "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
 "xsi:noNamespaceSchemaLocation"=>
  "http://www.hostip.info/api/hostip-1.0.1.xsd",
 "description"=>["This is the Hostip Lookup Service"],
 "name"=>["hostip"],
 "boundedBy"=>[{"Null"=>["inapplicable"]}],
 "featureMember"=>
  [{"Hostip"=>
     [{"ip"=>["193.145.124.21"],
       "name"=>["(Unknown city)"],
       "countryName"=>["SPAIN"],
       "countryAbbrev"=>["ES"]}]}]}
[11] pry(main)> cc["featureMember"][0]['Hostip'][0]['countryAbbrev']
=> ["ES"]

To implement this, we use the after callback mechanism in the Visit object, where we call a method after the object is created.

   after :create, :set_country

La Tabla Visit

Para lograr que cada vez que se crea un objeto Visit se guarde el país en la tabla Visit la clase será algo parecido a esto:

class Visit
  include DataMapper::Resource

  property  :id,          Serial
  property  :created_at,  DateTime
  property  :ip,          IPAddress
  property  :country,     String
  belongs_to  :link

  after :create, :set_country

  def set_country
    xml = RestClient.get "http://api.hostip.info/get_xml.php?ip=#{ip}"··
    self.country = XmlSimple.xml_in(xml.to_s, ...
    self.save
  end
  ...
end

Averiguar la IP

Este método ilustra múltiples formas de obtener la IP de la visita:

def get_remote_ip(env)
  puts "request.url = #{request.url}"
  puts "request.ip = #{request.ip}"
  if addr = env['HTTP_X_FORWARDED_FOR']
    puts "env['HTTP_X_FORWARDED_FOR'] = #{addr}"
    addr.split(',').first.strip
  else
    puts "env['REMOTE_ADDR'] = #{env['REMOTE_ADDR']}"
    env['REMOTE_ADDR']
  end
end

Gráficos con chartkick

Este ejemplo crguezl/sinatra-chartkick-example:

~/sinatra/sinatra-chartkik(master)]$ git remote -v
origin  git@github.com:crguezl/sinatra-chartkick-example.git (fetch)
origin  git@github.com:crguezl/sinatra-chartkick-example.git (push)
[~/sinatra/sinatra-chartkik(master)]$ pwd -P
/Users/casiano/local/src/ruby/sinatra/sinatra-chartkik
muestra como usar chartkick para generar gráficos:
[~/sinatra/sinatra-chartkik(master)]$ cat sinchart2.rb 
require 'sinatra'
require 'sinatra/reloader' if development?
require 'chartkick'

template :layout do
  <<LAYOUT
<html>
  <head>
    <script src="http://www.google.com/jsapi"></script>
    <script src="chartkick.js"></script>
  </head>
  <body>
    <%= yield %>
  </body>
</html>
LAYOUT
end

template :index do
  <<INDEX
    <%= pie_chart({"Football" => 10, "Basketball" => 5}) %>
INDEX
end

get '/' do
  erb :index
end



Subsecciones
Casiano Rodriguez León 2015-01-07