podio 0.8.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/Gemfile +9 -2
  4. data/README.md +1 -4
  5. data/examples/oauth_web_flow/Gemfile +4 -0
  6. data/examples/{sinatra.rb → oauth_web_flow/sinatra.rb} +7 -6
  7. data/lib/podio.rb +18 -3
  8. data/lib/podio/active_podio/base.rb +107 -62
  9. data/lib/podio/client.rb +30 -5
  10. data/lib/podio/error.rb +22 -10
  11. data/lib/podio/middleware/error_response.rb +23 -16
  12. data/lib/podio/middleware/json_response.rb +1 -1
  13. data/lib/podio/middleware/logger.rb +3 -0
  14. data/lib/podio/middleware/oauth2.rb +3 -0
  15. data/lib/podio/models/account_provider.rb +1 -0
  16. data/lib/podio/models/action.rb +19 -2
  17. data/lib/podio/models/activation_status.rb +8 -2
  18. data/lib/podio/models/app_store_category.rb +4 -1
  19. data/lib/podio/models/app_store_share.rb +29 -16
  20. data/lib/podio/models/application.rb +57 -2
  21. data/lib/podio/models/application_email.rb +5 -2
  22. data/lib/podio/models/application_field.rb +5 -2
  23. data/lib/podio/models/batch.rb +8 -0
  24. data/lib/podio/models/by_line.rb +2 -1
  25. data/lib/podio/models/calendar_event.rb +63 -3
  26. data/lib/podio/models/calendar_mute.rb +6 -1
  27. data/lib/podio/models/category.rb +1 -0
  28. data/lib/podio/models/comment.rb +29 -8
  29. data/lib/podio/models/condition.rb +8 -0
  30. data/lib/podio/models/condition_set.rb +40 -0
  31. data/lib/podio/models/contact.rb +13 -2
  32. data/lib/podio/models/contract.rb +85 -1
  33. data/lib/podio/models/contract_event.rb +17 -0
  34. data/lib/podio/models/contract_price.rb +3 -7
  35. data/lib/podio/models/contract_price_v2.rb +31 -0
  36. data/lib/podio/models/contract_user.rb +4 -0
  37. data/lib/podio/models/conversation.rb +114 -13
  38. data/lib/podio/models/conversation_event.rb +51 -0
  39. data/lib/podio/models/conversation_message.rb +2 -1
  40. data/lib/podio/models/conversation_participant.rb +3 -2
  41. data/lib/podio/models/date_election.rb +35 -0
  42. data/lib/podio/models/email_contact.rb +46 -0
  43. data/lib/podio/models/email_subscription_setting.rb +4 -23
  44. data/lib/podio/models/embed.rb +4 -4
  45. data/lib/podio/models/experiment.rb +71 -0
  46. data/lib/podio/models/external_file.rb +7 -2
  47. data/lib/podio/models/file_attachment.rb +23 -0
  48. data/lib/podio/models/form.rb +7 -0
  49. data/lib/podio/models/friend.rb +12 -0
  50. data/lib/podio/models/grant.rb +73 -0
  51. data/lib/podio/models/hook.rb +13 -6
  52. data/lib/podio/models/importer.rb +3 -0
  53. data/lib/podio/models/integration.rb +17 -6
  54. data/lib/podio/models/invoice.rb +30 -0
  55. data/lib/podio/models/item.rb +81 -14
  56. data/lib/podio/models/item_diff.rb +9 -5
  57. data/lib/podio/models/item_field.rb +11 -2
  58. data/lib/podio/models/item_revision.rb +5 -2
  59. data/lib/podio/models/linked_account.rb +1 -0
  60. data/lib/podio/models/live.rb +61 -0
  61. data/lib/podio/models/net_promoter_score.rb +28 -0
  62. data/lib/podio/models/notification.rb +9 -4
  63. data/lib/podio/models/notification_group.rb +5 -2
  64. data/lib/podio/models/o_auth.rb +4 -2
  65. data/lib/podio/models/o_auth_client.rb +1 -2
  66. data/lib/podio/models/organization.rb +22 -10
  67. data/lib/podio/models/organization_contact.rb +2 -1
  68. data/lib/podio/models/organization_member.rb +17 -3
  69. data/lib/podio/models/organization_profile.rb +8 -2
  70. data/lib/podio/models/pin.rb +27 -0
  71. data/lib/podio/models/profile.rb +19 -50
  72. data/lib/podio/models/promotion.rb +91 -0
  73. data/lib/podio/models/rating.rb +23 -5
  74. data/lib/podio/models/recurrence.rb +6 -3
  75. data/lib/podio/models/reference.rb +17 -1
  76. data/lib/podio/models/reminder.rb +8 -3
  77. data/lib/podio/models/search.rb +7 -1
  78. data/lib/podio/models/space.rb +41 -2
  79. data/lib/podio/models/space_contact.rb +1 -2
  80. data/lib/podio/models/space_invitation.rb +4 -5
  81. data/lib/podio/models/space_member.rb +37 -4
  82. data/lib/podio/models/status.rb +20 -8
  83. data/lib/podio/models/stream_activity_group.rb +40 -0
  84. data/lib/podio/models/stream_mute.rb +7 -8
  85. data/lib/podio/models/stream_object.rb +19 -11
  86. data/lib/podio/models/subscription.rb +7 -1
  87. data/lib/podio/models/tag.rb +6 -1
  88. data/lib/podio/models/tag_search.rb +2 -1
  89. data/lib/podio/models/task.rb +74 -18
  90. data/lib/podio/models/task_label.rb +10 -2
  91. data/lib/podio/models/user.rb +50 -6
  92. data/lib/podio/models/user_mail.rb +4 -0
  93. data/lib/podio/models/user_status.rb +7 -0
  94. data/lib/podio/models/view.rb +27 -7
  95. data/lib/podio/models/widget.rb +25 -3
  96. data/lib/podio/version.rb +1 -1
  97. data/podio.gemspec +13 -10
  98. data/script/config.example.rb +6 -0
  99. data/script/referenceGenerator.rb +87 -0
  100. data/test/active_podio_test.rb +44 -34
  101. data/test/client_test.rb +13 -2
  102. metadata +89 -36
  103. data/lib/podio/middleware/date_conversion.rb +0 -37
  104. data/lib/podio/models/bulletin.rb +0 -60
  105. data/lib/podio/models/connection.rb +0 -53
  106. data/lib/podio/models/news.rb +0 -85
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ddca0ab27daadb721f418559e369f9df19ff079b
4
+ data.tar.gz: 62879f0a0051374d7edf58585fb23fcdc1b8a65f
5
+ SHA512:
6
+ metadata.gz: 745846890f8a4a11a0ae2591db7d2921ab9fa33cb696b7573aedb7de87c46ddbf4966f16d97132e7da7e13756dff437eb48ae2dff15b34a6e7eed5509fd3550b
7
+ data.tar.gz: b1bd3c5b7177185fa91b198f88b05ebd51820c30a4fca4fb00f7f0feab151b2afa3ab2aa013f9881b623ba17eb5db3632be90a302585eeb9263c42a6f0c0297c
data/.gitignore CHANGED
@@ -2,3 +2,6 @@ pkg/*
2
2
  *.gem
3
3
  .bundle
4
4
  .DS_Store
5
+ Gemfile.lock
6
+ script/config.rb
7
+ *.swp
data/Gemfile CHANGED
@@ -1,5 +1,12 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
- gem 'rake'
3
+ platforms :jruby do
4
+ gem 'jruby-openssl', '~> 0.7'
5
+ end
6
+
7
+ platforms :rbx do
8
+ gem 'rubysl', '~> 2.0.15'
9
+ gem 'rubysl-test-unit', '~> 2.0.3'
10
+ end
4
11
 
5
12
  gemspec
data/README.md CHANGED
@@ -113,7 +113,7 @@ All unsuccessful responses returned by the API (everything that has a 4xx or 5xx
113
113
  puts exc.url # uri of the API request
114
114
 
115
115
  # you normally want this one, a human readable error description
116
- puts exc.response_body['error_description']
116
+ puts exc.message
117
117
  end
118
118
 
119
119
  On instance methods, however, exceptions are handled in a way similar to ActiveRecord. These methods returns a boolean indicating if the API request succeeded or not, and makes the code, description and parameters available when the request fails:
@@ -146,9 +146,6 @@ Full Example
146
146
  puts org.url
147
147
  end
148
148
 
149
- Note on Heroku Usage
150
- --------------------
151
- If you plan on using podio-rb on Heroku please note that only the 1.9.2 stack has been tested. Specifically, bamboo-mri-1.9.2 is recommended, while 1.8.7 is still stock on Heroku. Refer to their documentation for information on how to migrate your dynos
152
149
 
153
150
  Meta
154
151
  ----
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'sinatra', '~> 1.4.4'
4
+ gem 'podio'
@@ -18,11 +18,12 @@ require 'sinatra'
18
18
  require 'podio'
19
19
 
20
20
 
21
- # CHANGE this this to make this example work
22
- Podio.setup(
23
- :api_key => 'YOUR_API_KEY',
24
- :api_secret => 'YOUR_API_SECRET'
25
- )
21
+ before do
22
+ Podio.setup(
23
+ :api_key => 'YOUR_API_KEY', # CHANGE this this to make this example work
24
+ :api_secret => 'YOUR_API_SECRET' # CHANGE this this to make this example work
25
+ )
26
+ end
26
27
 
27
28
  get '/' do
28
29
  %(<p>Update the <code>Podio.setup</code> call in the sinatra app and <a href="/auth/podio">try to authorize</a>.</p>)
@@ -50,6 +51,6 @@ end
50
51
 
51
52
  def redirect_uri(path='/auth/podio/callback')
52
53
  uri = URI.parse(request.url)
53
- uri.path = path
54
+ uri.path = path
54
55
  uri.to_s
55
56
  end
@@ -2,8 +2,8 @@ require 'faraday'
2
2
  require 'active_support/core_ext'
3
3
 
4
4
  require 'podio/error'
5
+ require 'podio/version'
5
6
  require 'podio/middleware/json_request'
6
- #require 'podio/middleware/date_conversion'
7
7
  require 'podio/middleware/logger'
8
8
  require 'podio/middleware/oauth2'
9
9
  require 'podio/middleware/json_response'
@@ -76,36 +76,48 @@ module Podio
76
76
  autoload :ApplicationField, 'podio/models/application_field'
77
77
  autoload :AccountProvider, 'podio/models/account_provider'
78
78
  autoload :Batch, 'podio/models/batch'
79
- autoload :Bulletin, 'podio/models/bulletin'
80
79
  autoload :ByLine, 'podio/models/by_line'
81
80
  autoload :CalendarEvent, 'podio/models/calendar_event'
82
81
  autoload :CalendarMute, 'podio/models/calendar_mute'
83
82
  autoload :Category, 'podio/models/category'
84
83
  autoload :Comment, 'podio/models/comment'
84
+ autoload :Condition, 'podio/models/condition'
85
+ autoload :ConditionSet, 'podio/models/condition_set'
85
86
  autoload :Connection, 'podio/models/connection'
86
87
  autoload :Contact, 'podio/models/contact'
87
88
  autoload :Contract, 'podio/models/contract'
88
89
  autoload :ContractAccounting, 'podio/models/contract_accounting'
90
+ autoload :ContractEvent, 'podio/models/contract_event'
89
91
  autoload :ContractPrice, 'podio/models/contract_price'
92
+ autoload :ContractPriceV2, 'podio/models/contract_price_v2'
93
+ autoload :ContractUser, 'podio/models/contract_user'
90
94
  autoload :Conversation, 'podio/models/conversation'
95
+ autoload :ConversationEvent, 'podio/models/conversation_event'
91
96
  autoload :ConversationMessage, 'podio/models/conversation_message'
92
97
  autoload :ConversationParticipant, 'podio/models/conversation_participant'
98
+ autoload :DateElection, 'podio/models/date_election'
93
99
  autoload :EmailSubscriptionSetting, 'podio/models/email_subscription_setting'
100
+ autoload :EmailContact, 'podio/models/email_contact'
94
101
  autoload :Embed, 'podio/models/embed'
102
+ autoload :Experiment, 'podio/models/experiment'
95
103
  autoload :ExternalFile, 'podio/models/external_file'
96
104
  autoload :FileAttachment, 'podio/models/file_attachment'
97
105
  autoload :Filter, 'podio/models/filter'
98
106
  autoload :Form, 'podio/models/form'
107
+ autoload :Friend, 'podio/models/friend'
108
+ autoload :Grant, 'podio/models/grant'
99
109
  autoload :Hook, 'podio/models/hook'
100
110
  autoload :Importer, 'podio/models/importer'
101
111
  autoload :Integration, 'podio/models/integration'
112
+ autoload :Invoice, 'podio/models/invoice'
102
113
  autoload :Item, 'podio/models/item'
103
114
  autoload :ItemDiff, 'podio/models/item_diff'
104
115
  autoload :ItemField, 'podio/models/item_field'
105
116
  autoload :ItemRevision, 'podio/models/item_revision'
106
117
  autoload :LinkedAccount, 'podio/models/linked_account'
107
118
  autoload :LinkedAccountData, 'podio/models/linked_account_data'
108
- autoload :News, 'podio/models/news'
119
+ autoload :Live, 'podio/models/live'
120
+ autoload :NetPromoterScore, 'podio/models/net_promoter_score'
109
121
  autoload :Notification, 'podio/models/notification'
110
122
  autoload :NotificationGroup, 'podio/models/notification_group'
111
123
  autoload :OAuth, 'podio/models/o_auth'
@@ -114,7 +126,9 @@ module Podio
114
126
  autoload :OrganizationContact, 'podio/models/organization_contact'
115
127
  autoload :OrganizationMember, 'podio/models/organization_member'
116
128
  autoload :OrganizationProfile, 'podio/models/organization_profile'
129
+ autoload :Pin, 'podio/models/pin'
117
130
  autoload :Profile, 'podio/models/profile'
131
+ autoload :Promotion, 'podio/models/promotion'
118
132
  autoload :Question, 'podio/models/question'
119
133
  autoload :QuestionAnswer, 'podio/models/question_answer'
120
134
  autoload :QuestionOption, 'podio/models/question_option'
@@ -131,6 +145,7 @@ module Podio
131
145
  autoload :Status, 'podio/models/status'
132
146
  autoload :StreamMute, 'podio/models/stream_mute'
133
147
  autoload :StreamObject, 'podio/models/stream_object'
148
+ autoload :StreamActivityGroup, 'podio/models/stream_activity_group'
134
149
  autoload :Subscription, 'podio/models/subscription'
135
150
  autoload :Tag, 'podio/models/tag'
136
151
  autoload :TagSearch, 'podio/models/tag_search'
@@ -6,12 +6,15 @@ module ActivePodio
6
6
  extend ActiveModel::Naming, ActiveModel::Callbacks
7
7
  include ActiveModel::Conversion
8
8
 
9
- class_attribute :valid_attributes, :_associations, :_properties, :json_attributes
10
- attr_accessor :attributes, :error_code, :error_message, :error_parameters, :error_propagate
11
- alias_method :propagate_error?, :error_propagate
9
+ class_attribute :valid_attributes, :json_attributes, :_associations
10
+
11
+ self.valid_attributes = []
12
+ self.json_attributes = []
13
+ self._associations = {}
14
+
15
+ attr_accessor :attributes
12
16
 
13
17
  def initialize(attributes = {}, options = {})
14
- self.valid_attributes ||= []
15
18
  attributes = {} if attributes.blank?
16
19
  self.attributes = Hash[*self.valid_attributes.collect { |n| [n.to_sym, nil] }.flatten].merge(attributes.symbolize_keys)
17
20
 
@@ -28,11 +31,20 @@ module ActivePodio
28
31
  if self.respond_to?("#{key}=".to_sym)
29
32
  self.send("#{key}=".to_sym, value)
30
33
  else
31
- is_association_hash = value.is_a?(Hash) && self._associations.present? && self._associations.has_key?(key.to_sym) && self._associations[key.to_sym] == :has_one && (self.send(key.to_sym).respond_to?(:attributes) || self.send(key.to_sym).nil?)
34
+ is_association_hash = value.is_a?(Hash) && self._associations.has_key?(key.to_sym) && self._associations[key.to_sym] == :has_one && (self.send(key.to_sym).respond_to?(:attributes) || self.send(key.to_sym).nil?)
32
35
  if valid_attributes.include?(key.to_sym) || is_association_hash
36
+
33
37
  # Initialize nested object to get correctly casted values set back, unless the given values are all blank
34
38
  if is_association_hash
35
- self.send(:[]=, key.to_sym, value) if self.send(key.to_sym).nil? # If not set by constructor, set here to get typed values back
39
+ # Merge existing values with given values and do recursive initalize call to get values casted correctly
40
+ if self.send(key.to_sym).present?
41
+ existing_attributes = self.send(key.to_sym).try(:attributes) || {}
42
+ value.reverse_merge!(existing_attributes)
43
+ self.send(key.to_sym).initialize_attributes(value)
44
+ else
45
+ self.send(:[]=, key.to_sym, value)
46
+ end
47
+
36
48
  attributes = self.send(key.to_sym).try(:attributes)
37
49
  if attributes.present? && any_values_present_recursive?(attributes.values)
38
50
  value = attributes
@@ -68,7 +80,7 @@ module ActivePodio
68
80
  def []=(attribute, value)
69
81
  @attributes ||= {}
70
82
  @attributes[attribute.to_sym] = value
71
- if @belongs_to.present? && value.present?
83
+ if @belongs_to.present? && @belongs_to[:model].present? && @belongs_to[:as].present? && value.present?
72
84
  @belongs_to[:model][@belongs_to[:as]] ||= {}
73
85
  @belongs_to[:model][@belongs_to[:as]][attribute.to_sym] = value
74
86
  end
@@ -89,18 +101,27 @@ module ActivePodio
89
101
  result.merge!(:id => self.id) if self.respond_to?(:id)
90
102
 
91
103
  if options[:formatted]
92
- (self.valid_attributes + (self.json_attributes || [])).uniq.each do |name|
104
+ (self.valid_attributes + self.json_attributes).uniq.each do |name|
93
105
  result[name] = json_friendly_value(self.send(name), options)
94
106
  end
95
107
 
96
108
  unless options[:nested] == false
97
- if self._associations.respond_to?(:each)
98
- self._associations.each do |name, type|
109
+ self._associations.each do |name, type|
110
+ nested_value = self.send(name)
111
+ unless nested_value.nil?
112
+ nested_options = options.except(:methods)
113
+ if options[:methods].present? && options[:methods].respond_to?(:find)
114
+ methods_hash = options[:methods].find { |method| method.is_a?(Hash) }
115
+ if methods_hash.present?
116
+ nested_methods = methods_hash[name]
117
+ nested_options.merge!(:methods => nested_methods) if nested_methods.present?
118
+ end
119
+ end
99
120
  case type
100
121
  when :has_one
101
- result[name] = self.send(name).as_json(options.except(:methods))
122
+ result[name] = nested_value.as_json(nested_options)
102
123
  when :has_many
103
- result[name] = self.send(name).collect { |assoc| assoc.as_json(options.except(:methods)) }
124
+ result[name] = nested_value.collect { |assoc| assoc.as_json(nested_options) }
104
125
  end
105
126
  end
106
127
  end
@@ -111,7 +132,7 @@ module ActivePodio
111
132
 
112
133
  if options[:methods]
113
134
  options[:methods].each do |name|
114
- result[name] = json_friendly_value(self.send(name), options)
135
+ result[name] = json_friendly_value(self.send(name), options.except(:methods) ) unless name.is_a?(Hash)
115
136
  end
116
137
  end
117
138
 
@@ -120,17 +141,26 @@ module ActivePodio
120
141
 
121
142
  # Override this in models where the class name doesn't match the ref type
122
143
  def api_friendly_ref_type
123
- self.class.name.downcase
144
+ self.class.name.demodulize.parameterize
145
+ end
146
+
147
+ def parent_model
148
+ @belongs_to[:model] if @belongs_to.present?
124
149
  end
125
150
 
126
151
  private
127
152
 
128
153
  def klass_for_association(options)
129
154
  klass_name = options[:class]
155
+
156
+ if !klass_name.present? && options[:class_map].present?
157
+ class_property = options[:class_property] || :type
158
+ class_property_value = self.send(class_property).try(:to_sym)
159
+ klass_name = options[:class_map][class_property_value]
160
+ end
161
+
130
162
  raise "Missing class name of associated model. Provide with :class => 'MyClass'." unless klass_name.present?
131
- klass = klass_name.constantize rescue nil
132
- klass = "Podio::#{klass_name}".constantize unless klass.respond_to?(:ancestors) && klass.ancestors.include?(ActivePodio::Base)
133
- return klass
163
+ return self.class.klass_from_string(klass_name)
134
164
  end
135
165
 
136
166
  def any_values_present_recursive?(values)
@@ -145,7 +175,7 @@ module ActivePodio
145
175
 
146
176
  def json_friendly_value(ruby_value, options)
147
177
  if options[:formatted]
148
- json_value = case ruby_value.class.name
178
+ case ruby_value.class.name
149
179
  when "DateTime", "Time"
150
180
  ruby_value.iso8601
151
181
  when "Array"
@@ -153,9 +183,11 @@ module ActivePodio
153
183
  when "Hash"
154
184
  ruby_value.each { |key, value| ruby_value[key] = json_friendly_value(value, options) }
155
185
  ruby_value
186
+ when "BigDecimal"
187
+ ruby_value.to_f # No Decimal in Javascript, Float is better than String
156
188
  else
157
189
  if ruby_value.kind_of?(ActivePodio::Base)
158
- ruby_value.as_json(options)
190
+ ruby_value.as_json(options.except(:methods))
159
191
  else
160
192
  ruby_value
161
193
  end
@@ -163,6 +195,7 @@ module ActivePodio
163
195
  else
164
196
  ruby_value
165
197
  end
198
+
166
199
  end
167
200
 
168
201
  class << self
@@ -171,8 +204,7 @@ module ActivePodio
171
204
 
172
205
  # Defines the the supported attributes of the model
173
206
  def property(name, type = :string, options = {})
174
- self.valid_attributes ||= []
175
- self.valid_attributes << name
207
+ self.valid_attributes += [name]
176
208
 
177
209
  case type
178
210
  when :datetime
@@ -188,6 +220,10 @@ module ActivePodio
188
220
  define_boolean_accessors(name)
189
221
  when :array
190
222
  define_array_accessors(name)
223
+ when :float
224
+ define_float_accessor(name)
225
+ when :decimal
226
+ define_decimal_accessor(name)
191
227
  else
192
228
  define_generic_accessor(name)
193
229
  end
@@ -195,8 +231,7 @@ module ActivePodio
195
231
 
196
232
  # Wraps a single hash provided from the API in the given model
197
233
  def has_one(name, options = {})
198
- self._associations ||= {}
199
- self._associations[name] = :has_one
234
+ self._associations = self._associations.merge({name => :has_one})
200
235
 
201
236
  self.send(:define_method, name) do
202
237
  klass = klass_for_association(options)
@@ -220,8 +255,7 @@ module ActivePodio
220
255
 
221
256
  # Wraps a collection of hashes from the API to a collection of the given model
222
257
  def has_many(name, options = {})
223
- self._associations ||= {}
224
- self._associations[name] = :has_many
258
+ self._associations = self._associations.merge({name => :has_many})
225
259
 
226
260
  self.send(:define_method, name) do
227
261
  klass = klass_for_association(options)
@@ -229,7 +263,7 @@ module ActivePodio
229
263
  unless instances.present?
230
264
  property = options[:property] || name.to_sym
231
265
  if self[property].present? && self[property].respond_to?(:map)
232
- instances = self[property].map { |attributes| klass.new(attributes) }
266
+ instances = self[property].map { |attributes| klass.new(attributes, :belongs_to => { :model => self }) }
233
267
  self.instance_variable_set("@#{name}_has_many_instances", instances)
234
268
  else
235
269
  instances = []
@@ -284,45 +318,16 @@ module ActivePodio
284
318
  end
285
319
  end
286
320
 
287
- # Wraps the given methods in a begin/rescue block
288
- # If no error occurs, the return value of the method, or true if nil is returned, is returned
289
- # If a Podio::PodioError occurs, the method returns false and the error can be read from the error_message accessor
290
- # If another error occurs, it is still raised
291
- def handle_api_errors_for(*method_names)
292
- method_names.each do |method_name|
293
- self.send(:define_method, "#{method_name}_with_api_errors_handled") do |*args|
294
- success, code, message, parameters, result = nil
295
- begin
296
- result = self.send("#{method_name}_without_api_errors_handled", *args)
297
- success = true
298
- rescue Podio::PodioError => ex
299
- success = false
300
- code = ex.response_body["error"]
301
- message = ex.response_body["error_description"]
302
- parameters = ex.response_body["error_parameters"]
303
- propagate = ex.response_body["error_propagate"]
304
- end
305
-
306
- if success
307
- return result || true
308
- else
309
- @error_code = code
310
- @error_message = message
311
- @error_parameters = parameters || {}
312
- @error_propagate = propagate
313
- return false
314
- end
315
- end
316
-
317
- alias_method_chain method_name, :api_errors_handled
318
- end
319
- end
320
-
321
321
  def output_attribute_as_json(*attributes)
322
- self.json_attributes ||= []
323
322
  self.json_attributes += attributes
324
323
  end
325
324
 
325
+ def klass_from_string(klass_name)
326
+ klass = klass_name.constantize rescue nil
327
+ klass = "Podio::#{klass_name}".constantize unless klass.respond_to?(:ancestors) && klass.ancestors.include?(ActivePodio::Base)
328
+ return klass
329
+ end
330
+
326
331
  private
327
332
 
328
333
  def define_generic_accessor(name, options = {})
@@ -354,7 +359,11 @@ module ActivePodio
354
359
  value = if value.is_a?(DateTime)
355
360
  Time.zone.local_to_utc(value)
356
361
  else
357
- Time.zone.parse(value).try(:utc).try(:to_datetime)
362
+ begin
363
+ Time.zone.parse(value).try(:utc).try(:to_datetime)
364
+ rescue TZInfo::AmbiguousTime
365
+ nil
366
+ end
358
367
  end
359
368
  end
360
369
 
@@ -419,6 +428,42 @@ module ActivePodio
419
428
  end
420
429
  end
421
430
 
431
+ def define_float_accessor(name)
432
+ self.send(:define_method, name) do
433
+ if self[name.to_sym].present?
434
+ self[name.to_sym].to_f
435
+ else
436
+ nil
437
+ end
438
+ end
439
+
440
+ self.send(:define_method, "#{name}=") do |value|
441
+ if value.present?
442
+ self[name.to_sym] = value.to_f
443
+ else
444
+ self[name.to_sym] = nil
445
+ end
446
+ end
447
+ end
448
+
449
+ def define_decimal_accessor(name)
450
+ self.send(:define_method, name) do
451
+ if self[name.to_sym].present?
452
+ BigDecimal(self[name.to_sym], 2)
453
+ else
454
+ nil
455
+ end
456
+ end
457
+
458
+ self.send(:define_method, "#{name}=") do |value|
459
+ if value.present?
460
+ self[name.to_sym] = BigDecimal(value, 2)
461
+ else
462
+ self[name.to_sym] = nil
463
+ end
464
+ end
465
+ end
466
+
422
467
  def define_boolean_accessors(name)
423
468
  self.send(:define_method, "#{name}?") do
424
469
  if self[name.to_sym].present?