Class: Swivel::Connection

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

Defined Under Namespace

Classes: Config

Constant Summary collapse

@@calls =
[]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = Hash.new) ⇒ Connection

Instantiate a connection to Swivel. Use the connection to query or upload, append, or replace data. Passed in options will take precedence over values set in ~/.swivelrc.



693
694
695
696
697
698
# File 'lib/swivel.rb', line 693

def initialize options = Hash.new
  @config = Config::DEFAULT_CONFIG.clone
  @config.merge! options
  @headers = options[:headers] || Hash.new
  @headers.merge! 'Accept' => 'application/xml'
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



687
688
689
# File 'lib/swivel.rb', line 687

def config
  @config
end

#disable_auto_refreshObject

Returns the value of attribute disable_auto_refresh.



687
688
689
# File 'lib/swivel.rb', line 687

def disable_auto_refresh
  @disable_auto_refresh
end

Class Method Details

.reset_calls!Object



709
710
711
# File 'lib/swivel.rb', line 709

def self.reset_calls!
  @@calls = []
end

Instance Method Details

#call(path, params = Hash.new, method = :get, extra_headers = Hash.new) ⇒ Object

Call Swivel’s REST endpoint. This method actually performs the HTTP stuff that you need. and returns objects constructed from the returned XML. If an appropriate class is not available, just returns the XML string.

# returns an object that's a subclass of Swivel::Response
user = swivel.call '/users/1000010'
user.class # => Swivel::User

users = swivel.call '/users'
users.class # => Swivel::List

# returns a string (because an appropriate Swivel::Response subclass doesn't exist)
echo = swivel.call '/test/echo/howdy'
echo.class # => String

However, calling all of those endpoints is tedious. For swivel’s main objects there are some helper methods to get stuff:

data_set = swivel.data_set 1
data_set.class  # => Swivel::DataSet
data_set.id     # => 1

data_sets = swivel.data_sets

some_graph = swivel.graph 1232132

pwn3r = swivel.user 'visnu'
pwn3r.name     # => 'visnu'

All these convenience methoods (yeah, methoods) really do is prettify a get call to:

/object_type  or /object_type/id


745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
# File 'lib/swivel.rb', line 745

def call path, params = Hash.new, method = :get, extra_headers = Hash.new
  path, query_string = path.split("?")
  params = requestify(params)
  query_string += "&" if params and query_string
  params = "#{query_string}#{params}"
  path = "/#{config[:path_prefix]}#{path}"  unless config[:path_prefix].blank?
  response = nil
  elapsed_time = Benchmark.realtime do 
    response =
      Net::HTTP.start config[:host], config[:port] do |http|
        request_class = "Net::HTTP::#{method.to_s.camelize}".constantize
        if [:delete, :post, :put].include? method
          request = request_class.new path, headers.merge(extra_headers)
          http.read_timeout = config[:timeout_up]
          http.request request, params
        else
          request = request_class.new "#{path}?#{params}", headers.merge(extra_headers)
          http.read_timeout = config[:timeout_down]
          http.request request
        end
      end
  end
  @@calls << {:url => path, :time => elapsed_time} 
  body = response.body
  log "#{config[:host]}:#{config[:port]}/#{path}"
  log body
  case response.content_type
  when 'application/xml'
    doc = REXML::Document.new body
    if doc.root.elements[1].name.downcase == 'list'
      h = CobraVsMongoose.xml_to_hash body
      h = h["response"] if h["response"]
      resource = h['list']['@resource']
      count = h['list']['@count']
      total = h['list']['@total']
      h = h['list'][resource]
      h ||= []
      List.new nil, self, h, resource, count, total
      
    # 2c) enables rails ActiveRecord::Base#to_xml style lists (at root)
    elsif doc.root.elements.to_a.size == 1 and
        doc.root.elements[1].attributes['type'] == 'array'
      h = CobraVsMongoose.xml_to_hash body
      h = h["response"] if h["response"]

      list_name = doc.root.elements[1].name
      member_name = (h[list_name].keys.find{|k| !/^@/.match(k) } || list_name.singularize)
      list = h[list_name][member_name] || []

      list = [list] unless list.is_a?(Array)
      total = (doc.root.attributes['total'].to_i rescue list.size)
      # hack to get the gem to parse all array types correctly
      hack_list = list.map{|i| {member_name => i}}
      Swivel::List.new nil, @connection, hack_list, member_name, list.size, total

    else
      Response.class_for(doc.root.elements[1].name).new body, self, nil
    end
  when 'text/csv', 'text/plain'
    body
  else
    raise "Unknown content type: #{response.content_type}"
  end
rescue Exception => e
  #xml.blank? ? nil : xml
  warn "Failure while communicating with swivel"
  warn "Request: #{config[:host]}:#{config[:port]}#{path}"
  warn "Response: #{body}"
  raise e
end

#call!(*args) ⇒ Object



816
817
818
819
820
# File 'lib/swivel.rb', line 816

def call! *args
  returning call(*args) do |r|
    r.raise  if Swivel::Error === r
  end
end

#create_graph(options = Hash.new, persist = false) ⇒ Object

Lets you create a transient graph in Swivel.

If you pass in options => true, your transient graph will be immediately saved.

# build a transient graph, then save it.
transient_graph = swivel.create_graph {...}
graph = transient_graph.save

# a faster way of doing the same thing.
graph = swivel.create_graph {...}.merge(:save => 'true')

Transient graphs exist in Swivel so you can cheaply play around with different visualizations, without saving them to Swivel’s database. The idea is to let you quickly create visualizations and discard the duds and keep the gems. So, create graphs with impunity! And just save the ones you like.

From an object perspective, the big differences between transient and persistent graphs are:

transient_graph = swivel.create_graph {...}

# transient graphs do not have ids.  thus, they are not accessible
# at their own page at swivel.com.
transient_graph.id # => nil

# transient graphs have a 'transient' variant
transient_graph.variant # => 'transient'

# transient graphs have digests whereas persisted graphs do not.
# digests act as a unique identifier for transient graphs.  e.g.,
# you'd use the digest to build urls to the transient graph's image.
transient_graph.digest.blank? # => false
transient_graph.image_url.blank? # => false

Please don’t save transient graphs you don’t want to end up keeping around. It’s the most environmentally friendly thing to do.

required parameters

  • options

    • LineGraph, BarGraph, HorizontalBarGraph, PieGraph, or ScatterGraph

– TODO: that sucks. i want: line, bar, hbar, pie, scatter, spark ++

  • options a comma separated list of data_column ids

  • options the x-axis data_column id

optional parameters

  • options a comma separated list of join data_column ids

  • options a comma separated list of group by data_column ids

  • options aggregation functions for group_by_columns. one of: AvgFunction, ConcatFunction, MaxFunction, MinFunction, SumFunction, CountFunction, DistinctCountFunction

  • options a comma separated list of order-by data_column ids

  • options which sort to apply to the order_by_column. one of: ASC, DESC, ALPHABETICAL, UNSORTED

  • options time-scale of your X-axis, if applicable. one of: YEARLY, QUARTERLY, MONTHLY, WEEKLY, DAILY, HOURLY, BY_MINUTE, BY_SECOND

  • options time-range of your X-axis, if applicable. Swivel understands many human readable date/time formats. Examples: “3 years ago to now”, “1 month”, “1992-2004”.

  • options y-scale of the graph you’re creating, if applicable. one of: ABSOLUTE, AVERAGE, RANGE, RELATIVE, PERCENT



1015
1016
1017
1018
1019
1020
1021
1022
# File 'lib/swivel.rb', line 1015

def create_graph options = Hash.new, persist = false
  options.merge! :save => 'true' if persist
  unless options.has_key? :order_by_column
    options.merge! :order_by_column => options[:columns].split(',').first,
      :order_by_direction => 'DESC'
  end
  call '/graphs', options, :post
end

#inspectObject



700
701
702
# File 'lib/swivel.rb', line 700

def inspect
  super.sub /:api_key=>"[^"]*"/, ':api_key=>[FILTERED]'
end

#log(str) ⇒ Object



822
823
824
825
826
827
828
829
830
# File 'lib/swivel.rb', line 822

def log str
  unless config[:logfile] == nil
    unless @logger
      @logger = Logger.new(config[:logfile]) 
      @logger.level = Logger::INFO
    end
    @logger.info(str)
  end
end

#name_with_prefix(prefix, name) ⇒ Object



1039
1040
1041
# File 'lib/swivel.rb', line 1039

def name_with_prefix(prefix, name)
  prefix ? "#{prefix}[#{name}]" : name.to_s
end

#requestify(parameters, prefix = nil) ⇒ Object

copied verbatim from actioncontroller::integration::session



1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
# File 'lib/swivel.rb', line 1026

def requestify(parameters, prefix=nil)
  if Hash === parameters
    return nil if parameters.empty?
    parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
  elsif Array === parameters
    parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
  elsif prefix.nil?
    parameters
  else
    "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
  end
end

#upload!(options = Hash.new) ⇒ Object Also known as: create_data_set

Performs an upload, append, or replace to a data_set in Swivel.

# upload a file to swivel
data_set = swivel.upload {...}

In order to upload, append, or replace, you must have a valid api key. Get a Swivel API key.

# append to a data_set already in Swivel
more_data = `cat /tmp/more.csv`
data_set = swivel.append {:data => more_data}.merge(:id => data_set_id)

# replace underlying data in a data_set that already exists on Swivel
new_data = `cat /tmp/new.csv`
data_set = swivel.replace {:data => new_data}.merge(:id => data_set_id)

In order to append or replace a data_set, you must be the owner of the data. The new data must conform to the same column structure as the original data.

required parameters

  • options - data_set id that you want to append/replace. (append, replace only)

  • options - name for the data_set. (upload only)

  • options - citation for the data_set. e.g., U.N. (upload only)

  • options - a string of actual csv data. when appending or replacing data, a header row is optional. (upload, append, and replace)

  • options - tags for the data_set (upload only)

optional parameters (upload only; not for append or replace)

  • options - description for the data_set

  • options - citation url for the data set. e.g., un.org

  • options - when true, swivel tries to figure out the column names, column types, and column separator character automatically from the csv data. when missing or false, you must also supply options, options, and options.

  • options - a comma-separated string of column names. only send this option when auto_estimate is missing or false.

  • options - a comma-separated string of column types. possible types are DateTimeDataColumn, BooleanDataColumn, CurrencyDataColumn, NumberDataColumn, WholeNumberDataColumn, PercentageDataColumn, TextDataColumn. only send this option when auto_estimate is missing or false.

  • options - a character. usually a ‘,’. only send this option when auto_estimate is false.

  • options - a url to an image you’d like to associate with your data_set.

  • options - a url to the source for your image, for proper attribution.

More examples online

A few limitations since swivel.rb is yet nascent and in its formative days.

  • You must post data as a string of text.

  • Actually, that’s it, I think.



911
912
913
914
915
916
917
918
919
920
921
922
923
924
# File 'lib/swivel.rb', line 911

def upload! options = Hash.new
  opts = options.clone
  opts[:mode] ||= 'initial'

  uri, method =
    case opts[:mode]
    when 'append', 'replace'
      ["/data_sets/#{opts[:id]}", :put]
    else
      opts.reverse_merge! DEFAULT_UPLOAD_OPTIONS
      ['/data_sets', :post]
    end
  call! uri, opts, method
end