tacokit 0.1.0 → 0.2.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 (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