cashboard 1.0.1
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/.autotest +26 -0
- data/.gitignore +2 -0
- data/LICENSE +19 -0
- data/README.textile +58 -0
- data/Rakefile +25 -0
- data/cashboard.gemspec +129 -0
- data/examples/create_account.rb +38 -0
- data/examples/list_stuff_in_account.rb +15 -0
- data/examples/simple_workflow.rb +35 -0
- data/examples/time_tracking.rb +30 -0
- data/examples/toggle_timer.rb +0 -0
- data/lib/cashboard/account.rb +21 -0
- data/lib/cashboard/base.rb +223 -0
- data/lib/cashboard/behaviors/base.rb +4 -0
- data/lib/cashboard/behaviors/lists_line_items.rb +32 -0
- data/lib/cashboard/behaviors/toggleable.rb +18 -0
- data/lib/cashboard/client_company.rb +25 -0
- data/lib/cashboard/client_contact.rb +32 -0
- data/lib/cashboard/company_membership.rb +6 -0
- data/lib/cashboard/document_template.rb +10 -0
- data/lib/cashboard/employee.rb +29 -0
- data/lib/cashboard/errors.rb +48 -0
- data/lib/cashboard/estimate.rb +43 -0
- data/lib/cashboard/expense.rb +14 -0
- data/lib/cashboard/invoice.rb +75 -0
- data/lib/cashboard/invoice_line_item.rb +15 -0
- data/lib/cashboard/invoice_payment.rb +7 -0
- data/lib/cashboard/line_item.rb +27 -0
- data/lib/cashboard/payment.rb +22 -0
- data/lib/cashboard/project.rb +38 -0
- data/lib/cashboard/project_assignment.rb +9 -0
- data/lib/cashboard/time_entry.rb +45 -0
- data/lib/cashboard/version.rb +3 -0
- data/lib/cashboard.rb +75 -0
- data/lib/typecasted_open_struct.rb +82 -0
- data/test/fixtures/account.xml +50 -0
- data/test/fixtures/cashboard_credentials.yml +3 -0
- data/test/fixtures/client_companies.xml +41 -0
- data/test/fixtures/client_contacts.xml +53 -0
- data/test/fixtures/company_memberships.xml +21 -0
- data/test/fixtures/document_templates.xml +53 -0
- data/test/fixtures/employees.xml +51 -0
- data/test/fixtures/estimates.xml +243 -0
- data/test/fixtures/expenses.xml +101 -0
- data/test/fixtures/invoice_line_items.xml +138 -0
- data/test/fixtures/invoice_payments.xml +10 -0
- data/test/fixtures/invoices.xml +231 -0
- data/test/fixtures/line_items.xml +243 -0
- data/test/fixtures/payments.xml +93 -0
- data/test/fixtures/project_assignments.xml +30 -0
- data/test/fixtures/projects.xml +129 -0
- data/test/fixtures/time_entries.xml +213 -0
- data/test/full.rb +3 -0
- data/test/test_helper.rb +112 -0
- data/test/unit/account_test.rb +85 -0
- data/test/unit/document_template_test.rb +18 -0
- data/test/unit/estimate_test.rb +166 -0
- data/test/unit/expense_test.rb +16 -0
- data/test/unit/invoice_test.rb +185 -0
- data/test/unit/project_test.rb +198 -0
- data/test/unit/time_entry_test.rb +225 -0
- metadata +220 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
# Standard interface to toggle the status of something between Active
|
2
|
+
# and Closed inside Cashboard.
|
3
|
+
module Cashboard::Behaviors::Toggleable
|
4
|
+
# Toggles status of the project between active/closed
|
5
|
+
# and sets appropriate variable.
|
6
|
+
def toggle_status
|
7
|
+
options = self.class.merge_options()
|
8
|
+
options.merge!({:body => self.to_xml})
|
9
|
+
response = self.class.put(self.links[:toggle_status], options)
|
10
|
+
begin
|
11
|
+
self.class.check_status_code(response)
|
12
|
+
rescue
|
13
|
+
return false
|
14
|
+
end
|
15
|
+
self.is_active = !self.is_active
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class ClientCompany < Base
|
3
|
+
element :name
|
4
|
+
element :address
|
5
|
+
element :address2
|
6
|
+
element :city
|
7
|
+
element :state
|
8
|
+
element :zip
|
9
|
+
element :country_code
|
10
|
+
element :url
|
11
|
+
element :telephone
|
12
|
+
element :currency_type_code
|
13
|
+
element :notes
|
14
|
+
element :custom_1
|
15
|
+
element :custom_2
|
16
|
+
element :custom_3
|
17
|
+
|
18
|
+
# Returns all associated CompanyMemberships
|
19
|
+
def memberships(options={})
|
20
|
+
self.class.get_collection(
|
21
|
+
self.links[:memberships], Cashboard::CompanyMembership, options
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class ClientContact < Base
|
3
|
+
element :api_key
|
4
|
+
element :last_login, DateTime
|
5
|
+
element :login_count, Integer
|
6
|
+
element :address
|
7
|
+
element :address2
|
8
|
+
element :city
|
9
|
+
element :country_code
|
10
|
+
element :currency_type_code
|
11
|
+
element :custom_1
|
12
|
+
element :custom_2
|
13
|
+
element :custom_3
|
14
|
+
element :email_address
|
15
|
+
element :first_name
|
16
|
+
element :last_name
|
17
|
+
element :notes
|
18
|
+
element :password
|
19
|
+
element :state
|
20
|
+
element :telephone
|
21
|
+
element :url
|
22
|
+
element :zip
|
23
|
+
|
24
|
+
# Returns all associated CompanyMemberships
|
25
|
+
def memberships(options={})
|
26
|
+
self.class.get_collection(
|
27
|
+
self.links[:memberships], Cashboard::CompanyMembership, options
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class Employee < Base
|
3
|
+
STATUS_CODES = {
|
4
|
+
:employee => 0,
|
5
|
+
:administrator => 2
|
6
|
+
}
|
7
|
+
|
8
|
+
element :api_key, String
|
9
|
+
element :last_login, DateTime
|
10
|
+
element :login_count, Integer
|
11
|
+
element :address
|
12
|
+
element :address2
|
13
|
+
element :city
|
14
|
+
element :country_code
|
15
|
+
element :custom_1
|
16
|
+
element :custom_2
|
17
|
+
element :custom_3
|
18
|
+
element :email_address
|
19
|
+
element :employee_status_code, Integer
|
20
|
+
element :first_name
|
21
|
+
element :last_name
|
22
|
+
element :notes
|
23
|
+
element :password
|
24
|
+
element :state
|
25
|
+
element :telephone
|
26
|
+
element :url
|
27
|
+
element :zip
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class Unauthorized < StandardError; end
|
3
|
+
|
4
|
+
class HTTPError < StandardError
|
5
|
+
attr_reader :response
|
6
|
+
def initialize(response)
|
7
|
+
@response = response
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
|
13
|
+
#hint = response.response.body.nil? ? nil : response.response.body
|
14
|
+
#"#{self.class.to_s} : #{response.code}#{" - #{hint}" if hint}"
|
15
|
+
response.inspect
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Forbidden < HTTPError; end
|
20
|
+
class RateLimited < HTTPError; end
|
21
|
+
class NotFound < HTTPError; end
|
22
|
+
class PaymentRequired < HTTPError; end
|
23
|
+
class Unavailable < HTTPError; end
|
24
|
+
class ServerError < HTTPError; end
|
25
|
+
|
26
|
+
class BadRequest < HTTPError
|
27
|
+
# Custom parses our "errors" return XML
|
28
|
+
def to_s
|
29
|
+
response.response.body
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a hash of errors keyed on field name.
|
33
|
+
#
|
34
|
+
# Example
|
35
|
+
# {
|
36
|
+
# :field_name_one => "Error message",
|
37
|
+
# :field_name_two => "Error message"
|
38
|
+
# }
|
39
|
+
def errors
|
40
|
+
parsed_errors = XmlSimple.xml_in(response.response.body)
|
41
|
+
error_hash = {}
|
42
|
+
parsed_errors['error'].each do |e|
|
43
|
+
error_hash[e['field']] = e['content']
|
44
|
+
end
|
45
|
+
return error_hash
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class Estimate < Base
|
3
|
+
include Cashboard::Behaviors::Toggleable
|
4
|
+
include Cashboard::Behaviors::ListsLineItems
|
5
|
+
|
6
|
+
element :assigned_id
|
7
|
+
element :agreement_text
|
8
|
+
element :client_id
|
9
|
+
element :client_type
|
10
|
+
element :created_on, DateTime
|
11
|
+
element :deposit_amount, Float
|
12
|
+
element :discount_percentage, Float
|
13
|
+
element :document_template_id
|
14
|
+
element :has_been_sent, Boolean
|
15
|
+
element :intro_text
|
16
|
+
element :is_active, Boolean
|
17
|
+
element :name
|
18
|
+
element :requires_agreement, Boolean
|
19
|
+
element :sales_tax, Float
|
20
|
+
element :sales_tax_2, Float
|
21
|
+
element :sales_tax_2_cumulative, Boolean
|
22
|
+
# Read only attributes. Can't set these via API
|
23
|
+
element :discount_best, Float
|
24
|
+
element :discount_worst, Float
|
25
|
+
element :item_actual_best, Float
|
26
|
+
element :item_actual_worst, Float
|
27
|
+
element :item_cost_best, Float
|
28
|
+
element :item_cost_worst, Float
|
29
|
+
element :item_profit_best, Float
|
30
|
+
element :item_profit_worst, Float
|
31
|
+
element :item_taxable_best, Float
|
32
|
+
element :item_taxable_worst, Float
|
33
|
+
element :price_best, Float
|
34
|
+
element :price_worst, Float
|
35
|
+
element :tax_cost_best, Float
|
36
|
+
element :tax_cost_worst, Float
|
37
|
+
element :tax_cost_2_best, Float
|
38
|
+
element :tax_cost_2_worst, Float
|
39
|
+
element :time_best, Float
|
40
|
+
element :time_worst, Float
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class Expense < Base
|
3
|
+
element :amount, Float
|
4
|
+
element :category
|
5
|
+
element :created_on, DateTime
|
6
|
+
element :description
|
7
|
+
element :invoice_line_item_id # read only
|
8
|
+
element :is_billable, Boolean
|
9
|
+
element :payee_id
|
10
|
+
element :payee_type
|
11
|
+
element :person_id
|
12
|
+
element :project_id
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class Invoice < Base
|
3
|
+
element :assigned_id
|
4
|
+
element :balance, Float # readonly
|
5
|
+
element :client_id
|
6
|
+
element :client_type
|
7
|
+
element :created_on, DateTime
|
8
|
+
element :discount, Float # readonly
|
9
|
+
element :discount_percentage, Float
|
10
|
+
element :document_template_id
|
11
|
+
element :due_date, Date
|
12
|
+
element :early_period_in_days, Integer
|
13
|
+
element :has_been_sent, Boolean
|
14
|
+
element :include_expenses, Boolean
|
15
|
+
element :include_pdf, Boolean
|
16
|
+
element :include_time_entries, Boolean
|
17
|
+
element :invoice_date, Date
|
18
|
+
element :item_actual, Float # readonly
|
19
|
+
element :item_cost, Float # readonly
|
20
|
+
element :item_profit, Float # readonly
|
21
|
+
element :item_taxable, Float # readonly
|
22
|
+
element :late_fee, Float # readonly
|
23
|
+
element :late_percentage, Float
|
24
|
+
element :late_period_in_days, Integer
|
25
|
+
element :notes
|
26
|
+
element :payment_total, Float # readonly
|
27
|
+
element :post_reminder_in_days, Integer
|
28
|
+
element :pre_reminder_in_days, Integer
|
29
|
+
element :sales_tax, Float
|
30
|
+
element :sales_tax_2, Float
|
31
|
+
element :sales_tax_2_cumulative, Boolean
|
32
|
+
element :total, Float # readonly
|
33
|
+
element :total_quantity, Float # readonly
|
34
|
+
|
35
|
+
# Returns all associated LineItems
|
36
|
+
def line_items(options={})
|
37
|
+
self.class.get_collection(
|
38
|
+
self.links[:line_items], Cashboard::InvoiceLineItem, options
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Imports uninvoiced items (time entries, expenses, flat fee tasks)
|
43
|
+
# that belong to the same client that this invoice was created for.
|
44
|
+
#
|
45
|
+
# Either raises a Cashboard error (errors.rb) or returns a collection
|
46
|
+
# of Cashboard::InvoiceLineItem objects.
|
47
|
+
def import_uninvoiced_items(project_ids={}, start_date=nil, end_date=nil)
|
48
|
+
xml_options = get_import_xml_options(project_ids, start_date, end_date)
|
49
|
+
|
50
|
+
options = self.class.merge_options()
|
51
|
+
options.merge!({:body => xml_options})
|
52
|
+
response = self.class.put(self.links[:import_uninvoiced_items], options)
|
53
|
+
|
54
|
+
self.class.check_status_code(response)
|
55
|
+
|
56
|
+
collection = response.parsed_response[Cashboard::InvoiceLineItem.resource_name.singularize]
|
57
|
+
collection.map do |h|
|
58
|
+
Cashboard::InvoiceLineItem.new(h)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_import_xml_options(project_ids, start_date, end_date)
|
63
|
+
xml_options = ''
|
64
|
+
xml = Builder::XmlMarkup.new(:target => xml_options, :indent => 2)
|
65
|
+
xml.instruct!
|
66
|
+
xml.projects do
|
67
|
+
project_ids.each {|pid| xml.id pid} if project_ids
|
68
|
+
end
|
69
|
+
xml.start_date start_date
|
70
|
+
xml.end_date end_date
|
71
|
+
|
72
|
+
return xml_options
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class InvoiceLineItem < Base
|
3
|
+
element :description
|
4
|
+
element :flat_fee, Float
|
5
|
+
element :invoice_id
|
6
|
+
element :invoice_schedule_id
|
7
|
+
element :is_taxable, Boolean
|
8
|
+
element :markup_percentage, Float
|
9
|
+
element :price_per, Float
|
10
|
+
element :quantity, Float
|
11
|
+
element :rank, Integer
|
12
|
+
element :title
|
13
|
+
element :total, Float # readonly
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class LineItem < Base
|
3
|
+
TYPE_CODES = {
|
4
|
+
:custom => 0,
|
5
|
+
:task => 1,
|
6
|
+
:product => 2
|
7
|
+
}
|
8
|
+
|
9
|
+
element :best_time_in_minutes, Integer
|
10
|
+
element :created_on, DateTime
|
11
|
+
element :description, String
|
12
|
+
element :estimate_id, String
|
13
|
+
element :flat_fee, Float
|
14
|
+
element :is_complete, Boolean
|
15
|
+
element :is_taxable, Boolean
|
16
|
+
element :markup_percentage, Float
|
17
|
+
element :price_per, Float
|
18
|
+
element :project_id, String
|
19
|
+
element :quantity_low, Float
|
20
|
+
element :quantity_high, Float
|
21
|
+
element :rank, Integer
|
22
|
+
element :title, String
|
23
|
+
element :type_code, Integer
|
24
|
+
element :unit_label, String
|
25
|
+
element :worst_time_in_minutes, Integer
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class Payment < Base
|
3
|
+
element :amount, Float
|
4
|
+
element :assigned_id
|
5
|
+
element :client_id
|
6
|
+
element :client_type
|
7
|
+
element :created_on, Date
|
8
|
+
element :document_template_id
|
9
|
+
element :estimate_id
|
10
|
+
element :notes
|
11
|
+
element :person_id
|
12
|
+
element :transaction_id
|
13
|
+
|
14
|
+
# Returns all InvoicePayments associated with this payment
|
15
|
+
def invoice_payments(options={})
|
16
|
+
self.class.get_collection(
|
17
|
+
self.links[:invoices], Cashboard::InvoicePayment, options
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class Project < Base
|
3
|
+
include Cashboard::Behaviors::Toggleable
|
4
|
+
include Cashboard::Behaviors::ListsLineItems
|
5
|
+
|
6
|
+
BILLING_CODES = {
|
7
|
+
:non_billable => 0,
|
8
|
+
:task_rate => 1,
|
9
|
+
:employee_rate => 2
|
10
|
+
}
|
11
|
+
|
12
|
+
CLIENT_VIEW_TIME_CODES = {
|
13
|
+
:show_when_invoiced => 0,
|
14
|
+
:show_when_marked_billable => 1,
|
15
|
+
:never => 2
|
16
|
+
}
|
17
|
+
|
18
|
+
element :billing_code, Integer
|
19
|
+
element :client_name, String
|
20
|
+
element :client_id, Integer
|
21
|
+
element :client_type, String
|
22
|
+
element :client_view_time_code, Integer
|
23
|
+
element :completion_date, Date
|
24
|
+
element :created_on, Date
|
25
|
+
element :is_active, Boolean
|
26
|
+
element :name, String
|
27
|
+
element :rate, Float
|
28
|
+
element :start_date, Date
|
29
|
+
|
30
|
+
# Returns all employee ProjectAssignments
|
31
|
+
def employee_project_assignments(options={})
|
32
|
+
self.class.get_collection(
|
33
|
+
self.links[:assigned_employees], Cashboard::ProjectAssignment, options
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Cashboard
|
2
|
+
class TimeEntry < Base
|
3
|
+
element :created_on, DateTime
|
4
|
+
element :description
|
5
|
+
element :invoice_line_item_id # readonly
|
6
|
+
element :is_billable, Boolean
|
7
|
+
element :is_running, Boolean # readonly
|
8
|
+
element :line_item_id
|
9
|
+
element :minutes, Integer
|
10
|
+
element :minutes_with_timer, Integer # readonly
|
11
|
+
element :person_id
|
12
|
+
element :timer_started_at, DateTime # readonly
|
13
|
+
|
14
|
+
# Starts or stops timer depending on its current state.
|
15
|
+
#
|
16
|
+
# Will return an object of Cashboard::Struct if another timer was stopped
|
17
|
+
# during this toggle operation.
|
18
|
+
#
|
19
|
+
# Will return nil if no timer was stopped.
|
20
|
+
def toggle_timer
|
21
|
+
options = self.class.merge_options()
|
22
|
+
options.merge!({:body => self.to_xml})
|
23
|
+
response = self.class.put(self.links[:toggle_timer], options)
|
24
|
+
|
25
|
+
# Raise special errors if not a success
|
26
|
+
self.class.check_status_code(response)
|
27
|
+
|
28
|
+
# Re-initialize ourselves with information from response
|
29
|
+
initialize(response.parsed_response)
|
30
|
+
|
31
|
+
if self.stopped_timer
|
32
|
+
stopped_timer = Cashboard::Struct.new(self.stopped_timer)
|
33
|
+
end
|
34
|
+
|
35
|
+
stopped_timer || nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# If a TimeEntry has no invoice_line_item_id set, then it
|
39
|
+
# hasn't been included on an invoice.
|
40
|
+
def has_been_invoiced?
|
41
|
+
!self.invoice_line_item_id.blank?
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/cashboard.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'typecasted_open_struct'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'active_support'
|
6
|
+
require 'httparty'
|
7
|
+
require 'xmlsimple'
|
8
|
+
require 'builder'
|
9
|
+
|
10
|
+
module Cashboard
|
11
|
+
# When reading the parsed hashes generated from parser we ignore these pairs.
|
12
|
+
IGNORED_XML_KEYS = ['rel', 'read_only']
|
13
|
+
|
14
|
+
class Struct < TypecastedOpenStruct
|
15
|
+
# Since we're dealing with initializing from hashes with 'content'
|
16
|
+
# keys we need to set properties based on those keys.
|
17
|
+
#
|
18
|
+
# Additionally, we do some magic to ignore attributes we don't care about.
|
19
|
+
#
|
20
|
+
# The basic concept is lifted from ostruct.rb
|
21
|
+
def initialize(hash={})
|
22
|
+
@table = {}
|
23
|
+
return unless hash
|
24
|
+
hash.each do |k,v|
|
25
|
+
# Remove keys that aren't useful for our purposes.
|
26
|
+
if v.class == Hash
|
27
|
+
Cashboard::IGNORED_XML_KEYS.each {|ignored| v.delete(ignored)}
|
28
|
+
end
|
29
|
+
# Access items based on the 'content' key inside the hash.
|
30
|
+
# Allows us to deal with all XML tags equally, even if the tags
|
31
|
+
# have attributes or not.
|
32
|
+
if v.class == Hash && v['content']
|
33
|
+
@table[k.to_sym] = v['content']
|
34
|
+
elsif v.class == Hash && v.empty?
|
35
|
+
@table[k.to_sym] = nil
|
36
|
+
else
|
37
|
+
@table[k.to_sym] = v
|
38
|
+
end
|
39
|
+
new_ostruct_member(k)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Override HTTParty's XML parsing, which doesn't really work
|
46
|
+
# well for the output we receive.
|
47
|
+
class HTTParty::Parser
|
48
|
+
protected
|
49
|
+
def xml
|
50
|
+
XmlSimple.xml_in(
|
51
|
+
body,
|
52
|
+
'KeepRoot' => false,
|
53
|
+
# Force 'link' tags into an array always
|
54
|
+
'ForceArray' => %w(link),
|
55
|
+
# Force each item into a hash with a 'content' key for the tag value
|
56
|
+
# If we don't do this random tag attributes can screw us up.
|
57
|
+
'ForceContent' => true
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# After we've defined some basics let's include
|
63
|
+
# the Cashboard-rb API libraries
|
64
|
+
|
65
|
+
# Load base first or there's some issues with dependencies.
|
66
|
+
require 'cashboard/base'
|
67
|
+
require 'cashboard/behaviors/base'
|
68
|
+
require 'cashboard/behaviors/toggleable'
|
69
|
+
require 'cashboard/behaviors/lists_line_items'
|
70
|
+
|
71
|
+
library_files = Dir[File.join(File.dirname(__FILE__), 'cashboard/*.rb')]
|
72
|
+
library_files.each do |lib|
|
73
|
+
next if lib.include?('cashboard/base.rb')
|
74
|
+
require lib
|
75
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
class Boolean; end
|
6
|
+
|
7
|
+
# A class to provide a quick and dirty way to typecast attributes
|
8
|
+
# that we specify, while silently setting the rest.
|
9
|
+
#
|
10
|
+
# Allows us to specify important attributes, but doesn't force us to
|
11
|
+
# update schema in order to deal with unexpected new properties.
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# class MyFoo < TypecastedOpenStruct
|
17
|
+
# element :true_false_thing, Boolean
|
18
|
+
# element :amount, Float
|
19
|
+
# end
|
20
|
+
class TypecastedOpenStruct < OpenStruct
|
21
|
+
@@elements = {}
|
22
|
+
|
23
|
+
def self.element(name, attr_type=String, options={})
|
24
|
+
element = Element.new(name, attr_type, options)
|
25
|
+
@@elements[name] = element
|
26
|
+
|
27
|
+
# Define getter to attr_typecast proper value
|
28
|
+
define_method(element.method_name) do
|
29
|
+
self.class.attr_typecast(
|
30
|
+
@table[element.method_name.to_sym],
|
31
|
+
@@elements[name].attr_type
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Lifted from HappyMapper. Thanks! :)
|
37
|
+
def self.attr_typecast(value, attr_type)
|
38
|
+
return value if value.kind_of?(attr_type) || value.nil?
|
39
|
+
begin
|
40
|
+
if attr_type == String then value.to_s
|
41
|
+
elsif attr_type == Float then value.to_f
|
42
|
+
elsif attr_type == Time then Time.parse(value.to_s)
|
43
|
+
elsif attr_type == Date then Date.parse(value.to_s)
|
44
|
+
elsif attr_type == DateTime then DateTime.parse(value.to_s)
|
45
|
+
elsif attr_type == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
|
46
|
+
elsif attr_type == Integer
|
47
|
+
# ganked from datamapper
|
48
|
+
value_to_i = value.to_i
|
49
|
+
if value_to_i == 0 && value != '0'
|
50
|
+
value_to_s = value.to_s
|
51
|
+
begin
|
52
|
+
Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
|
53
|
+
rescue ArgumentError
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
else
|
57
|
+
value_to_i
|
58
|
+
end
|
59
|
+
else
|
60
|
+
value
|
61
|
+
end
|
62
|
+
rescue
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Element
|
68
|
+
attr_types = [String, Float, Time, Date, DateTime, Integer, Boolean]
|
69
|
+
|
70
|
+
attr_accessor :name, :attr_type, :options, :namespace
|
71
|
+
|
72
|
+
def initialize(name, attr_type=String, options={})
|
73
|
+
self.name = name.to_s
|
74
|
+
self.attr_type = attr_type
|
75
|
+
self.options = options
|
76
|
+
end
|
77
|
+
|
78
|
+
def method_name
|
79
|
+
@method_name ||= name.tr('-', '_')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|