podio 0.5.1 → 0.6.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.
Files changed (57) hide show
  1. data/.travis.yml +9 -0
  2. data/Gemfile +2 -0
  3. data/Rakefile +2 -0
  4. data/lib/podio.rb +19 -43
  5. data/lib/podio/active_podio/base.rb +83 -23
  6. data/lib/podio/client.rb +14 -3
  7. data/lib/podio/models/action.rb +15 -0
  8. data/lib/podio/models/activation_status.rb +9 -0
  9. data/lib/podio/models/activity.rb +10 -0
  10. data/lib/podio/models/app_store_share.rb +2 -0
  11. data/lib/podio/models/application.rb +30 -1
  12. data/lib/podio/models/application_field.rb +2 -1
  13. data/lib/podio/models/bulletin.rb +1 -0
  14. data/lib/podio/models/calendar.rb +45 -0
  15. data/lib/podio/models/calendar_mute.rb +27 -0
  16. data/lib/podio/models/comment.rb +1 -0
  17. data/lib/podio/models/connection.rb +7 -1
  18. data/lib/podio/models/contact.rb +9 -1
  19. data/lib/podio/models/contract.rb +113 -0
  20. data/lib/podio/models/contract_accounting.rb +33 -0
  21. data/lib/podio/models/contract_price.rb +46 -0
  22. data/lib/podio/models/email_subscription_setting.rb +2 -0
  23. data/lib/podio/models/file_attachment.rb +16 -7
  24. data/lib/podio/models/filter.rb +11 -0
  25. data/lib/podio/models/form.rb +26 -2
  26. data/lib/podio/models/item.rb +32 -13
  27. data/lib/podio/models/item_diff.rb +4 -0
  28. data/lib/podio/models/item_field.rb +1 -1
  29. data/lib/podio/models/meeting.rb +126 -0
  30. data/lib/podio/models/meeting_participiant.rb +5 -0
  31. data/lib/podio/models/news.rb +2 -1
  32. data/lib/podio/models/notification_group.rb +6 -1
  33. data/lib/podio/models/organization.rb +12 -2
  34. data/lib/podio/models/organization_member.rb +2 -3
  35. data/lib/podio/models/profile.rb +49 -1
  36. data/lib/podio/models/rating.rb +6 -0
  37. data/lib/podio/models/recurrence.rb +25 -0
  38. data/lib/podio/models/reminder.rb +33 -0
  39. data/lib/podio/models/search.rb +20 -0
  40. data/lib/podio/models/space.rb +22 -6
  41. data/lib/podio/models/{space_invite.rb → space_invitation.rb} +1 -3
  42. data/lib/podio/models/space_member.rb +7 -0
  43. data/lib/podio/models/status.rb +16 -0
  44. data/lib/podio/models/stream_mute.rb +27 -0
  45. data/lib/podio/models/stream_object.rb +66 -0
  46. data/lib/podio/models/tag.rb +2 -0
  47. data/lib/podio/models/tag_search.rb +14 -0
  48. data/lib/podio/models/task.rb +33 -0
  49. data/lib/podio/models/user.rb +60 -6
  50. data/lib/podio/models/user_mail.rb +7 -0
  51. data/lib/podio/models/user_status.rb +3 -0
  52. data/lib/podio/models/widget.rb +1 -0
  53. data/lib/podio/version.rb +1 -1
  54. data/podio.gemspec +3 -4
  55. data/test/active_podio_test.rb +36 -9
  56. data/test/models_sanity_test.rb +5 -3
  57. metadata +69 -68
@@ -0,0 +1,9 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby
6
+ - rbx
7
+ notifications:
8
+ email:
9
+ - fabricius@podio.com
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source "http://rubygems.org"
2
2
 
3
+ gem 'rake'
4
+
3
5
  gemspec
data/Rakefile CHANGED
@@ -4,6 +4,8 @@ require 'bundler'
4
4
 
5
5
  Bundler::GemHelper.install_tasks
6
6
 
7
+ task :default => [:test]
8
+
7
9
  desc 'Run tests'
8
10
  Rake::TestTask.new(:test) do |t|
9
11
  ENV['ENABLE_STUBS'] = 'true'
@@ -3,7 +3,7 @@ require 'active_support/core_ext'
3
3
 
4
4
  require 'podio/error'
5
5
  require 'podio/middleware/json_request'
6
- require 'podio/middleware/date_conversion'
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'
@@ -66,22 +66,31 @@ module Podio
66
66
  autoload :Client, 'podio/client'
67
67
  autoload :ResponseWrapper, 'podio/response_wrapper'
68
68
 
69
+ autoload :Action, 'podio/models/action'
70
+ autoload :ActivationStatus, 'podio/models/activation_status'
71
+ autoload :Activity, 'podio/models/activity'
69
72
  autoload :AppStoreShare, 'podio/models/app_store_share'
70
73
  autoload :Application, 'podio/models/application'
71
74
  autoload :ApplicationEmail, 'podio/models/application_email'
72
75
  autoload :ApplicationField, 'podio/models/application_field'
73
76
  autoload :Bulletin, 'podio/models/bulletin'
74
77
  autoload :ByLine, 'podio/models/by_line'
78
+ autoload :Calendar, 'podio/models/calendar'
79
+ autoload :CalendarMute, 'podio/models/calendar_mute'
75
80
  autoload :Category, 'podio/models/category'
76
81
  autoload :Comment, 'podio/models/comment'
77
82
  autoload :Connection, 'podio/models/connection'
78
83
  autoload :Contact, 'podio/models/contact'
84
+ autoload :Contract, 'podio/models/contract'
85
+ autoload :ContractAccounting, 'podio/models/contract_accounting'
86
+ autoload :ContractPrice, 'podio/models/contract_price'
79
87
  autoload :Conversation, 'podio/models/conversation'
80
88
  autoload :ConversationMessage, 'podio/models/conversation_message'
81
89
  autoload :ConversationParticipant, 'podio/models/conversation_participant'
82
90
  autoload :EmailSubscriptionSetting, 'podio/models/email_subscription_setting'
83
91
  autoload :Embed, 'podio/models/embed'
84
92
  autoload :FileAttachment, 'podio/models/file_attachment'
93
+ autoload :Filter, 'podio/models/filter'
85
94
  autoload :Form, 'podio/models/form'
86
95
  autoload :Hook, 'podio/models/hook'
87
96
  autoload :Importer, 'podio/models/importer'
@@ -90,6 +99,8 @@ module Podio
90
99
  autoload :ItemDiff, 'podio/models/item_diff'
91
100
  autoload :ItemField, 'podio/models/item_field'
92
101
  autoload :ItemRevision, 'podio/models/item_revision'
102
+ autoload :Meeting, 'podio/models/meeting'
103
+ autoload :MeetingParticipant, 'podio/models/meeting_participiant'
93
104
  autoload :News, 'podio/models/news'
94
105
  autoload :Notification, 'podio/models/notification'
95
106
  autoload :NotificationGroup, 'podio/models/notification_group'
@@ -104,60 +115,25 @@ module Podio
104
115
  autoload :QuestionAnswer, 'podio/models/question_answer'
105
116
  autoload :QuestionOption, 'podio/models/question_option'
106
117
  autoload :Rating, 'podio/models/rating'
118
+ autoload :Recurrence, 'podio/models/recurrence'
119
+ autoload :Reminder, 'podio/models/reminder'
107
120
  autoload :Search, 'podio/models/search'
108
121
  autoload :Space, 'podio/models/space'
109
122
  autoload :SpaceContact, 'podio/models/space_contact'
110
- autoload :SpaceInvite, 'podio/models/space_invite'
123
+ autoload :SpaceInvitation, 'podio/models/space_invitation'
111
124
  autoload :SpaceMember, 'podio/models/space_member'
112
125
  autoload :Status, 'podio/models/status'
126
+ autoload :StreamMute, 'podio/models/stream_mute'
127
+ autoload :StreamObject, 'podio/models/stream_object'
113
128
  autoload :Subscription, 'podio/models/subscription'
114
129
  autoload :Tag, 'podio/models/tag'
130
+ autoload :TagSearch, 'podio/models/tag_search'
115
131
  autoload :Task, 'podio/models/task'
116
132
  autoload :TaskLabel, 'podio/models/task_label'
117
133
  autoload :User, 'podio/models/user'
134
+ autoload :UserMail, 'podio/models/user_mail'
118
135
  autoload :UserStatus, 'podio/models/user_status'
119
136
  autoload :Via, 'podio/models/via'
120
137
  autoload :Widget, 'podio/models/widget'
121
138
 
122
-
123
- # autoload :Application, 'podio/areas/application'
124
- # autoload :Bulletin, 'podio/areas/bulletin'
125
- # autoload :Category, 'podio/areas/app_store'
126
- # autoload :AppStoreShare, 'podio/areas/app_store'
127
- # autoload :Comment, 'podio/areas/comment'
128
- # autoload :Connection, 'podio/areas/connection'
129
- # autoload :Contact, 'podio/areas/contact'
130
- # autoload :Conversation, 'podio/areas/conversation'
131
- # autoload :Email, 'podio/areas/email'
132
- # autoload :File, 'podio/areas/file'
133
- # autoload :Form, 'podio/areas/form'
134
- # autoload :Email, 'podio/areas/email'
135
- # autoload :Hook, 'podio/areas/hook'
136
- # autoload :Item, 'podio/areas/item'
137
- # autoload :ItemDiff, 'podio/areas/item'
138
- # autoload :ItemField, 'podio/areas/item'
139
- # autoload :ItemRevision, 'podio/areas/item'
140
- # autoload :Importer, 'podio/areas/importer'
141
- # autoload :Integration, 'podio/areas/integration'
142
- # autoload :News, 'podio/areas/news'
143
- # autoload :Notification, 'podio/areas/notification'
144
- # autoload :NotificationGroup, 'podio/areas/notification'
145
- # autoload :OAuth, 'podio/areas/oauth'
146
- # autoload :OAuthClient, 'podio/areas/oauth'
147
- # autoload :Organization, 'podio/areas/organization'
148
- # autoload :OrganizationMember, 'podio/areas/organization_member'
149
- # autoload :OrganizationProfile, 'podio/areas/organization_profile'
150
- # autoload :Rating, 'podio/areas/rating'
151
- # autoload :Search, 'podio/areas/search'
152
- # autoload :Space, 'podio/areas/space'
153
- # autoload :SpaceInvite, 'podio/areas/space'
154
- # autoload :SpaceMember, 'podio/areas/space'
155
- # autoload :Status, 'podio/areas/status'
156
- # autoload :Subscription, 'podio/areas/subscription'
157
- # autoload :Tag, 'podio/areas/tag'
158
- # autoload :Task, 'podio/areas/task'
159
- # autoload :TaskLabel, 'podio/areas/task'
160
- # autoload :User, 'podio/areas/user'
161
- # autoload :UserStatus, 'podio/areas/user_status'
162
- # autoload :Widget, 'podio/areas/widget'
163
139
  end
@@ -6,20 +6,39 @@ module ActivePodio
6
6
  extend ActiveModel::Naming, ActiveModel::Callbacks
7
7
  include ActiveModel::Conversion
8
8
 
9
- class_inheritable_accessor :valid_attributes
10
- attr_accessor :attributes, :error_code, :error_message, :error_parameters
9
+ class_inheritable_accessor :valid_attributes, :associations
10
+ attr_accessor :attributes, :error_code, :error_message, :error_parameters, :error_propagate
11
+ alias_method :propagate_error?, :error_propagate
11
12
 
12
- def initialize(attributes = {})
13
+ def initialize(attributes = {}, options = {})
13
14
  self.valid_attributes ||= []
14
15
  attributes ||= {}
15
16
  self.attributes = Hash[*self.valid_attributes.collect { |n| [n.to_sym, nil] }.flatten].merge(attributes.symbolize_keys)
17
+
18
+ @values_from_api = options[:values_from_api] # Used to determine if date times should be converted from local to utc, or are already utc
19
+
16
20
  attributes.each do |key, value|
17
21
  if self.respond_to?("#{key}=".to_sym)
18
- self.send("#{key}=".to_sym, value)
19
- elsif valid_attributes.include?(key.to_sym)
20
- self.send(:[]=, key.to_sym, value)
22
+ self.send("#{key}=".to_sym, value)
23
+ else
24
+ 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)
25
+ if valid_attributes.include?(key.to_sym) || is_association_hash
26
+ # Initialize nested object to get correctly casted values set back, unless the given values are all blank
27
+ if is_association_hash
28
+ attributes = self.send(key.to_sym).attributes
29
+ if any_values_present_recursive?(attributes.values)
30
+ value = attributes
31
+ else
32
+ value = nil
33
+ end
34
+ end
35
+ self.send(:[]=, key.to_sym, value)
36
+ end
21
37
  end
22
38
  end
39
+
40
+ @belongs_to = options[:belongs_to] # Allows has_one associations to communicate their changed content back to their parent model
41
+ @values_from_api = false
23
42
  end
24
43
 
25
44
  def persisted?
@@ -31,7 +50,9 @@ module ActivePodio
31
50
  end
32
51
 
33
52
  def to_param
34
- return self.id.try(:to_s) if self.respond_to?(:id)
53
+ local_id = self.id if self.respond_to?(:id)
54
+ local_id = nil if local_id == self.object_id # Id still returns object_id in Ruby 1.8.7, JRuby and Rubinius
55
+ local_id.try(:to_s)
35
56
  end
36
57
 
37
58
  def [](attribute)
@@ -42,10 +63,14 @@ module ActivePodio
42
63
  def []=(attribute, value)
43
64
  @attributes ||= {}
44
65
  @attributes[attribute.to_sym] = value
66
+ if @belongs_to.present? && value.present?
67
+ @belongs_to[:model][@belongs_to[:as]] ||= {}
68
+ @belongs_to[:model][@belongs_to[:as]][attribute.to_sym] = value
69
+ end
45
70
  end
46
71
 
47
72
  def ==(other)
48
- self.respond_to?(:id) && other.respond_to?(:id) && self.id == other.id
73
+ !self.nil? && !other.nil? && self.respond_to?(:id) && other.respond_to?(:id) && self.id == other.id
49
74
  end
50
75
  alias :eql? :==
51
76
 
@@ -70,19 +95,29 @@ module ActivePodio
70
95
  end
71
96
  return klass
72
97
  end
98
+
99
+ def any_values_present_recursive?(values)
100
+ values.any? do |value|
101
+ if value.respond_to?(:values)
102
+ any_values_present_recursive?(value.values)
103
+ else
104
+ value.present?
105
+ end
106
+ end
107
+ end
73
108
 
74
109
  class << self
75
110
 
76
111
  public
77
112
 
78
113
  # Defines the the supported attributes of the model
79
- def property(name, type = :string)
114
+ def property(name, type = :string, options = {})
80
115
  self.valid_attributes ||= []
81
116
  self.valid_attributes << name
82
117
 
83
118
  case type
84
119
  when :datetime
85
- define_datetime_accessor(name)
120
+ define_datetime_accessor(name, options)
86
121
  when :date
87
122
  define_date_accessor(name)
88
123
  when :integer
@@ -99,13 +134,16 @@ module ActivePodio
99
134
 
100
135
  # Wraps a single hash provided from the API in the given model
101
136
  def has_one(name, options = {})
137
+ self.associations ||= {}
138
+ self.associations[name] = :has_one
139
+
102
140
  self.send(:define_method, name) do
103
141
  klass = klass_for_association(options)
104
142
  instance = self.instance_variable_get("@#{name}_has_one_instance")
105
143
  unless instance.present?
106
144
  property = options[:property] || name.to_sym
107
145
  if self[property].present?
108
- instance = klass.new(self[property])
146
+ instance = klass.new(self[property], :belongs_to => { :model => self, :as => property })
109
147
  self.instance_variable_set("@#{name}_has_one_instance", instance)
110
148
  else
111
149
  instance = nil
@@ -117,6 +155,9 @@ module ActivePodio
117
155
 
118
156
  # Wraps a collection of hashes from the API to a collection of the given model
119
157
  def has_many(name, options = {})
158
+ self.associations ||= {}
159
+ self.associations[name] = :has_many
160
+
120
161
  self.send(:define_method, name) do
121
162
  klass = klass_for_association(options)
122
163
  instances = self.instance_variable_get("@#{name}_has_many_instances")
@@ -139,12 +180,12 @@ module ActivePodio
139
180
 
140
181
  # Returns a single instance of the model
141
182
  def member(response)
142
- new(response)
183
+ new(response, :values_from_api => true)
143
184
  end
144
185
 
145
186
  # Returns a simple collection model instances
146
187
  def list(response)
147
- response.map! { |item| new(item) }
188
+ response.map! { |item| new(item, :values_from_api => true) }
148
189
  response
149
190
  end
150
191
 
@@ -154,21 +195,24 @@ module ActivePodio
154
195
  # * total_count: The total number of records matching the given conditions
155
196
  def collection(response)
156
197
  result = Struct.new(:all, :count, :total_count).new(response['items'], response['filtered'], response['total'])
157
- result.all.map! { |item| new(item) }
198
+ result.all.map! { |item| new(item, :values_from_api => true) }
158
199
  result
159
200
  end
160
201
 
161
202
  def delegate_to_hash(hash_name, *attribute_names)
162
- options = attribute_names.extract_options! || { :prefix => false, :setter => false }
203
+ options = attribute_names.extract_options!
204
+ options.reverse_merge!(:prefix => false, :setter => false)
163
205
  options.assert_valid_keys(:prefix, :setter)
164
206
  attribute_names.each do |attribute_name|
165
207
  hash_index = attribute_name.to_s.gsub(/[\?!]/, '')
166
208
  method_name = "#{options[:prefix] ? "#{hash_name}_" : ''}#{attribute_name}"
167
209
  self.send(:define_method, method_name) do
210
+ self.send("#{hash_name}=", {}) unless self.send(hash_name)
168
211
  self.send(hash_name)[hash_index]
169
212
  end
170
213
  if options[:setter]
171
214
  self.send(:define_method, "#{method_name}=") do |value|
215
+ self.send("#{hash_name}=", {}) unless self.send(hash_name)
172
216
  self.send(hash_name)[hash_index] = value
173
217
  end
174
218
  end
@@ -177,7 +221,7 @@ module ActivePodio
177
221
 
178
222
  # Wraps the given methods in a begin/rescue block
179
223
  # If no error occurs, the return value of the method, or true if nil is returned, is returned
180
- # If a Podio::BadRequestError occurs, the method returns false and the error can be read from the error_message accessor
224
+ # If a Podio::PodioError occurs, the method returns false and the error can be read from the error_message accessor
181
225
  # If another error occurs, it is still raised
182
226
  def handle_api_errors_for(*method_names)
183
227
  method_names.each do |method_name|
@@ -186,11 +230,12 @@ module ActivePodio
186
230
  begin
187
231
  result = self.send("#{method_name}_without_api_errors_handled", *args)
188
232
  success = true
189
- rescue Podio::BadRequestError, Podio::AuthorizationError => ex
233
+ rescue Podio::PodioError => ex
190
234
  success = false
191
235
  code = ex.response_body["error"]
192
236
  message = ex.response_body["error_description"]
193
237
  parameters = ex.response_body["error_parameters"]
238
+ propagate = ex.response_body["error_propagate"]
194
239
  end
195
240
 
196
241
  if success
@@ -198,7 +243,8 @@ module ActivePodio
198
243
  else
199
244
  @error_code = code
200
245
  @error_message = message
201
- @error_parameters = parameters
246
+ @error_parameters = parameters || {}
247
+ @error_propagate = propagate
202
248
  return false
203
249
  end
204
250
  end
@@ -225,12 +271,20 @@ module ActivePodio
225
271
  end
226
272
  end
227
273
 
228
- def define_datetime_accessor(name)
274
+ def define_datetime_accessor(name, options = {})
229
275
  self.send(:define_method, name) do
230
- self[name.to_sym].try(:to_datetime).try(:in_time_zone)
276
+ options[:convert_timezone] == false ? self[name.to_sym].try(:to_datetime) : self[name.to_sym].try(:to_datetime).try(:in_time_zone)
231
277
  end
232
278
 
233
279
  self.send(:define_method, "#{name}=") do |value|
280
+
281
+ # TODO: This should eventually be done on all date times
282
+ # This option is a temporary fix while API transitions to UTC only
283
+ if options[:convert_incoming_local_datetime_to_utc] && value.present? && !@values_from_api
284
+ value = value.try(:to_datetime) unless value.is_a?(DateTime)
285
+ value = Time.zone.local_to_utc(value)
286
+ end
287
+
234
288
  self[name.to_sym] = if value.is_a?(DateTime)
235
289
  value.try(:to_s, :db)
236
290
  else
@@ -248,7 +302,13 @@ module ActivePodio
248
302
  self[name.to_sym] = if value.is_a?(Date)
249
303
  value.try(:to_s, :db)
250
304
  else
251
- value.try(:to_s)
305
+ value = value.try(:to_s)
306
+ if defined?(I18n) && value.present? && !(value =~ /^\d{4}-\d{2}-\d{2}$/) # If we have I18n available, assume that we are in Rails and try to convert the string to a date to convert it to ISO 8601
307
+ value_as_date = Date.strptime(value, I18n.t('date.formats.default')) rescue nil
308
+ value_as_date.nil? ? value.try(:to_s) : value_as_date.try(:to_s, :db)
309
+ else
310
+ value
311
+ end
252
312
  end
253
313
  end
254
314
  end
@@ -298,7 +358,7 @@ module ActivePodio
298
358
  end
299
359
 
300
360
  self.send(:define_method, "#{name}=") do |array|
301
- self[name.to_sym] = array.reject(&:blank?) if array.respond_to?(:reject)
361
+ self[name.to_sym] = array.respond_to?(:reject) ? array.reject(&:blank?) : array
302
362
  end
303
363
  end
304
364
 
@@ -307,7 +367,7 @@ module ActivePodio
307
367
  end
308
368
 
309
369
  self.send(:define_method, "#{name.to_s.pluralize}=") do |array|
310
- self[name.to_sym] = array.reject(&:blank?) if array.respond_to?(:reject)
370
+ self[name.to_sym] = array.respond_to?(:reject) ? array.reject(&:blank?) : array
311
371
  end
312
372
 
313
373
  self.send(:define_method, "default_#{name.to_s.singularize}") do
@@ -72,6 +72,17 @@ module Podio
72
72
  @oauth_token
73
73
  end
74
74
 
75
+ # Sign in with an OpenID, only available for Podio
76
+ def authenticate_with_openid(identifier)
77
+ response = @oauth_connection.post do |req|
78
+ req.url '/oauth/token_by_openid', :grant_type => 'openid', :client_id => api_key, :client_secret => api_secret, :identifier => identifier
79
+ end
80
+
81
+ @oauth_token = OAuthToken.new(response.body)
82
+ configure_oauth
83
+ @oauth_token
84
+ end
85
+
75
86
  def oauth_token=(new_oauth_token)
76
87
  @oauth_token = new_oauth_token.is_a?(Hash) ? OAuthToken.new(new_oauth_token) : new_oauth_token
77
88
  configure_oauth
@@ -91,7 +102,7 @@ module Podio
91
102
  headers = @headers.dup
92
103
  headers['User-Agent'] = 'Podio Ruby Library'
93
104
  headers['authorization'] = "OAuth2 #{oauth_token.access_token}" if oauth_token
94
- headers['X-Podio-Dry-Run'] = '1' if @test_mode
105
+ headers['X-Podio-Dry-Run'] = @test_mode if @test_mode
95
106
 
96
107
  headers
97
108
  end
@@ -114,9 +125,9 @@ module Podio
114
125
  builder.adapter(*default_adapter)
115
126
 
116
127
  # first response middleware defined get's executed last
117
- builder.use Middleware::DateConversion
128
+ # builder.use Middleware::DateConversion
118
129
  builder.use Middleware::ErrorResponse
119
- builder.use Middleware::JsonResponse unless raw
130
+ builder.use Middleware::JsonResponse
120
131
  builder.use Middleware::ResponseRecorder if @record_mode
121
132
  end
122
133
  end
@@ -0,0 +1,15 @@
1
+ class Podio::Action < ActivePodio::Base
2
+ property :action_id, :integer
3
+ property :type, :string
4
+ property :data, :hash
5
+ property :text, :string
6
+
7
+ alias_method :id, :action_id
8
+
9
+ class << self
10
+ def find(id)
11
+ member Podio.connection.get("/action/#{id}").body
12
+ end
13
+ end
14
+
15
+ end