tacokit 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +86 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +16 -25
  5. data/Guardfile +1 -1
  6. data/README.md +61 -17
  7. data/Rakefile +10 -5
  8. data/lib/tacokit/authorization.rb +14 -13
  9. data/lib/tacokit/client/actions.rb +68 -28
  10. data/lib/tacokit/client/boards.rb +92 -85
  11. data/lib/tacokit/client/cards.rb +285 -92
  12. data/lib/tacokit/client/checklists.rb +48 -28
  13. data/lib/tacokit/client/labels.rb +14 -16
  14. data/lib/tacokit/client/lists.rb +52 -28
  15. data/lib/tacokit/client/members.rb +56 -46
  16. data/lib/tacokit/client/notifications.rb +13 -27
  17. data/lib/tacokit/client/organizations.rb +0 -6
  18. data/lib/tacokit/client/searches.rb +0 -1
  19. data/lib/tacokit/client/tokens.rb +0 -6
  20. data/lib/tacokit/client/types.rb +0 -1
  21. data/lib/tacokit/client/webhooks.rb +3 -8
  22. data/lib/tacokit/client.rb +48 -75
  23. data/lib/tacokit/collection.rb +49 -0
  24. data/lib/tacokit/configuration.rb +4 -6
  25. data/lib/tacokit/error.rb +0 -2
  26. data/lib/tacokit/middleware/boom.rb +6 -7
  27. data/lib/tacokit/middleware.rb +2 -10
  28. data/lib/tacokit/resource.rb +84 -57
  29. data/lib/tacokit/response.rb +3 -3
  30. data/lib/tacokit/transform.rb +35 -13
  31. data/lib/tacokit/utils.rb +40 -8
  32. data/lib/tacokit/version.rb +1 -1
  33. data/lib/tacokit.rb +0 -2
  34. data/spec/cassettes/Tacokit_Client_Actions/_action_board/returns_action_board.json +1 -0
  35. data/spec/cassettes/Tacokit_Client_Actions/_action_card/returns_action_card.json +1 -0
  36. data/spec/cassettes/Tacokit_Client_Actions/_action_entities/returns_action_entities.json +1 -0
  37. data/spec/cassettes/Tacokit_Client_Actions/_action_list/returns_action_list.json +1 -0
  38. data/spec/cassettes/Tacokit_Client_Boards/_add_board_member/adds_invites_member_to_board.json +1 -1
  39. data/spec/cassettes/Tacokit_Client_Boards/_board_actions/returns_board_actions.json +1 -0
  40. data/spec/cassettes/Tacokit_Client_Boards/_board_cards/returns_board_cards.json +1 -0
  41. data/spec/cassettes/Tacokit_Client_Boards/_board_checklists/returns_board_checklists.json +1 -0
  42. data/spec/cassettes/Tacokit_Client_Boards/_board_labels/returns_board_labels.json +1 -0
  43. data/spec/cassettes/Tacokit_Client_Boards/_board_lists/returns_board_lists.json +1 -0
  44. data/spec/cassettes/Tacokit_Client_Boards/_board_members/returns_board_members.json +1 -0
  45. data/spec/cassettes/Tacokit_Client_Boards/_board_organization/returns_board_organization.json +1 -0
  46. data/spec/cassettes/Tacokit_Client_Boards/_board_preferences/returns_preferences_for_a_board.json +1 -0
  47. data/spec/cassettes/Tacokit_Client_Boards/_board_stars/returns_board_stars.json +1 -0
  48. data/spec/cassettes/Tacokit_Client_Boards/_member_board_stars/returns_board_stars.json +1 -0
  49. data/spec/cassettes/Tacokit_Client_Boards/_update_board_member/updates_member.json +1 -0
  50. data/spec/cassettes/Tacokit_Client_Cards/_add_comment/creates_a_comment.json +1 -0
  51. data/spec/cassettes/Tacokit_Client_Cards/_add_label/adds_label_to_card.json +1 -0
  52. data/spec/cassettes/Tacokit_Client_Cards/_add_member_to_card/adds_member_to_card.json +1 -0
  53. data/spec/cassettes/Tacokit_Client_Cards/_archive_card/archives_card.json +1 -0
  54. data/spec/cassettes/Tacokit_Client_Cards/_archive_card/restore_card.json +1 -0
  55. data/spec/cassettes/Tacokit_Client_Cards/_attachments/returns_an_attachment.json +1 -0
  56. data/spec/cassettes/Tacokit_Client_Cards/_attachments/returns_attachment.json +1 -0
  57. data/spec/cassettes/Tacokit_Client_Cards/_attachments/returns_attachments.json +1 -0
  58. data/spec/cassettes/Tacokit_Client_Cards/_card/returns_a_card_by_card_resource.json +1 -0
  59. data/spec/cassettes/Tacokit_Client_Cards/_card/returns_a_card_by_id.json +1 -1
  60. data/spec/cassettes/Tacokit_Client_Cards/_card/returns_a_card_by_short_link.json +1 -1
  61. data/spec/cassettes/Tacokit_Client_Cards/_card_actions/returns_card_actions.json +1 -0
  62. data/spec/cassettes/Tacokit_Client_Cards/_card_board/returns_card_board.json +1 -0
  63. data/spec/cassettes/Tacokit_Client_Cards/_card_field/returns_a_hash.json +1 -1
  64. data/spec/cassettes/Tacokit_Client_Cards/_card_field/returns_a_value.json +1 -1
  65. data/spec/cassettes/Tacokit_Client_Cards/_card_field/returns_an_array.json +1 -1
  66. data/spec/cassettes/Tacokit_Client_Cards/_card_list/returns_a_list.json +1 -0
  67. data/spec/cassettes/Tacokit_Client_Cards/_card_members/returns_members.json +1 -0
  68. data/spec/cassettes/Tacokit_Client_Cards/_card_members/returns_members_voted.json +1 -0
  69. data/spec/cassettes/Tacokit_Client_Cards/_card_resource/returns_card_actions.json +1 -1
  70. data/spec/cassettes/Tacokit_Client_Cards/_card_resource/returns_card_board.json +1 -1
  71. data/spec/cassettes/Tacokit_Client_Cards/_card_resource/returns_card_check_item_states.json +1 -1
  72. data/spec/cassettes/Tacokit_Client_Cards/_card_stickers/returns_.json +1 -0
  73. data/spec/cassettes/Tacokit_Client_Cards/_checklists/returns_check_item_states.json +1 -0
  74. data/spec/cassettes/Tacokit_Client_Cards/_checklists/returns_checklists.json +1 -0
  75. data/spec/cassettes/Tacokit_Client_Cards/_create_card/creates_a_card.json +1 -1
  76. data/spec/cassettes/Tacokit_Client_Cards/_create_card_attachment/attaches_external_url.json +1 -1
  77. data/spec/cassettes/Tacokit_Client_Cards/_create_card_attachment/uploads_local_file.json +1 -1
  78. data/spec/cassettes/Tacokit_Client_Cards/_delete_card/deletes_a_card.json +1 -1
  79. data/spec/cassettes/Tacokit_Client_Cards/_delete_card_resource/deletes_resource.json +1 -1
  80. data/spec/cassettes/Tacokit_Client_Cards/_delete_comment/deletes_comment.json +1 -0
  81. data/spec/cassettes/Tacokit_Client_Cards/_list/returns_list.json +1 -0
  82. data/spec/cassettes/Tacokit_Client_Cards/_move_card/moves_card_to_a_new_position.json +1 -0
  83. data/spec/cassettes/Tacokit_Client_Cards/_move_card/moves_card_to_another_board_list.json +1 -0
  84. data/spec/cassettes/Tacokit_Client_Cards/_move_card/moves_card_to_board.json +1 -0
  85. data/spec/cassettes/Tacokit_Client_Cards/_move_card/moves_card_to_list.json +1 -0
  86. data/spec/cassettes/Tacokit_Client_Cards/_move_card/raises_error_if_missing_options.json +1 -0
  87. data/spec/cassettes/Tacokit_Client_Cards/_start_checklist/adds_a_checklist_item.json +1 -0
  88. data/spec/cassettes/Tacokit_Client_Cards/_start_checklist/adds_new_checklist_by_name.json +1 -0
  89. data/spec/cassettes/Tacokit_Client_Cards/_start_checklist/copies_a_checklist_by_id.json +1 -0
  90. data/spec/cassettes/Tacokit_Client_Cards/_stickers/returns_stickers.json +1 -0
  91. data/spec/cassettes/Tacokit_Client_Cards/_update_card/updates_a_card.json +1 -1
  92. data/spec/cassettes/Tacokit_Client_Cards/_update_checklist_item/updates_checklist_item.json +1 -0
  93. data/spec/cassettes/Tacokit_Client_Cards/_update_comment/updates_a_comment.json +1 -0
  94. data/spec/cassettes/Tacokit_Client_Cards/_vote/adds_a_member_s_vote_to_card.json +1 -0
  95. data/spec/cassettes/Tacokit_Client_Checklists/_check_items/adds_a_checklist_check_item.json +1 -0
  96. data/spec/cassettes/Tacokit_Client_Checklists/_check_items/retrieves_checklist_check_items.json +1 -0
  97. data/spec/cassettes/Tacokit_Client_Lists/_archive_list_cards/should_archive_cards_in_list.json +1 -0
  98. data/spec/cassettes/Tacokit_Client_Lists/_list_actions/returns_list_actions.json +1 -0
  99. data/spec/cassettes/Tacokit_Client_Lists/_list_board/returns_list_board.json +1 -0
  100. data/spec/cassettes/Tacokit_Client_Lists/_list_cards/returns_list_cards.json +1 -0
  101. data/spec/cassettes/Tacokit_Client_Lists/_move_list_cards/should_move_cards_in_list.json +1 -0
  102. data/spec/cassettes/Tacokit_Client_Members/_actions/returns_member_actions.json +1 -0
  103. data/spec/cassettes/Tacokit_Client_Members/_board_stars/returns_member_board_stars.json +1 -0
  104. data/spec/cassettes/Tacokit_Client_Members/_boards/returns_member_boards.json +1 -0
  105. data/spec/cassettes/Tacokit_Client_Members/_cards/returns_member_cards.json +1 -0
  106. data/spec/cassettes/Tacokit_Client_Members/_notifications/returns_member_notifications.json +1 -0
  107. data/spec/cassettes/Tacokit_Client_Members/_organizations/returns_member_organizations.json +1 -0
  108. data/spec/cassettes/Tacokit_Client_Members/_tokens/returns_member_organizations.json +1 -0
  109. data/spec/cassettes/Tacokit_Client_Members/_tokens/returns_member_tokens.json +1 -0
  110. data/spec/spec_helper.rb +39 -29
  111. data/spec/tacokit/authorization_spec.rb +11 -11
  112. data/spec/tacokit/client/actions_spec.rb +56 -19
  113. data/spec/tacokit/client/boards_spec.rb +81 -78
  114. data/spec/tacokit/client/cards_spec.rb +322 -68
  115. data/spec/tacokit/client/checklists_spec.rb +29 -27
  116. data/spec/tacokit/client/labels_spec.rb +8 -16
  117. data/spec/tacokit/client/lists_spec.rb +74 -27
  118. data/spec/tacokit/client/members_spec.rb +55 -41
  119. data/spec/tacokit/client/notifications_spec.rb +3 -31
  120. data/spec/tacokit/client/organizations_spec.rb +6 -27
  121. data/spec/tacokit/client/searches_spec.rb +1 -3
  122. data/spec/tacokit/client/tokens_spec.rb +2 -18
  123. data/spec/tacokit/client/types_spec.rb +2 -3
  124. data/spec/tacokit/client/webhooks_spec.rb +6 -14
  125. data/spec/tacokit/client_spec.rb +3 -3
  126. data/spec/tacokit/configuration_spec.rb +30 -31
  127. data/spec/tacokit/middleware/boom_spec.rb +6 -7
  128. data/spec/tacokit/resource_spec.rb +142 -17
  129. data/spec/tacokit/transform_spec.rb +37 -35
  130. data/spec/tacokit_spec.rb +3 -5
  131. data/tacokit.gemspec +6 -6
  132. metadata +132 -15
  133. data/config.ru +0 -7
  134. data/lib/tacokit/middleware/debug.rb +0 -22
  135. data/spec/cassettes/Tacokit_Client_Boards/_update_board_resource/updates_member.json +0 -1
  136. data/spec/cassettes/Tacokit_Client_Members/_member/raises_error_for_missing_token.json +0 -1
@@ -1,27 +1,30 @@
1
- require 'forwardable'
2
-
3
- require 'faraday'
4
- require 'faraday_middleware'
5
-
6
- require 'tacokit/authorization'
7
- require 'tacokit/configuration'
8
- require 'tacokit/middleware'
9
- require 'tacokit/response'
10
- require 'tacokit/transform'
11
-
12
- require 'tacokit/client/actions'
13
- require 'tacokit/client/boards'
14
- require 'tacokit/client/cards'
15
- require 'tacokit/client/checklists'
16
- require 'tacokit/client/labels'
17
- require 'tacokit/client/lists'
18
- require 'tacokit/client/members'
19
- require 'tacokit/client/notifications'
20
- require 'tacokit/client/organizations'
21
- require 'tacokit/client/searches'
22
- require 'tacokit/client/tokens'
23
- require 'tacokit/client/types'
24
- require 'tacokit/client/webhooks'
1
+ require "forwardable"
2
+
3
+ require "addressable/uri"
4
+
5
+ require "faraday"
6
+ require "faraday_middleware"
7
+
8
+ require "tacokit/authorization"
9
+ require "tacokit/collection"
10
+ require "tacokit/configuration"
11
+ require "tacokit/middleware"
12
+ require "tacokit/response"
13
+ require "tacokit/transform"
14
+
15
+ require "tacokit/client/actions"
16
+ require "tacokit/client/boards"
17
+ require "tacokit/client/cards"
18
+ require "tacokit/client/checklists"
19
+ require "tacokit/client/labels"
20
+ require "tacokit/client/lists"
21
+ require "tacokit/client/members"
22
+ require "tacokit/client/notifications"
23
+ require "tacokit/client/organizations"
24
+ require "tacokit/client/searches"
25
+ require "tacokit/client/tokens"
26
+ require "tacokit/client/types"
27
+ require "tacokit/client/webhooks"
25
28
 
26
29
  module Tacokit
27
30
  class Client
@@ -47,10 +50,12 @@ module Tacokit
47
50
  def_delegators :configuration, *Configuration.keys
48
51
  def_delegators :configuration, :user_authenticated?, :user_credentials
49
52
  def_delegators :configuration, :app_authenticated?, :app_credentials
50
- def_delegators :transform, :serialize, :deserialize
53
+ def_delegators :transform, :serialize, :deserialize, :serialize_params
54
+
55
+ attr_accessor :last_response
51
56
 
52
57
  def initialize(options = {})
53
- self.configuration.options = options
58
+ configuration.options = options
54
59
  end
55
60
 
56
61
  def reset!
@@ -81,61 +86,24 @@ module Tacokit
81
86
  request :delete, url, options
82
87
  end
83
88
 
89
+ def paginated_get(*args)
90
+ Collection.new(self, :get, *args)
91
+ end
92
+
84
93
  def request(method, url, data = nil, params = nil)
85
94
  if [:get, :body].include?(method)
86
95
  params ||= data
87
96
  data = nil
88
97
  end
89
98
 
90
- params = normalize_request_params(params || {})
91
99
  response = connection.send method, url do |req|
92
- req.params.update params
100
+ req.params.update serialize_params(params)
93
101
  req.body = serialize(data) if data
94
102
  end
95
103
 
96
- Response.new(self, response).data
97
- end
98
-
99
- def transform
100
- @transform ||= Transform.new
101
- end
102
-
103
- # Prepare ruby-style params for trello request
104
- def normalize_request_params(params)
105
- {}.tap do |norm|
106
- params.each do |key,value|
107
- norm[key] = normalize_param_value(value)
108
- end
109
- end
110
- end
104
+ @last_response = last_response = Response.new(self, response)
111
105
 
112
- def normalize_param_value(value)
113
- case value
114
- when Array
115
- value.map { |v| camp(v) }.join(',')
116
- when /\,/
117
- normalize_param_value(value.split(','))
118
- else
119
- camp(value)
120
- end
121
- end
122
-
123
- def path_join(*paths)
124
- paths.join('/')
125
- end
126
-
127
- def to_path(*paths)
128
- warn "to_path is deprecated"
129
- path_join(*paths)
130
- end
131
-
132
- def camel_path(path)
133
- camelize(path.to_s, :lower)
134
- end
135
- alias camp camel_path
136
-
137
- def camel_join(*paths)
138
- path_join paths.map { |p| camel_path(p) }
106
+ last_response.data
139
107
  end
140
108
 
141
109
  def to_s
@@ -143,13 +111,19 @@ module Tacokit
143
111
  end
144
112
  alias_method :inspect, :to_s
145
113
 
114
+ private
115
+
116
+ def transform
117
+ @transform ||= Transform.new
118
+ end
119
+
146
120
  def connection
147
- @connection = Faraday.new(url: api_endpoint) do |http|
148
- http.headers[:user_agent] = 'TacoKit 0.0.1'
121
+ @connection ||= Faraday.new(url: api_endpoint) do |http|
122
+ http.headers[:user_agent] = "TacoKit 0.0.1"
149
123
 
150
124
  if user_authenticated?
151
125
  http.request :oauth, user_credentials
152
- else app_authenticated?
126
+ elsif app_authenticated?
153
127
  http.params.update app_credentials
154
128
  end
155
129
 
@@ -159,11 +133,10 @@ module Tacokit
159
133
 
160
134
  http.response :json, content_type: /\bjson$/
161
135
  http.response :boom
162
- http.response :logger if ENV['DEBUG']
136
+ http.response :logger if ENV["DEBUG"]
163
137
 
164
138
  http.adapter Faraday.default_adapter
165
139
  end
166
140
  end
167
-
168
141
  end
169
142
  end
@@ -0,0 +1,49 @@
1
+ require "forwardable"
2
+
3
+ module Tacokit
4
+ class Collection
5
+ include Enumerable
6
+ extend Forwardable
7
+
8
+ MAX = 1000
9
+
10
+ def_delegators :@collection, :[], :empty?, :size
11
+
12
+ def initialize(client, method, path, options)
13
+ @client, @method, @path, @options = client, method, path, options
14
+
15
+ @page = options.fetch(:page, 0)
16
+ @limit = options.fetch(:limit, 50)
17
+
18
+ @collection = []
19
+
20
+ fetch_next_page
21
+ end
22
+
23
+ def each(start = 0)
24
+ return to_enum(:each, start) unless block_given?
25
+ Array(@collection[start..-1]).each do |element|
26
+ yield(element)
27
+ end
28
+ unless last?
29
+ start = [@collection.size, start].max
30
+ fetch_next_page
31
+ each(start, &Proc.new)
32
+ end
33
+ self
34
+ end
35
+
36
+ private
37
+
38
+ def last?
39
+ @last_response_empty || (@page * @limit) >= MAX
40
+ end
41
+
42
+ def fetch_next_page
43
+ response = @client.send(@method, @path, @options.merge(page: @page))
44
+ @last_response_empty = response.empty?
45
+ @page += 1
46
+ @collection += response
47
+ end
48
+ end
49
+ end
@@ -1,6 +1,5 @@
1
1
  module Tacokit
2
2
  class Configuration
3
-
4
3
  API_URL = "https://api.trello.com".freeze
5
4
  WEB_URL = "https://trello.com".freeze
6
5
 
@@ -41,7 +40,7 @@ module Tacokit
41
40
  end
42
41
 
43
42
  def app_credentials
44
- { key: app_key, token: app_token }.delete_if { |k,v| v.nil? }
43
+ { key: app_key, token: app_token }.delete_if { |k, v| v.nil? }
45
44
  end
46
45
 
47
46
  private
@@ -50,11 +49,10 @@ module Tacokit
50
49
  {
51
50
  api_endpoint: File.join(API_URL, API_VERSION),
52
51
  web_endpoint: File.join(WEB_URL, API_VERSION),
53
- app_key: ENV['TRELLO_APP_KEY'],
54
- app_secret: ENV['TRELLO_APP_SECRET'],
55
- app_token: ENV['TRELLO_APP_TOKEN']
52
+ app_key: ENV["TRELLO_APP_KEY"],
53
+ app_secret: ENV["TRELLO_APP_SECRET"],
54
+ app_token: ENV["TRELLO_APP_TOKEN"]
56
55
  }
57
56
  end
58
-
59
57
  end
60
58
  end
data/lib/tacokit/error.rb CHANGED
@@ -25,7 +25,5 @@ module Tacokit
25
25
 
26
26
  # 407
27
27
  ConnectionFailed = Class.new(ClientError)
28
-
29
28
  end
30
-
31
29
  end
@@ -1,26 +1,25 @@
1
1
  module Tacokit
2
2
  module Middleware
3
3
  class Boom < Faraday::Response::Middleware
4
- ClientErrorStatuses = 400...600
4
+ CLIENT_ERROR_STATUSES = 400...600
5
5
 
6
6
  def on_complete(env)
7
7
  case env[:status]
8
8
  when 401
9
- raise Tacokit::Error::Unauthorized.new(error_message(env))
9
+ raise Tacokit::Error::Unauthorized, error_message(env)
10
10
  when 404
11
- raise Tacokit::Error::ResourceNotFound.new(error_message(env))
11
+ raise Tacokit::Error::ResourceNotFound, error_message(env)
12
12
  when 407
13
13
  # mimic the behavior that we get with proxy requests with HTTPS
14
- raise Tacokit::Error::ConnectionFailed, %{407 "Proxy Authentication Required "}
15
- when ClientErrorStatuses
16
- raise Tacokit::Error::ClientError.new(error_message(env))
14
+ raise Tacokit::Error::ConnectionFailed, %(407 "Proxy Authentication Required ")
15
+ when CLIENT_ERROR_STATUSES
16
+ raise Tacokit::Error::ClientError, error_message(env)
17
17
  end
18
18
  end
19
19
 
20
20
  def error_message(env)
21
21
  "Server returned #{env[:status]}: #{env.body}. Headers #{env.response_headers.inspect}"
22
22
  end
23
-
24
23
  end
25
24
  end
26
25
  end
@@ -1,15 +1,7 @@
1
- require 'tacokit/middleware/boom'
2
- # require 'tacokit/middleware/debug'
1
+ require "tacokit/middleware/boom"
3
2
 
4
3
  module Tacokit
5
4
  module Middleware
6
-
7
- # Faraday::Request.register_middleware \
8
- # :example => lambda { Tacokit::Middleware::Example }
9
- #
10
-
11
- Faraday::Response.register_middleware \
12
- :boom => lambda { Tacokit::Middleware::Boom }
13
-
5
+ Faraday::Response.register_middleware boom: -> { Tacokit::Middleware::Boom }
14
6
  end
15
7
  end
@@ -1,36 +1,25 @@
1
- require 'forwardable'
1
+ require "forwardable"
2
2
 
3
3
  module Tacokit
4
4
  class Resource
5
- SPECIAL_METHODS = Set.new(%w(client fields))
6
- attr_reader :_client
7
- attr_reader :_fields
8
- attr_reader :attrs
9
- alias to_hash attrs
10
- alias to_h attrs
11
5
  include Enumerable
12
6
  extend Forwardable
13
7
 
14
- def_delegators :@_fields, :fetch, :keys, :key?, :has_key?, :include?, :any?
8
+ SPECIAL_METHODS = Set.new(%w[fields])
9
+ attr_reader :_fields
10
+ attr_reader :attrs
11
+ alias_method :to_hash, :attrs
12
+ alias_method :to_h, :attrs
13
+
14
+ def_delegators :@_fields, :fetch, :keys, :any?
15
15
 
16
- def initialize(client, data = {})
17
- @_client = client
16
+ def initialize(data = {})
18
17
  @attrs = {}
19
- @_metaclass = (class << self; self; end)
20
18
  @_fields = Set.new
21
19
  data.each do |key, value|
22
- @_fields << key
23
20
  @attrs[key.to_sym] = process_value(value)
24
21
  end
25
- @_metaclass.send(:attr_accessor, *data.keys)
26
- end
27
-
28
- def process_value(value)
29
- case value
30
- when Hash then self.class.new(@_client, value)
31
- when Array then value.map { |v| process_value(v) }
32
- else value
33
- end
22
+ new_attrs(*data.keys)
34
23
  end
35
24
 
36
25
  def each(&block)
@@ -49,42 +38,55 @@ module Tacokit
49
38
  nil
50
39
  end
51
40
 
52
- def self.attr_accessor(*attrs)
53
- attrs.each do |attribute|
54
- class_eval do
55
- define_method attribute do
56
- @attrs[attribute.to_sym]
57
- end
41
+ def key?(key)
42
+ @_fields.include?(key)
43
+ end
44
+ alias_method :has_key?, :key?
45
+ alias_method :include?, :key?
46
+
47
+ def inspect
48
+ (to_attrs.respond_to?(:pretty_inspect) ? to_attrs.pretty_inspect : to_attrs.inspect)
49
+ end
58
50
 
59
- define_method "#{attribute}=" do |value|
60
- @attrs[attribute.to_sym] = value
61
- end
51
+ alias_method :to_s, :inspect
62
52
 
63
- define_method "#{attribute}?" do
64
- !!@attrs[attribute.to_sym]
65
- end
53
+ def to_attrs
54
+ hash = attrs.clone
55
+ hash.keys.each do |k|
56
+ if hash[k].is_a?(Resource)
57
+ hash[k] = hash[k].to_attrs
58
+ elsif hash[k].is_a?(Array) && hash[k].all? { |el| el.is_a?(Resource) }
59
+ hash[k] = hash[k].collect(&:to_attrs)
66
60
  end
67
61
  end
62
+ hash
63
+ end
64
+
65
+ def update(attributes)
66
+ attributes.each do |key, value|
67
+ send("#{key}=", value)
68
+ end
68
69
  end
69
70
 
70
- ATTR_SETTER = '='.freeze
71
- ATTR_PREDICATE = '?'.freeze
71
+ private
72
+
73
+ def process_value(value)
74
+ case value
75
+ when Hash then self.class.new(value)
76
+ when Array then value.map { |v| process_value(v) }
77
+ else cast_value_type(value)
78
+ end
79
+ end
80
+
81
+ ATTR_SETTER = "=".freeze
82
+ ATTR_PREDICATE = "?".freeze
72
83
 
73
- # Provides access to a resource's attributes.
74
84
  def method_missing(method, *args)
75
85
  attr_name, suffix = method.to_s.scan(/([a-z0-9\_]+)(\?|\=)?$/i).first
76
86
  if suffix == ATTR_SETTER
77
- @_metaclass.send(:attr_accessor, attr_name)
78
- @_fields << attr_name.to_sym
79
- send(method, args.first)
87
+ setter_missing(attr_name, args.first)
80
88
  elsif attr_name && @_fields.include?(attr_name.to_sym)
81
- value = @attrs[attr_name.to_sym]
82
- case suffix
83
- when nil
84
- @_metaclass.send(:attr_accessor, attr_name)
85
- value
86
- when ATTR_PREDICATE then !!value
87
- end
89
+ getter_missing(attr_name, suffix)
88
90
  elsif suffix.nil? && SPECIAL_METHODS.include?(attr_name)
89
91
  instance_variable_get "@_#{attr_name}"
90
92
  elsif attr_name && !@_fields.include?(attr_name.to_sym)
@@ -94,21 +96,46 @@ module Tacokit
94
96
  end
95
97
  end
96
98
 
97
- def inspect
98
- to_attrs.respond_to?(:pretty_inspect) ? to_attrs.pretty_inspect : to_attrs.inspect
99
+ def new_attrs(*names)
100
+ names.map { |n| new_attr(n) }
99
101
  end
100
102
 
101
- def to_attrs
102
- hash = self.attrs.clone
103
- hash.keys.each do |k|
104
- if hash[k].is_a?(Tacokit::Resource)
105
- hash[k] = hash[k].to_attrs
106
- elsif hash[k].is_a?(Array) && hash[k].all?{|el| el.is_a?(Tacokit::Resource)}
107
- hash[k] = hash[k].collect{|el| el.to_attrs}
108
- end
103
+ def new_attr(name)
104
+ name = name.to_sym
105
+ @_fields << name
106
+ unless respond_to?(name)
107
+ define_singleton_method(name) { @attrs[name] }
108
+ define_singleton_method("#{name}=") { |v| @attrs[name] = v }
109
+ define_singleton_method("#{name}?") { !!@attrs[name] }
109
110
  end
110
- hash
111
+ name
112
+ end
113
+
114
+ def setter_missing(attr_name, value)
115
+ new_attr(attr_name)
116
+ send("#{attr_name}=", value)
111
117
  end
112
118
 
119
+ def getter_missing(attr_name, suffix)
120
+ value = @attrs[attr_name.to_sym]
121
+ case suffix
122
+ when nil
123
+ new_attr(attr_name)
124
+ value
125
+ when ATTR_PREDICATE then !!value
126
+ end
127
+ end
128
+
129
+ # rubocop:disable Metrics/LineLength
130
+ ISO8601 = %r{^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$}.freeze
131
+ # rubocop:enable Metrics/LineLength
132
+ def cast_value_type(value)
133
+ case value
134
+ when ISO8601 then DateTime.parse(value)
135
+ else value
136
+ end
137
+ rescue
138
+ value
139
+ end
113
140
  end
114
141
  end
@@ -1,6 +1,5 @@
1
1
  module Tacokit
2
2
  class Response
3
-
4
3
  attr_reader :client, :status, :headers, :env, :data
5
4
 
6
5
  def initialize(client, res, options = {})
@@ -11,13 +10,14 @@ module Tacokit
11
10
  @data = process_data(@client.deserialize(res.body))
12
11
  end
13
12
 
13
+ private
14
+
14
15
  def process_data(data)
15
16
  case data
16
- when Hash then Resource.new(client, data)
17
+ when Hash then Resource.new(data)
17
18
  when Array then data.map { |value| process_data(value) }
18
19
  else data
19
20
  end
20
21
  end
21
-
22
22
  end
23
23
  end
@@ -2,6 +2,10 @@ module Tacokit
2
2
  class Transform
3
3
  include Tacokit::Utils
4
4
 
5
+ def serialize_params(params)
6
+ normalize_request_params(params)
7
+ end
8
+
5
9
  def serialize(body)
6
10
  serialize_body(body)
7
11
  end
@@ -12,6 +16,25 @@ module Tacokit
12
16
 
13
17
  private
14
18
 
19
+ def normalize_request_params(params)
20
+ {}.tap do |norm|
21
+ (params || {}).each do |key, value|
22
+ norm[key] = normalize_param_value(value)
23
+ end
24
+ end
25
+ end
26
+
27
+ def normalize_param_value(value)
28
+ case value
29
+ when Array
30
+ value.map { |v| camel_path(v) }.join(",")
31
+ when /\,/
32
+ normalize_param_value(value.split(","))
33
+ else
34
+ camel_path(value)
35
+ end
36
+ end
37
+
15
38
  def serialize_body(body)
16
39
  case body
17
40
  when Hash
@@ -31,26 +54,25 @@ module Tacokit
31
54
 
32
55
  def camelize_key(key)
33
56
  k = key.to_s
34
- k = k.gsub(%r{([a-zA-Z]+?)_id(s\b|\b)?$}, "id_\\1\\2")
35
- k = k.gsub(%r{(#{camelize_special_cases.keys.join('|')})}) { |m| camelize_special_cases.fetch(m) }
57
+ k = k.gsub(%r{([a-zA-Z_]+?)_id(s\b|\b)?$}, "id_\\1\\2")
58
+ k = k.gsub(%r{(#{camelize_special_cases.keys.join("|")})}) { |m| camelize_special_cases.fetch(m) }
36
59
  camelize(k, :lower)
37
60
  end
38
61
 
39
62
  # Converts
40
- # 'prefs' => { 'voting' => 'members' }
63
+ # "prefs" => { "voting" => "members" }
41
64
  # to
42
- # 'prefs/voting' => 'members
65
+ # "prefs/voting" => "members
43
66
  #
44
67
  def flatten_nested_keys(body)
45
68
  options = {}
46
69
  body.each do |key, value|
47
- if value.is_a?(Hash)
48
- value = flatten_nested_keys(value.dup)
49
- value.each do |nested_key, nested_value|
50
- options["#{key}/#{nested_key}"] = nested_value
51
- end
52
- body.delete(key)
70
+ next unless value.is_a?(Hash)
71
+ value = flatten_nested_keys(value.dup)
72
+ value.each do |nested_key, nested_value|
73
+ options["#{key}/#{nested_key}"] = nested_value
53
74
  end
75
+ body.delete(key)
54
76
  end
55
77
  body.merge(options)
56
78
  end
@@ -78,7 +100,7 @@ module Tacokit
78
100
 
79
101
  def underscore_key(key)
80
102
  k = key.to_s
81
- k = k.gsub(%r{(#{pluralize_special_cases.join('|')})}, "\\1s")
103
+ k = k.gsub(%r{(#{pluralize_special_cases.join("|")})}, "\\1s")
82
104
  k = underscore(k)
83
105
  k.gsub(%r{^id_([a-zA-Z_]+?)(s\b|\b)$}, "\\1_id\\2")
84
106
  end
@@ -89,9 +111,9 @@ module Tacokit
89
111
 
90
112
  def camelize_special_cases
91
113
  {
92
- 'callback_url' => 'callbackURL'
114
+ "callback_url" => "callbackURL",
115
+ "checklist_source_id" => "idChecklistSource"
93
116
  }
94
117
  end
95
-
96
118
  end
97
119
  end