sugarcrm 0.7.9 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.rdoc CHANGED
@@ -12,14 +12,15 @@ RubyGem for interacting with SugarCRM via REST.
12
12
 
13
13
  A less clunky way to interact with SugarCRM via REST.
14
14
 
15
- I've built an abstraction layer on top of the SugarCRM REST API, instead of +get_entry("Users", "1")+ you can
16
- call +SugarCRM::User.find(1)+. There is also support for collections à la +SugarCRM::User.find(1).accounts+.
15
+ I've built an abstraction layer on top of the SugarCRM REST API, instead of get_entry("Users", "1") you can
16
+ call SugarCRM::User.find(1). There is also support for collections à la SugarCRM::User.find(1).email_addresses.
17
17
  ActiveRecord style finders are in place, with limited support for conditions and joins
18
- e.g. +SugarCRM::Contacts.find_by_title("VP of Sales")+ will work, but +SugarCRM::Contacts.find_by_title("VP of Sales", {:conditions => {:deleted => 0}})+ will not.
18
+ e.g. SugarCRM::Contacts.find_by_title("VP of Sales") will work, but SugarCRM::Contacts.find_by_title("VP of Sales", {:conditions => {:deleted => 0}}) will not.
19
19
 
20
20
  == FEATURES/PROBLEMS:
21
21
 
22
22
  * Supports all v2 API calls
23
+ * Supports saving of Module specific objects.
23
24
  * Auto-generation of Module specific objects. When a connection is established, get_available_modules is called and the resultant modules are turned into SugarCRM::Module classes.
24
25
  * If you just want to use the vanilla API, you can access the methods directly on the SugarCRM.connection object.
25
26
 
@@ -28,7 +29,10 @@ e.g. +SugarCRM::Contacts.find_by_title("VP of Sales")+ will work, but +SugarCRM:
28
29
  require 'sugarcrm'
29
30
 
30
31
  # Establish a connection
31
- SugarCRM::Base.establish_connection("http://localhost/sugarcrm", 'user', 'password', {:debug => false})
32
+ SugarCRM.connect("http://localhost/sugarcrm", 'user', 'password')
33
+
34
+ # Enable debugging on the current connection
35
+ SugarCRM.connection.debug = true
32
36
 
33
37
  # Get the logged in user
34
38
  SugarCRM.current_user
@@ -38,9 +42,24 @@ e.g. +SugarCRM::Contacts.find_by_title("VP of Sales")+ will work, but +SugarCRM:
38
42
 
39
43
  # Retrieve a User by user_name
40
44
  SugarCRM::User.find_by_user_name("admin")
45
+
46
+ # Update a User's title
47
+ u = SugarCRM::User.find_by_first_name_and_last_name("Will", "Westin")
48
+ u.title = "Sales Manager Central"
49
+ u.save
50
+
51
+ # Check if an object is valid (i.e. if it has the required fields to save)
52
+ u.valid?
53
+
54
+ # Access the errors collection
55
+ u.errors
56
+
57
+ # Delete an Account
58
+ a = SugarCRM::Account.find_by_name("JAB Funds Ltd.")
59
+ a.delete
41
60
 
42
- # Retrieve all Contacts assigned to a particular user.
43
- SugarCRM::User.find_by_user_name('sarah').contacts
61
+ # Retrieve all Email Addresses assigned to a particular user.
62
+ SugarCRM::User.find_by_user_name('sarah').email_addresses
44
63
 
45
64
  # Retrieve all email addresses on an Account
46
65
  SugarCRM::Account.find_by_name("JAB Funds Ltd.").contacts.each do |contact|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.9
1
+ 0.8.0
data/lib/sugarcrm.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'pp'
2
+ require 'set'
2
3
  require 'rubygems'
3
4
  require 'active_support/core_ext'
4
5
 
@@ -1,8 +1,10 @@
1
1
  module SugarCRM; module AssociationMethods
2
2
 
3
- # Returns an array of the module link fields
4
- def associations_from_module_link_fields
5
- self.class._module.link_fields.keys
3
+ module ClassMethods
4
+ # Returns an array of the module link fields
5
+ def associations_from_module_link_fields
6
+ self._module.link_fields.keys
7
+ end
6
8
  end
7
9
 
8
10
  # Generates the association proxy methods for related modules
@@ -30,8 +32,7 @@ module SugarCRM; module AssociationMethods
30
32
  # "type"=>"link"},
31
33
  #
32
34
  def query_association(association)
33
- klass = self.class._module.link_fields[association.to_s]["module"]
34
- objects = SugarCRM.connection.get_relationships(
35
+ collection = SugarCRM.connection.get_relationships(
35
36
  self.class._module.name,
36
37
  self.id,
37
38
  association.to_s
@@ -3,13 +3,95 @@ module SugarCRM; module AttributeMethods
3
3
  module ClassMethods
4
4
  # Returns a hash of the module fields from the module
5
5
  def attributes_from_module_fields
6
- fields = {}
6
+ fields = {}.with_indifferent_access
7
7
  self._module.fields.keys.sort.each do |k|
8
8
  fields[k.to_s] = nil
9
9
  end
10
10
  fields
11
11
  end
12
12
  end
13
+
14
+ # Determines if attributes have been changed
15
+ def changed?
16
+ @modified_attributes.length > 0
17
+ end
18
+
19
+ # Is this a new record?
20
+ def new?
21
+ @id.blank?
22
+ end
23
+
24
+ # Converts the attributes hash into format recognizable by Sugar
25
+ # { :last_name => "Smith"}
26
+ # becomes
27
+ # { :last_name => {:name => "last_name", :value => "Smith"}}
28
+ def serialize_attributes
29
+ attr_hash = {}
30
+ @attributes.each_pair do |name,value|
31
+ attr_hash[name] = serialize_attribute(name,value)
32
+ end
33
+ attr_hash[:id] = serialize_id unless new?
34
+ attr_hash
35
+ end
36
+
37
+ # Converts the modified_attributes hash into format recognizable by Sugar
38
+ # { :last_name => {:old => "Smit", :new => "Smith"}}
39
+ # becomes
40
+ # { :last_name => {:name => "last_name", :value => "Smith"}}
41
+ def serialize_modified_attributes
42
+ attr_hash = {}
43
+ @modified_attributes.each_pair do |name,hash|
44
+ attr_hash[name] = serialize_attribute(name,hash[:new])
45
+ end
46
+ attr_hash[:id] = serialize_id unless new?
47
+ attr_hash
48
+ end
49
+
50
+ # Checks to see if we have all the neccessary attributes
51
+ def valid?
52
+ valid = true
53
+ self.class._module.required_fields.each do |attribute|
54
+ case attr_type_for(attribute)
55
+ when "bool"
56
+ case @attributes[attribute]
57
+ when TrueClass:
58
+ next
59
+ when FalseClass:
60
+ next
61
+ else
62
+ @errors.add "#{attribute} must be true or false"
63
+ valid = false
64
+ end
65
+ else
66
+ if @attributes[attribute].blank?
67
+ @errors.add "#{attribute} cannot be blank"
68
+ valid = false
69
+ end
70
+ end
71
+ end
72
+ valid
73
+ end
74
+
75
+ # List the required attributes for save
76
+ def required_attributes
77
+ self.class._module.required_fields
78
+ end
79
+
80
+ # Serializes the id
81
+ def serialize_id
82
+ {:name => "id", :value => @id.to_s}
83
+ end
84
+
85
+ # Un-typecasts the attribute - false becomes 0
86
+ def serialize_attribute(name,value)
87
+ attr_value = value
88
+ case attr_type_for(name)
89
+ when "bool"
90
+ attr_value = 0
91
+ attr_value = 1 if value
92
+ end
93
+ {:name => name, :value => attr_value}
94
+ end
13
95
 
14
96
  # Generates get/set methods for keys in the attributes hash
15
97
  def define_attribute_methods
@@ -52,6 +134,26 @@ module SugarCRM; module AttributeMethods
52
134
  end
53
135
 
54
136
  protected
137
+
138
+ # Returns the attribute type for a given attribute
139
+ def attr_type_for(attribute)
140
+ field = self.class._module.fields[attribute]
141
+ return false unless field
142
+ field["type"]
143
+ end
144
+
145
+ # Attempts to typecast each attribute based on the module field type
146
+ def typecast_attributes
147
+ @attributes.each_pair do |name,value|
148
+ attr_type = attr_type_for(name)
149
+ next unless attr_type
150
+ case attr_type
151
+ when "bool"
152
+ @attributes[name] = (value == "1")
153
+ end
154
+ end
155
+ @attributes
156
+ end
55
157
 
56
158
  # Wrapper around attributes hash
57
159
  def read_attribute(key)
@@ -60,6 +162,7 @@ module SugarCRM; module AttributeMethods
60
162
 
61
163
  # Wrapper around attributes hash
62
164
  def write_attribute(key, value)
165
+ @modified_attributes[key] = { :old => @attributes[key].to_s, :new => value }
63
166
  @attributes[key] = value
64
167
  end
65
168
 
data/lib/sugarcrm/base.rb CHANGED
@@ -4,7 +4,7 @@ require 'sugarcrm/association_methods'
4
4
  module SugarCRM; class Base
5
5
 
6
6
  # Unset all of the instance methods we don't need.
7
- instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^define_method$|^class$|^instance_of.$)/ }
7
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^define_method$|^class$|^methods$|^instance_of.$|^respond_to.$)/ }
8
8
 
9
9
  # This holds our connection
10
10
  cattr_accessor :connection, :instance_writer => false
@@ -21,9 +21,11 @@ module SugarCRM; class Base
21
21
 
22
22
  # Contains a list of attributes
23
23
  attr :attributes, true
24
+ attr :modified_attributes, true
24
25
  attr :associations, true
25
26
  attr :id, true
26
27
  attr :debug, true
28
+ attr :errors, true
27
29
 
28
30
  class << self # Class methods
29
31
  def establish_connection(url, user, pass, opts={})
@@ -74,7 +76,7 @@ module SugarCRM; class Base
74
76
 
75
77
  case ids.size
76
78
  when 0
77
- raise RecordNotFound, "Couldn't find #{name} without an ID"
79
+ raise RecordNotFound, "Couldn't find #{self._module.name} without an ID"
78
80
  when 1
79
81
  result = find_one(ids.first, options)
80
82
  expects_array ? [ result ] : result
@@ -124,6 +126,8 @@ module SugarCRM; class Base
124
126
  end
125
127
 
126
128
  def query_from_options(options)
129
+ # If we dont have conditions, just return an empty query
130
+ return "" unless options[:conditions]
127
131
  conditions = []
128
132
  options[:conditions].each_pair do |column, value|
129
133
  conditions << "#{self._module.table_name}.#{column} = \'#{value}\'"
@@ -282,9 +286,13 @@ module SugarCRM; class Base
282
286
  def initialize(id=nil, attributes={})
283
287
  @id = id
284
288
  @attributes = self.class.attributes_from_module_fields.merge(attributes)
285
- @associations = associations_from_module_link_fields
289
+ @modified_attributes = {}
290
+ @associations = self.class.associations_from_module_link_fields
291
+ @errors = Set.new
286
292
  define_attribute_methods
287
293
  define_association_methods
294
+ typecast_attributes
295
+ self
288
296
  end
289
297
 
290
298
  def inspect
@@ -293,16 +301,38 @@ module SugarCRM; class Base
293
301
 
294
302
  def to_s
295
303
  attrs = []
296
- @attributes.each_key do |k|
297
- attrs << "#{k}: #{attribute_for_inspect(k)}"
304
+ @attributes.keys.sort.each do |k|
305
+ attrs << "#{k}: #{attribute_for_inspect(k)}"
298
306
  end
299
307
  "#<#{self.class} #{attrs.join(", ")}>"
300
308
  end
301
309
 
310
+ # Saves the current object, checks that required fields are present.
311
+ # returns true or false
302
312
  def save
303
- response = SugarCRM.connection.set_entry(self._module.name, @attributes)
313
+ return false unless changed?
314
+ return false unless valid?
315
+ # If we get a Hash back, return true. Otherwise return false.
316
+ (SugarCRM.connection.set_entry(self.class._module.name, serialize_modified_attributes).class == Hash)
317
+ end
318
+
319
+ # Saves the current object, checks that required fields are present.
320
+ # raises an exception if a save fails
321
+ def save!
322
+ raise InvalidRecord, errors.to_a.join(", ") unless valid?
323
+ # If we get a Hash back, return true. Otherwise return false.
324
+ (SugarCRM.connection.set_entry(self.class._module.name, serialize_modified_attributes).class == Hash)
304
325
  end
305
326
 
327
+ def delete
328
+ return false if @id.blank?
329
+ params = {}
330
+ params[:id] = serialize_id
331
+ params[:deleted]= {:name => "deleted", :value => "1"}
332
+ (SugarCRM.connection.set_entry(self.class._module.name, params).class == Hash)
333
+ end
334
+
335
+
306
336
  # Wrapper around class attribute
307
337
  def attribute_methods_generated?
308
338
  self.class.attribute_methods_generated
@@ -316,6 +346,7 @@ module SugarCRM; class Base
316
346
  include AttributeMethods
317
347
  extend AttributeMethods::ClassMethods
318
348
  include AssociationMethods
349
+ extend AssociationMethods::ClassMethods
319
350
  end
320
351
 
321
352
  end; end
@@ -87,6 +87,10 @@ module SugarCRM; class Connection
87
87
  options[:debug] = debug
88
88
  end
89
89
 
90
+ def debug?
91
+ options[:debug]
92
+ end
93
+
90
94
  private
91
95
 
92
96
  def handle_response
@@ -3,7 +3,8 @@ module SugarCRM; class Connection
3
3
  # work with the report module.
4
4
  def get_entries(module_name, ids, opts={})
5
5
  login! unless logged_in?
6
- options = { :fields => [],
6
+ options = {
7
+ :fields => [],
7
8
  :link_fields => [],
8
9
  }.merge! opts
9
10
 
@@ -2,7 +2,9 @@ module SugarCRM; class Connection
2
2
  # Retrieves the specified number of records in a module.
3
3
  def get_entries_count(module_name, query, opts={})
4
4
  login! unless logged_in?
5
- options = {:deleted => 0}.merge! opts
5
+ options = {
6
+ :deleted => 0
7
+ }.merge! opts
6
8
 
7
9
  json = <<-EOF
8
10
  {
@@ -2,7 +2,8 @@ module SugarCRM; class Connection
2
2
  # Retrieves a single SugarBean based on the ID.
3
3
  def get_entry(module_name, id, opts={})
4
4
  login! unless logged_in?
5
- options = { :fields => [],
5
+ options = {
6
+ :fields => [],
6
7
  :link_fields => [],
7
8
  }.merge! opts
8
9
 
@@ -8,8 +8,8 @@ def get_entry_list(module_name, query, opts={})
8
8
  :offset => '',
9
9
  :fields => [],
10
10
  :link_fields => [],
11
- :max_results => '',
12
- :deleted => ''
11
+ :limit => '',
12
+ :deleted => 0
13
13
  }.merge! opts
14
14
 
15
15
  json = <<-EOF
@@ -21,7 +21,7 @@ def get_entry_list(module_name, query, opts={})
21
21
  \"offset\": \"#{options[:offset]}\"\,
22
22
  \"select_fields\": #{resolve_fields(module_name, options[:fields])}\,
23
23
  \"link_name_to_fields_array\": #{options[:link_fields].to_json}\,
24
- \"max_results\": \"#{options[:max_results]}\"\,
24
+ \"max_results\": \"#{options[:limit]}\"\,
25
25
  \"deleted\": #{options[:deleted]}
26
26
  }
27
27
  EOF
@@ -2,12 +2,13 @@ module SugarCRM; class Connection
2
2
  # Returns the ID, module name and fields for specified modules.
3
3
  # Supported modules are Accounts, Bugs, Calls, Cases, Contacts,
4
4
  # Leads, Opportunities, Projects, Project Tasks, and Quotes.
5
- def search_by_module(search_string, modules, options={})
5
+ def search_by_module(search_string, modules, ops={})
6
6
  login! unless logged_in?
7
7
 
8
- { :offset => nil,
9
- :max_results => nil,
10
- }.merge! options
8
+ options = {
9
+ :offset => nil,
10
+ :limit => nil,
11
+ }.merge! opts
11
12
 
12
13
  json = <<-EOF
13
14
  {
@@ -15,7 +16,7 @@ module SugarCRM; class Connection
15
16
  \"search_string\": \"#{search_string}\"\,
16
17
  \"modules\": \"#{modules}\"\,
17
18
  \"offset\": #{options[:offset]}\,
18
- \"max_results\": #{options[:max_results]}
19
+ \"max_results\": #{options[:limit]}
19
20
  }
20
21
  EOF
21
22
  json.gsub!(/^\s{6}/,'')
@@ -3,7 +3,12 @@ module SugarCRM; class Connection
3
3
  # FIXME: This is to work around a bug in SugarCRM 6.0
4
4
  # where no fields are returned if no fields are specified
5
5
  if fields.length == 0
6
- fields = Module.find(module_name).fields.keys
6
+ mod = Module.find(module_name)
7
+ if mod
8
+ fields = mod.fields.keys
9
+ else
10
+ fields = "id"
11
+ end
7
12
  end
8
13
  return fields.to_json
9
14
  end
@@ -22,4 +22,7 @@ module SugarCRM
22
22
 
23
23
  class RecordNotFound < RuntimeError
24
24
  end
25
+
26
+ class InvalidRecord < RuntimeError
27
+ end
25
28
  end
@@ -33,6 +33,16 @@ module SugarCRM
33
33
  @fields_registered
34
34
  end
35
35
 
36
+ def required_fields
37
+ required_fields = []
38
+ ignore_fields = ["id", "date_entered", "date_modified"]
39
+ self.fields.each_value do |field|
40
+ next if ignore_fields.include? field["name"]
41
+ required_fields << field["name"] if field["required"] == 1
42
+ end
43
+ required_fields
44
+ end
45
+
36
46
  def link_fields
37
47
  self.fields unless link_fields?
38
48
  handle_empty_array
@@ -7,6 +7,12 @@ module SugarCRM
7
7
  def self.connection=(connection)
8
8
  @@connection = connection
9
9
  end
10
+ def self.connect(url, user, pass, options={})
11
+ SugarCRM::Base.establish_connection(url, user, pass, options)
12
+ end
13
+ class << self
14
+ alias :connect! :connect
15
+ end
10
16
 
11
17
  @@modules = []
12
18
  def self.modules
@@ -8,7 +8,12 @@ module SugarCRM; class Response
8
8
  r = new(json)
9
9
  begin
10
10
  return r.to_obj
11
- rescue
11
+ rescue => e
12
+ if SugarCRM.connection.debug?
13
+ puts "Failed to process JSON:"
14
+ pp json
15
+ puts e
16
+ end
12
17
  return json
13
18
  end
14
19
  end
@@ -23,18 +28,17 @@ module SugarCRM; class Response
23
28
  # Tries to instantiate and return an object with the values
24
29
  # populated from the response
25
30
  def to_obj
31
+ # If this is not a "entry_list" response, just return
32
+ return @response unless @response["entry_list"]
33
+
26
34
  objects = []
27
35
  @response["entry_list"].each do |object|
28
36
  attributes = []
29
37
  _module = resolve_module(object)
30
38
  id = object["id"]
31
- begin
32
- attributes = flatten_name_value_list(object)
33
- rescue ArgumentError => e
34
- end
39
+ attributes = flatten_name_value_list(object)
35
40
  if SugarCRM.const_get(_module)
36
41
  if attributes.length == 0
37
- pp object
38
42
  raise AttributeParsingError, "response contains objects without attributes!"
39
43
  end
40
44
  objects << SugarCRM.const_get(_module).new(id, attributes)
@@ -53,7 +57,7 @@ module SugarCRM; class Response
53
57
  def to_json
54
58
  @response.to_json
55
59
  end
56
-
60
+
57
61
  def resolve_module(list)
58
62
  list["module_name"].classify
59
63
  end
@@ -16,5 +16,8 @@ class TestGetEntry < Test::Unit::TestCase
16
16
  should "return an object when #get_entry" do
17
17
  assert_instance_of SugarCRM::User, @response
18
18
  end
19
+ should "typecast boolean fields properly" do
20
+ assert !(@response.deleted)
21
+ end
19
22
  end
20
23
  end
data/test/helper.rb CHANGED
@@ -8,7 +8,7 @@ require 'sugarcrm'
8
8
 
9
9
  class Test::Unit::TestCase
10
10
  # Replace these with your test instance
11
- URL = "http://valet/sugarcrm6"
11
+ URL = "http://valet/sugarcrm"
12
12
  USER = "admin"
13
13
  PASS = 'letmein'
14
14
 
data/test/test_module.rb CHANGED
@@ -10,5 +10,9 @@ class TestModule < Test::Unit::TestCase
10
10
  should "respond to #fields" do
11
11
  assert_respond_to SugarCRM.modules[0], :fields
12
12
  end
13
+
14
+ should "return required fields when #required_fields" do
15
+ assert SugarCRM::User._module.required_fields.include? "user_name"
16
+ end
13
17
  end
14
18
  end
@@ -3,6 +3,11 @@ require 'helper'
3
3
  class TestSugarCRM < Test::Unit::TestCase
4
4
  context "A SugarCRM::Base instance" do
5
5
 
6
+ should "establish a connection when SugarCRM#connect!" do
7
+ SugarCRM.connect!(URL, USER, PASS)
8
+ assert SugarCRM.connection.connected?
9
+ end
10
+
6
11
  should "establish a connection when Base#establish_connection" do
7
12
  SugarCRM::Base.establish_connection(URL, USER, PASS)
8
13
  assert SugarCRM.connection.connected?
@@ -12,6 +17,10 @@ class TestSugarCRM < Test::Unit::TestCase
12
17
  assert_equal "Users", SugarCRM::User._module.name
13
18
  end
14
19
 
20
+ should "responsd to self#methods" do
21
+ assert_instance_of Array, SugarCRM::User.new.methods
22
+ end
23
+
15
24
  should "respond to self.connection" do
16
25
  assert_respond_to SugarCRM::User, :connection
17
26
  assert_instance_of SugarCRM::Connection, SugarCRM::User.connection
@@ -26,7 +35,7 @@ class TestSugarCRM < Test::Unit::TestCase
26
35
  end
27
36
 
28
37
  should "respond to self.attributes_from_modules_fields" do
29
- assert_instance_of Hash, SugarCRM::User.attributes_from_module_fields
38
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, SugarCRM::User.attributes_from_module_fields
30
39
  end
31
40
 
32
41
  should "return an instance of itself when #new" do
@@ -38,10 +47,31 @@ class TestSugarCRM < Test::Unit::TestCase
38
47
  assert SugarCRM::User.attribute_methods_generated
39
48
  end
40
49
 
41
- should "respond to attributes derived from #_module.fields" do
50
+ should "not save a record that is missing required attributes" do
42
51
  u = SugarCRM::User.new
43
52
  u.last_name = "Test"
44
- assert_equal "Test", u.last_name
53
+ assert !u.save
54
+ assert_raise SugarCRM::InvalidRecord do
55
+ u.save!
56
+ end
57
+ end
58
+
59
+ should "create, modify, and delete a record" do
60
+ #SugarCRM.connection.debug = true
61
+ u = SugarCRM::User.new
62
+ u.email1 = "abc@abc.com"
63
+ u.first_name = "Test"
64
+ u.last_name = "User"
65
+ u.system_generated_password = false
66
+ u.user_name = "test_user"
67
+ u.status = "Active"
68
+ assert_equal "Test", u.modified_attributes[:first_name][:new]
69
+ assert u.save!
70
+ m = SugarCRM::User.find_by_first_name_and_last_name("Test", "User")
71
+ m.title = "Test User"
72
+ assert m.save!
73
+ assert m.delete
74
+ #SugarCRM.connection.debug = false
45
75
  end
46
76
 
47
77
  should "return an an instance of itself when sent #find(id)" do
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sugarcrm
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 63
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 7
9
- - 9
10
- version: 0.7.9
8
+ - 8
9
+ - 0
10
+ version: 0.8.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Carl Hicks
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-11-14 00:00:00 -08:00
18
+ date: 2010-12-04 00:00:00 -08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency