podio 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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