swivel 0.0.8 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README +31 -63
  2. data/lib/swivel.rb +251 -56
  3. metadata +2 -2
data/README CHANGED
@@ -16,7 +16,8 @@ swivel.rb is a smallish bit that lets you interface with Swivel's REST API.
16
16
 
17
17
  == The REST API
18
18
 
19
- TODO: some copy
19
+ The Swivel API is mostly RESTful. You can read and write many kinds of
20
+ resources. Here's the list.
20
21
 
21
22
  ------ ----------- ------------
22
23
  method uri what it does
@@ -46,9 +47,13 @@ TODO: some copy
46
47
  get /rest/users/#{id} show user #{id}
47
48
  put /rest/users/#{id} update user #{id}
48
49
 
49
- TODO: optional parameters for lists, etc.
50
- TODO: search
51
- TODO: echo
50
+ (non-RESTful methods)
51
+ get /rest/test/echo/#{text} echo some text
52
+ get /rest/search search for stuff
53
+
54
+ Most method calls take parameters. Check out the
55
+ {Swivel API Explorer}[http://swivel.com/api/list] to try calls interactively.
56
+ Also, check out some {sample code}[http://swivel.com/api/code].
52
57
 
53
58
  == Make it go go racer
54
59
 
@@ -87,13 +92,14 @@ Never fear, it's installed when you install the gem.
87
92
 
88
93
  $ ri Swivel
89
94
 
90
- Another nice one:
95
+ Other nice ones:
91
96
 
97
+ $ ri Swivel::Connection
92
98
  $ ri Swivel::Response
93
99
 
94
100
  == Examples. Worth a thousand words
95
101
 
96
- Ready, set, ... wait. Got an API key? Get an API key (http://swivel.com/api/key).
102
+ Ready, set, ... wait. Got an API key? {Get an API key}[http://swivel.com/api/key].
97
103
  Try to keep your API key close to your chest, safely tucked away from prying eyes
98
104
  and dangers of the "real world"... dangers like parking tickets and untied shoelaces.
99
105
  It's like a password, so don't share it.
@@ -101,6 +107,9 @@ It's like a password, so don't share it.
101
107
  # writes your api key into ~/.swivelrc
102
108
  swivel -k <your api key>
103
109
 
110
+ (Did you notice you got a `swivel` at your command line? Sneaky! It's part of the
111
+ gem. And it can query or pipe data into Swivel from your terminal!)
112
+
104
113
  Now, back to it. Ready, set, go!
105
114
 
106
115
  $ irb
@@ -223,6 +232,14 @@ TODO: explain it in this here table
223
232
  * time_scale
224
233
  * aggregation_function
225
234
 
235
+ == Uploading Data
236
+
237
+ See Swivel::Connection#upload
238
+
239
+ == Create Graphs
240
+
241
+ TODO
242
+
226
243
  == Something completely different: A tryst at the command line
227
244
 
228
245
  The command line program lets you query Swivel or upload data into Swivel.
@@ -250,8 +267,8 @@ It uses a ~/.swivelrc file to remember settings. If you don't have a
250
267
  -rw-rw-r-- 1 huned huned 105 Jun 4 05:04 /home/huned/.swivelrc
251
268
 
252
269
  Note, however, that the api_key setting is blank. Once you finagle an api key,
253
- run `swivel -k <your api key>` to update your ~/.swivelrc. (Remember: finagle an api
254
- key from http://swivel.com/api/key.)
270
+ run `swivel -k <your api key>` to update your ~/.swivelrc. (Remember: {finagle an api
271
+ key}[http://swivel.com/api/key].)
255
272
 
256
273
  $ cat ~/.swivelrc
257
274
  ---
@@ -302,67 +319,18 @@ Edit by Visnu: Huned wrote this at 4am. He tired out right here.
302
319
 
303
320
  == Feedback
304
321
 
305
- Feedback, comments, and (especially) patches welcome at developer@swivel.com.
322
+ Feedback, comments, and (especially) patches welcome at mailto:developer@swivel.com.
323
+
324
+ You can rcov swivel.rb by running this from the top level directory:
325
+
326
+ $ rcov -Ilib -t test/test_swivel.rb
306
327
 
307
328
  == Respek
308
329
 
309
330
  Respeks to _why, errtheblog, 37signals, our moms, and of course the community.
310
- Wanna write code with us? http://swivel.com/about/jobs
331
+ Wanna write code with us? {We're hiring!}[http://swivel.com/about/jobs]
311
332
 
312
333
  == License
313
334
 
314
335
  This software is licensed under the exact same license as Ruby itself. Peace
315
336
  out.
316
-
317
- upload options
318
- mode
319
- initial append replace
320
-
321
- auto_estimate
322
- ordered_column_names
323
- ordered_column_types
324
- column_separator
325
-
326
- original_asset_name
327
- original_asset_path
328
- data (or data_content)
329
-
330
- name
331
- color
332
- description
333
- citation
334
- citation_url
335
- image_url
336
- image_source_url
337
- display_tags
338
- public_tags
339
-
340
- graph options
341
- graph_type
342
- BarGraph LineGraph HorizontalBarGraph PieGraph ScatterGraph
343
- width
344
- height
345
- limit
346
- image_url
347
- name
348
- description
349
- display_tags
350
- time_range
351
- ???
352
- time_scale
353
- hourly daily weekly quarterly yearly
354
- scale
355
- absolute average relative range percent
356
-
357
- variant
358
- default thumbnail sparkline preview share
359
-
360
- columns
361
- order_by_direction
362
- asc desc unsorted alpha
363
- order_by_column
364
- x_axis_column
365
- join_on_columns
366
- group_by_columns
367
- highlight_columns
368
-
data/lib/swivel.rb CHANGED
@@ -59,69 +59,67 @@ end
59
59
  # ==Overview
60
60
  #
61
61
  # Create a new connection to swivel. Grabs options from ~/.swivelrc, creating
62
- # it if necessary.
62
+ # it if necessary. ({Get a Swivel API key}[http://swivel.com/api/key])
63
63
  #
64
64
  # swivel = Swivel::Connection.new
65
65
  #
66
66
  # Get data from Swivel
67
67
  #
68
68
  # # show a data_set's name
69
- # data_set = swivel.call '/rest/data_sets/1000001'
69
+ # data_set = swivel.data_set 1000001
70
70
  # data_set.name # => "American Longevity"
71
71
  #
72
72
  # # list data_sets' names
73
- # data_sets = swivel.call '/rest/data_sets'
73
+ # data_sets = swivel.data_sets
74
74
  # data_sets.collect do |data_set|
75
75
  # data_set.name
76
76
  # end
77
77
  #
78
78
  # # show a data_column's name
79
- # data_column = swivel.call '/rest/data_columns/1000343'
79
+ # data_column = swivel.data_column 1000343
80
80
  # data_column.name # => "Average Per Capita Income"
81
81
  #
82
82
  # # list data_columns' names
83
- # data_columns = swivel.call '/rest/data_columns'
83
+ # data_columns = swivel.data_columns
84
84
  # data_columns.collect do |data_column|
85
85
  # data_column.name
86
86
  # end
87
87
  #
88
88
  # # show a user's name
89
- # user = swivel.call '/rest/user/1000010'
89
+ # user = swivel.user 1000010
90
90
  # user.name # => "huned"
91
91
  #
92
92
  # # list users' names
93
- # users = swivel.call '/rest/users'
93
+ # users = swivel.users
94
94
  # users.collect do |user|
95
95
  # user.name
96
96
  # end
97
97
  #
98
98
  # # show a graph's name
99
- # graph = swivel.call '/rest/graphs/5119232'
99
+ # graph = swivel.graph 5119232
100
100
  # graph.name # => "Vinyl to Ipods"
101
101
  #
102
102
  # # list graphs' names
103
- # graphs = swivel.call '/rest/graphs'
103
+ # graphs = swivel.graphs
104
104
  # graphs.collect do |graph|
105
105
  # graph.name
106
106
  # end
107
107
  #
108
108
  # Upload data
109
109
  #
110
+ # See Swivel::Connection#upload for a detailed list of options
111
+ #
110
112
  # # upload a new data_set
111
113
  # data_set = swivel.upload {...}
112
114
  #
113
115
  # # append to an existing data_set
114
- # data_set = swivel.append {...}.merge(:id => orig_data_set_id, :mode => 'append')
116
+ # data_set = swivel.append {...}.merge(:id => orig_data_set_id)
115
117
  #
116
118
  # # replace data for an existing data_set
117
- # data_set = swivel.append {...}.merge(:id => orig_data_set_id, :mode => 'replace')
118
- #
119
- # TODO: SwQL
119
+ # data_set = swivel.replace {...}.merge(:id => orig_data_set_id)
120
120
  #
121
121
  # TODO: constructing URLs for csvs, html pages, etc
122
122
  #
123
- # TODO: object cache {in memory, on filesystem}
124
- #
125
123
 
126
124
  module Swivel
127
125
 
@@ -263,14 +261,50 @@ module Swivel
263
261
  end
264
262
  end
265
263
 
266
- %w/DataColumn DataSet User/.each do |class_name|
264
+ %w/DataColumn User/.each do |class_name|
267
265
  class_eval <<-LAZY
268
266
  class #{class_name} < Response; end
269
267
  LAZY
270
268
  end
271
269
 
270
+ class DataSet < Response
271
+ # Append new data to this data_set. Passed in options must contain
272
+ # csv data. This csv data must adhrere to the same column structure
273
+ # as the originally uploaded data_set.
274
+ #
275
+ # data_set = swivel.data_set id
276
+ # data_set.append! :data => `/tmp/append.csv`
277
+
278
+ def append! options = Hash.new
279
+ options.merge! :mode => 'append', :id => self.id
280
+ raise 'feed me data!' if options[:data].blank?
281
+ appended_data_set = @connection.upload options
282
+ @refreshed_at = nil
283
+ @retried = false
284
+ @doc = appended_data_set.instance_variable_get :@doc
285
+ self
286
+ end
287
+
288
+ # Replace data in this data_set. Passed in options must contain
289
+ # csv data. This csv data must adhere to the same column structure
290
+ # as the originally uploaded data set.
291
+ #
292
+ # data_set = swivel.data_set id
293
+ # data_set.replace! :data => `/tmp/replace.csv`
294
+
295
+ def replace! options = Hash.new
296
+ options.merge! :mode => 'replace', :id => self.id
297
+ raise 'feed me data!' if options[:data].blank?
298
+ replaced_data_set = @connection.upload options
299
+ @refreshed_at = nil
300
+ @retried = false
301
+ @doc = replaced_data_set.instance_variable_get :@doc
302
+ self
303
+ end
304
+ end
305
+
272
306
  class Graph < Response
273
- def image_url options = {}
307
+ def image_url options = Hash.new
274
308
  host = @connection.config[:image_host]
275
309
  if variant == 'transient'
276
310
  "http://#{host}/graphs/get/#{digest}"
@@ -278,6 +312,47 @@ module Swivel
278
312
  "http://#{host}/graphs/image/#{id}"
279
313
  end
280
314
  end
315
+
316
+ # Swivel::Connection#create_graph returns a transient graph that
317
+ # must be saved in order for it to persist at Swivel. Only persistent
318
+ # graphs have page urls at Swivel.
319
+ #
320
+ # call save on a transient graph in order to do this.
321
+ #
322
+ # # create a transient graph
323
+ # transient_graph = swivel.create_graph {...}
324
+ # transient_graph.variant # => 'transient'
325
+ # transient_graph.id.blank? # => true
326
+ #
327
+ # # persist the transient graph
328
+ # persisted_graph = transient_graph.save
329
+ # persisted_graph.variant # => 'default'
330
+ # persisted_graph.id.blank? # false
331
+ #
332
+ # # now, you can grab it from swivel whenever
333
+ # g = swivel.graph persisted_graph.id
334
+ # g.name == persisted_graph.name # => true
335
+
336
+ def save!
337
+ return self unless variant == 'transient'
338
+ options = {:save => 'true', :digest => self.digest}
339
+ # PUT /rest/graphs/0 denotes updating a transient graph. Swivel
340
+ # will read the digest and save the transient graph that matches
341
+ # that digest.
342
+ saved_graph = @connection.call '/rest/graphs/0', options, :put
343
+ @doc = saved_graph.instance_variable_get :@doc
344
+ @refreshed_at = nil
345
+ @retried = false
346
+ self
347
+ end
348
+
349
+ def add_column! data_column
350
+ # TODO
351
+ end
352
+
353
+ def remove_column! data_column
354
+ # TODO
355
+ end
281
356
  end
282
357
 
283
358
  # Encapsulates lists of resources. Typically, items contained within the list are
@@ -412,6 +487,24 @@ module Swivel
412
487
  # # returns a string (because an appropriate Swivel::Response subclass doesn't exist)
413
488
  # echo = swivel.call '/rest/test/echo/howdy'
414
489
  # echo.class # => String
490
+ #
491
+ # However, calling all of those endpoints is tedious. For swivel's main objects
492
+ # there are some helper methods to get stuff:
493
+ #
494
+ # data_set = swivel.data_set 1
495
+ # data_set.class # => Swivel::DataSet
496
+ # data_set.id # => 1
497
+ #
498
+ # data_sets = swivel.data_sets
499
+ #
500
+ # some_graph = swivel.graph 1232132
501
+ #
502
+ # pwn3r = swivel.user 'visnu'
503
+ # pwn3r.name # => 'visnu'
504
+ #
505
+ # All these convenience methoods (yeah, methoods) really do is prettify a get call
506
+ # to:
507
+ # /rest/object_type or /rest/object_type/id
415
508
 
416
509
  def call path, params = Hash.new, method = :get
417
510
  xml =
@@ -428,26 +521,9 @@ module Swivel
428
521
  doc = REXML::Document.new xml
429
522
  Response.class_for(doc.root.elements[1].name).new xml, self
430
523
  rescue Exception => e
431
- xml || nil
524
+ xml.blank? ? nil : xml
432
525
  end
433
526
 
434
- # Calling all of those endpoints is tedious. For swivel's main objects
435
- # there are some helper methods to get stuff:
436
- #
437
- # data_set = swivel.data_set 1
438
- # data_set.class # => Swivel::DataSet
439
- # data_set.id # => 1
440
- #
441
- # data_sets = swivel.data_sets
442
- #
443
- # some_graph = swivel.graph 1232132
444
- #
445
- # da_bomb = swivel.user 'visnu'
446
- # da_bomb.name # => 'visnu'
447
- #
448
- # All these really do is prettify a get call to:
449
- # /rest/object_type or /rest/object_type/id
450
-
451
527
  %w/ data_columns data_sets graphs users /.each do |obj|
452
528
  class_eval <<-LAZY
453
529
  def #{obj} options = nil
@@ -468,49 +544,168 @@ module Swivel
468
544
  LAZY
469
545
  end
470
546
 
471
- # Performs an upload, append, or replace. Set options[:mode] to one of "initial",
472
- # "append", or "replace". If unset, options[:mode] defaults to "initial". Append
473
- # and replace can also be called directly, without setting options[:mode].
547
+ DEFAULT_UPLOAD_OPTIONS = {
548
+ :citation => 'swivel.rb',
549
+ :citation_url => 'swivel.com/developer',
550
+ :image_url => 'http://swivel.com/images/logo.png',
551
+ :image_source_url => 'http://swivel.com',
552
+ :display_tags => 'swivel api swivel.rb'
553
+ }
554
+
555
+ # Performs an upload, append, or replace to a data_set in Swivel.
556
+ # # upload a file to swivel
557
+ # data_set = swivel.upload {...}
474
558
  #
475
- # In order to append or replace a data_set, you must be the owner of the data.
476
- # The new data must conform to the same column structure as the original data.
559
+ # In order to upload, append, or replace, you must have a valid api key.
560
+ # {Get a Swivel API key}[http://swivel.com/api/key].
477
561
  #
478
- # In order to upload (including replace and append), you must have a valid api key.
562
+ # # append to a data_set already in Swivel
563
+ # more_data = `cat /tmp/more.csv`
564
+ # data_set = swivel.append {:data => more_data}.merge(:id => data_set_id)
479
565
  #
480
- # TODO: outline required and optional parameters. give a few examples.
566
+ # # replace underlying data in a data_set that already exists on Swivel
567
+ # new_data = `cat /tmp/new.csv`
568
+ # data_set = swivel.replace {:data => new_data}.merge(:id => data_set_id)
481
569
  #
482
- # TODO: elaborate on requirements/assumptions for replace and append modes.
570
+ # In order to append or replace a data_set, you must be the owner of the data.
571
+ # The new data must conform to the same column structure as the original data.
483
572
  #
484
- # TODO: limitations and crap?
573
+ # required parameters
574
+ # * options['id'] - data_set id that you want to append/replace. (append, replace only)
575
+ # * options['name'] - name for the data_set. (upload only)
576
+ # * options['citation'] - citation for the data_set. e.g., U.N. (upload only)
577
+ # * options['data'] - a string of actual csv data. when appending or replacing data, a header row is optional. (upload, append, and replace)
578
+ # * options['display_tags'] - tags for the data_set (upload only)
485
579
  #
486
- # # upload a file to swivel
487
- # data_set = swivel.upload {...}
580
+ # optional parameters (upload only; not for append or replace)
581
+ # * options['description'] - description for the data_set
582
+ # * options['citation_url'] - citation url for the data set. e.g., http://un.org
583
+ # * options['auto_estimate'] - 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['column_names'], options['column_types'], and options['column_separator'].
584
+ # * options['column_names'] - a comma-separated string of column names. only send this option when auto_estimate is missing or false.
585
+ # * options['column_types'] - 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.
586
+ # * options['column_separator'] - a character. usually a ','. only send this option when auto_estimate is false.
587
+ # * options['image_url'] - a url to an image you'd like to associate with your data_set.
588
+ # * options['image_source_url'] - a url to the source for your image, for proper attribution.
488
589
  #
489
- # # append to a data_set already in Swivel
490
- # data_set = swivel.append {...}
590
+ # {More examples online}[http://swivel.com/api/code]
491
591
  #
492
- # # replace underlying data in a data_set that already exists on Swivel
493
- # data_set = swivel.replace {...}
592
+ # A few limitations since swivel.rb is yet nascent and in its formative
593
+ # days.
594
+ # * You must post data as a string of text.
595
+ # * Actually, that's it, I think.
494
596
 
495
597
  def upload options = Hash.new
496
- options[:mode] ||= 'initial'
598
+ opts = options.clone
599
+ opts[:mode] ||= 'initial'
600
+
497
601
  uri, method =
498
- case options[:mode]
602
+ case opts[:mode]
499
603
  when 'append', 'replace'
500
- ["/rest/data_sets/#{options[:id]}", :put]
604
+ opts[:auto_estimate] = true
605
+ ["/rest/data_sets/#{opts[:id]}", :put]
501
606
  else
607
+ force_auto_estimate =
608
+ !opts.has_key?(:auto_estimate) && !opts.has_key?(:column_type)
609
+ opts[:auto_estimate] = true if force_auto_estimate
610
+ opts.reverse_merge! DEFAULT_UPLOAD_OPTIONS
502
611
  ['/rest/data_sets', :post]
503
612
  end
504
- call uri, options, method
613
+ call uri, opts, method
505
614
  end
615
+ alias_method :create_data_set, :upload
506
616
 
507
617
  %w/append replace/.each do |mode|
508
618
  class_eval <<-LAZY
509
619
  def #{mode} options = Hash.new
510
- options[:mode] = "#{mode}"
511
- upload options
620
+ opts = options.clone
621
+ opts[:mode] = "#{mode}"
622
+ upload opts
512
623
  end
513
624
  LAZY
514
625
  end
626
+
627
+ # Lets you create a transient graph in Swivel.
628
+ #
629
+ # If you pass in options[:save] => true, your transient graph will be
630
+ # immediately saved.
631
+ #
632
+ # # build a transient graph, then save it.
633
+ # transient_graph = swivel.create_graph {...}
634
+ # graph = transient_graph.save
635
+ #
636
+ # # a faster way of doing the same thing.
637
+ # graph = swivel.create_graph {...}.merge(:save => 'true')
638
+ #
639
+ # Transient graphs exist in Swivel so you can cheaply play around with
640
+ # different visualizations, without saving them to Swivel's database.
641
+ # The idea is to let you quickly create visualizations and discard the
642
+ # duds and keep the gems. So, create graphs with impunity! And just
643
+ # save the ones you like.
644
+ #
645
+ # From an object perspective, the big differences between transient
646
+ # and persistent graphs are:
647
+ #
648
+ # transient_graph = swivel.create_graph {...}
649
+ #
650
+ # # transient graphs do not have ids. thus, they are not accessible
651
+ # # at their own page at swivel.com.
652
+ # transient_graph.id # => nil
653
+ #
654
+ # # transient graphs have a 'transient' variant
655
+ # transient_graph.variant # => 'transient'
656
+ #
657
+ # # transient graphs have digests whereas persisted graphs do not.
658
+ # # digests act as a unique identifier for transient graphs. e.g.,
659
+ # # you'd use the digest to build urls to the transient graph's image.
660
+ # transient_graph.digest.blank? # => false
661
+ # transient_graph.image_url.blank? # => false
662
+ #
663
+ # Please don't save transient graphs you don't want to end up keeping
664
+ # around. It's the most environmentally friendly thing to do.
665
+ #
666
+ # required parameters
667
+ # * options[:graph_type]
668
+ # * LineGraph, BarGraph, HorizontalBarGraph, PieGraph, or ScatterGraph
669
+ #--
670
+ # TODO: that sucks. i want: line, bar, hbar, pie, scatter, spark
671
+ #++
672
+ # * options[:columns]
673
+ # a comma separated list of data_column ids
674
+ # * options[:x_axis_column]
675
+ # the x-axis data_column id
676
+ #
677
+ # optional parameters
678
+ # * options[:join_on_columns]
679
+ # a comma separated list of join data_column ids
680
+ # * options[:group_by_columns]
681
+ # a comma separated list of group by data_column ids
682
+ # * options[:aggregation_function]
683
+ # aggregation functions for group_by_columns.
684
+ # one of: AvgFunction, ConcatFunction, MaxFunction, MinFunction,
685
+ # SumFunction, CountFunction, DistinctCountFunction
686
+ # * options[:order_by_column]
687
+ # a comma separated list of order-by data_column ids
688
+ # * options[:order_by_direction]
689
+ # which sort to apply to the order_by_column.
690
+ # one of: ASC, DESC, ALPHABETICAL, UNSORTED
691
+ # * options[:time_scale]
692
+ # time-scale of your X-axis, if applicable. one of: YEARLY,
693
+ # QUARTERLY, MONTHLY, WEEKLY, DAILY, HOURLY, BY_MINUTE, BY_SECOND
694
+ # * options[:time_range]
695
+ # time-range of your X-axis, if applicable. Swivel understands many
696
+ # human readable date/time formats. Examples: "3 years ago to now",
697
+ # "1 month", "1992-2004".
698
+ # * options[:scale]
699
+ # y-scale of the graph you're creating, if applicable. one
700
+ # of: ABSOLUTE, AVERAGE, RANGE, RELATIVE, PERCENT
701
+
702
+ def create_graph options = Hash.new, persist = false
703
+ options.merge! :save => 'true' if persist
704
+ unless options.has_key? :order_by_column
705
+ options.merge! :order_by_column => options[:columns].split(',').first,
706
+ :order_by_direction => 'DESC'
707
+ end
708
+ call '/rest/graphs', options, :post
709
+ end
515
710
  end
516
711
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: swivel
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.8
7
- date: 2007-06-28 00:00:00 -07:00
6
+ version: 0.0.15
7
+ date: 2007-07-01 00:00:00 -07:00
8
8
  summary: Ruby interface to the Swivel API.
9
9
  require_paths:
10
10
  - lib