namely 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +22 -24
  3. data/lib/namely.rb +2 -101
  4. data/lib/namely/authenticator.rb +61 -14
  5. data/lib/namely/collection.rb +78 -0
  6. data/lib/namely/connection.rb +90 -0
  7. data/lib/namely/exceptions.rb +0 -3
  8. data/lib/namely/model.rb +78 -0
  9. data/lib/namely/resource_gateway.rb +7 -2
  10. data/lib/namely/version.rb +1 -1
  11. data/spec/fixtures/vcr_cassettes/{country_head.yml → countries_head.yml} +4 -4
  12. data/spec/fixtures/vcr_cassettes/{country_head_missing.yml → countries_head_missing.yml} +4 -4
  13. data/spec/fixtures/vcr_cassettes/{country_index.yml → countries_index.yml} +6 -6
  14. data/spec/fixtures/vcr_cassettes/{country_show.yml → countries_show.yml} +4 -4
  15. data/spec/fixtures/vcr_cassettes/{country_show_missing.yml → countries_show_missing.yml} +7 -7
  16. data/spec/fixtures/vcr_cassettes/{currencytype_index.yml → currency_types_index.yml} +5 -5
  17. data/spec/fixtures/vcr_cassettes/current_user.yml +57 -0
  18. data/spec/fixtures/vcr_cassettes/{event_head.yml → events_head.yml} +4 -4
  19. data/spec/fixtures/vcr_cassettes/{event_head_missing.yml → events_head_missing.yml} +5 -5
  20. data/spec/fixtures/vcr_cassettes/events_index.yml +89 -0
  21. data/spec/fixtures/vcr_cassettes/{event_show.yml → events_show.yml} +5 -5
  22. data/spec/fixtures/vcr_cassettes/{event_show_missing.yml → events_show_missing.yml} +6 -6
  23. data/spec/fixtures/vcr_cassettes/fields_index.yml +48 -0
  24. data/spec/fixtures/vcr_cassettes/{jobtier_index.yml → job_tiers_index.yml} +5 -5
  25. data/spec/fixtures/vcr_cassettes/{field_index.yml → profiles/fields_index.yml} +5 -5
  26. data/spec/fixtures/vcr_cassettes/profiles_create.yml +85 -0
  27. data/spec/fixtures/vcr_cassettes/{profile_create_failed.yml → profiles_create_failed.yml} +9 -10
  28. data/spec/fixtures/vcr_cassettes/{profile_head.yml → profiles_head.yml} +4 -4
  29. data/spec/fixtures/vcr_cassettes/{profile_head_missing.yml → profiles_head_missing.yml} +4 -4
  30. data/spec/fixtures/vcr_cassettes/profiles_index.yml +981 -0
  31. data/spec/fixtures/vcr_cassettes/{profile_show.yml → profiles_show.yml} +4 -4
  32. data/spec/fixtures/vcr_cassettes/{profile_show_missing.yml → profiles_show_missing.yml} +7 -7
  33. data/spec/fixtures/vcr_cassettes/profiles_show_updated.yml +91 -0
  34. data/spec/fixtures/vcr_cassettes/profiles_update.yml +95 -0
  35. data/spec/fixtures/vcr_cassettes/profiles_update_revert.yml +95 -0
  36. data/spec/fixtures/vcr_cassettes/{report_head.yml → reports_head.yml} +4 -4
  37. data/spec/fixtures/vcr_cassettes/{report_head_missing.yml → reports_head_missing.yml} +5 -5
  38. data/spec/fixtures/vcr_cassettes/reports_show.yml +186 -0
  39. data/spec/fixtures/vcr_cassettes/{report_show_missing.yml → reports_show_missing.yml} +7 -7
  40. data/spec/fixtures/vcr_cassettes/token.yml +6 -6
  41. data/spec/namely/authenticator_spec.rb +36 -0
  42. data/spec/namely/connection_spec.rb +15 -0
  43. data/spec/namely/integration_spec.rb +94 -0
  44. data/spec/namely/resource_gateway_spec.rb +18 -10
  45. data/spec/shared_examples/a_resource_with_a_create_action.rb +24 -0
  46. data/spec/shared_examples/a_resource_with_a_show_action.rb +38 -0
  47. data/spec/shared_examples/a_resource_with_an_index_action.rb +15 -0
  48. data/spec/shared_examples/{a_model_with_an_update_action.rb → a_resource_with_an_update_action.rb} +10 -9
  49. data/spec/spec_helper.rb +0 -17
  50. metadata +75 -88
  51. data/lib/namely/country.rb +0 -9
  52. data/lib/namely/currency_type.rb +0 -9
  53. data/lib/namely/event.rb +0 -9
  54. data/lib/namely/field.rb +0 -9
  55. data/lib/namely/job_tier.rb +0 -9
  56. data/lib/namely/profile.rb +0 -13
  57. data/lib/namely/report.rb +0 -9
  58. data/lib/namely/restful_model.rb +0 -150
  59. data/spec/fixtures/vcr_cassettes/event_index.yml +0 -88
  60. data/spec/fixtures/vcr_cassettes/profile_create.yml +0 -85
  61. data/spec/fixtures/vcr_cassettes/profile_index.yml +0 -979
  62. data/spec/fixtures/vcr_cassettes/profile_show_updated.yml +0 -91
  63. data/spec/fixtures/vcr_cassettes/profile_update.yml +0 -95
  64. data/spec/fixtures/vcr_cassettes/profile_update_revert.yml +0 -95
  65. data/spec/fixtures/vcr_cassettes/report_show.yml +0 -185
  66. data/spec/namely/configuration_spec.rb +0 -33
  67. data/spec/namely/country_spec.rb +0 -11
  68. data/spec/namely/currency_type_spec.rb +0 -5
  69. data/spec/namely/event_spec.rb +0 -11
  70. data/spec/namely/field_spec.rb +0 -5
  71. data/spec/namely/job_tier_spec.rb +0 -5
  72. data/spec/namely/profile_spec.rb +0 -25
  73. data/spec/namely/report_spec.rb +0 -8
  74. data/spec/shared_examples/a_model_with_a_create_action.rb +0 -24
  75. data/spec/shared_examples/a_model_with_a_show_action.rb +0 -38
  76. data/spec/shared_examples/a_model_with_an_index_action.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c6c9e3f2c619a62f8ecf845c656d50ff12f025d9
4
- data.tar.gz: e17424ee887917ba69dd1928557bb6cd5c64ab02
3
+ metadata.gz: a5bb34f16c66dc6a4c03d22c8001877df63c1596
4
+ data.tar.gz: 6d524bf7b582e3400b69501d7f3c765b47471ab3
5
5
  SHA512:
6
- metadata.gz: 2a9a3dc86ec13b62778feca282add92456013a154dfc8fa5e652a761a9f58350e35ee626184b5bf4aaf60cc47a9d58f56e6aeef156f6204ae16bc58ebdb11a0a
7
- data.tar.gz: 5a025299aad278901b62a0fc0a24a4977cb4c56aaee28fcbc93f85c65811e70598c400366a487e872f8a78c2d8a19a4f65baecb577e4de15404d7375da07d297
6
+ metadata.gz: e4a363d03f16638af12ce7a2e375eb0e24f2cc6b479a97ec4c6363a455153f463fdabe13b0be46e340d6990ac24679356db22016f07c7b2e0bb33f6d1cbdba78
7
+ data.tar.gz: b03735ea330318c229197c12135f6470d119e6178844afcae57e834d8873dd5daeacab625ee02e184d50f7f7181104959ba2470467430e4faec2cd0ff476cb30
data/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  [![Travis](https://travis-ci.org/namely/ruby-client.svg?branch=master)](https://travis-ci.org/namely/ruby-client/builds)
4
4
  [![Code Climate](https://codeclimate.com/github/namely/ruby-client/badges/gpa.svg)](https://codeclimate.com/github/namely/ruby-client)
5
5
 
6
- TODO: Write a gem description
6
+ The Namely gem wraps the Namely HTTP API, allowing you to manipulate
7
+ your account through Ruby.
7
8
 
8
9
  ## Installation
9
10
 
@@ -21,34 +22,31 @@ Or install it yourself as:
21
22
 
22
23
  $ gem install namely
23
24
 
24
- ## Configuration
25
+ ## Establishing a connection
25
26
 
26
- You'll need to configure the gem to use your Namely account. Do this
27
- by setting the `access_token` and `subdomain` variables in a
28
- configuration block:
27
+ First, you'll need to create a connection to your Namely account using
28
+ your access token and subdomain.
29
29
 
30
30
  ```ruby
31
- Namely.configure do |config|
32
- config.access_token = "your_access_token"
33
- config.subdomain = "your-organization"
34
- end
31
+ namely = Namely::Connection.new(
32
+ access_token: "your_access_token",
33
+ subdomain: "your-organization",
34
+ )
35
35
  ```
36
36
 
37
37
  An access token can be obtained through your organization's Namely
38
38
  account.
39
39
 
40
- Namely associates a subdomain with your
41
- organization. organization. For example, if your account is at
42
- `http://your-organization.namely.com/`, your subdomain would be
43
- `"your-organization"`.
44
-
45
- In a Rails application this configuration belongs in
46
- `config/initializers/namely.rb`.
40
+ Namely associates a subdomain with your organization. For example, if
41
+ your account is at `http://your-organization.namely.com/`, your
42
+ subdomain would be `"your-organization"`.
47
43
 
48
44
  ## Usage Examples
49
45
 
46
+ Once you've created a connection you can use it to access your data.
47
+
50
48
  ```ruby
51
- Namely::Country.all.each do |country|
49
+ namely.countries.all.each do |country|
52
50
  puts "#{country.id} - #{country.name}"
53
51
  end
54
52
  # AF - Afghanistan
@@ -59,7 +57,7 @@ end
59
57
  ```
60
58
 
61
59
  ```ruby
62
- if Namely::Country.exists?("BE")
60
+ if namely.countries.exists?("BE")
63
61
  "Belgium exists!"
64
62
  else
65
63
  "Hmm."
@@ -67,15 +65,15 @@ end # => "Belgium exists!"
67
65
  ```
68
66
 
69
67
  ```ruby
70
- Namely::Country.find("BE")
71
- # => <Namely::Country id="BE", name="Belgium", subdivision_type="Province", links={"subdivisions"=>[{"id"=>"BRU", "name"=>"Brussels"}, {"id"=>"VAN", "name"=>"Antwerpen (nl)"}, {"id"=>"VBR", "name"=>"Vlaams Brabant (nl)"}, {"id"=>"VLI", "name"=>"Limburg (nl)"}, {"id"=>"VOV", "name"=>"Oost-Vlaanderen (nl)"}, {"id"=>"VWV", "name"=>"West-Vlaanderen (nl)"}, {"id"=>"WBR", "name"=>"Brabant Wallon (fr)"}, {"id"=>"WHT", "name"=>"Hainaut (fr)"}, {"id"=>"WLG", "name"=>"Liège (fr)"}, {"id"=>"WLX", "name"=>"Luxembourg (fr)"}, {"id"=>"WNA", "name"=>"Namur (fr)"}]}>
68
+ namely.countries.find("BE")
69
+ # => <Namely::Model id="BE", name="Belgium", subdivision_type="Province", links={"subdivisions"=>[{"id"=>"BRU", "name"=>"Brussels"}, {"id"=>"VAN", "name"=>"Antwerpen (nl)"}, {"id"=>"VBR", "name"=>"Vlaams Brabant (nl)"}, {"id"=>"VLI", "name"=>"Limburg (nl)"}, {"id"=>"VOV", "name"=>"Oost-Vlaanderen (nl)"}, {"id"=>"VWV", "name"=>"West-Vlaanderen (nl)"}, {"id"=>"WBR", "name"=>"Brabant Wallon (fr)"}, {"id"=>"WHT", "name"=>"Hainaut (fr)"}, {"id"=>"WLG", "name"=>"Liège (fr)"}, {"id"=>"WLX", "name"=>"Luxembourg (fr)"}, {"id"=>"WNA", "name"=>"Namur (fr)"}]}>
72
70
  ```
73
71
 
74
72
  ```ruby
75
- foo_bar = Namely::Profile.create!(
76
- first_name: "Metasyntactic",
77
- last_name: "Variable",
78
- email: "foo_bar@namely.com"
73
+ foo_bar = namely.profiles.create!(
74
+ first_name: "Dade",
75
+ last_name: "Murphy",
76
+ email: "crash_override@example.com"
79
77
  )
80
78
 
81
79
  foo_bar.id # => "37c919e2-f1c8-4beb-b1d4-a9a36ccc830c"
@@ -6,109 +6,10 @@ require "rest_client"
6
6
  require "namely/authenticator"
7
7
  require "namely/exceptions"
8
8
  require "namely/resource_gateway"
9
- require "namely/restful_model"
10
-
11
- require "namely/country"
12
- require "namely/currency_type"
13
- require "namely/event"
14
- require "namely/field"
15
- require "namely/job_tier"
16
- require "namely/profile"
17
- require "namely/report"
9
+ require "namely/collection"
10
+ require "namely/connection"
18
11
 
19
12
  require "namely/version"
20
13
 
21
14
  module Namely
22
- class << self
23
- attr_writer :configuration
24
- end
25
-
26
- # Return the current configuration.
27
- #
28
- # @raise [ImproperlyConfiguredError] if the subdomain or access token
29
- # haven't been configured.
30
- #
31
- # @return [Configuration]
32
- def self.configuration
33
- @configuration || raise(
34
- ImproperlyConfiguredError,
35
- "Before using the Namely gem, you'll need to configure it with `Namely.configure`."
36
- )
37
- end
38
-
39
- # Set the configuration variables (the subdomain and access token)
40
- # that allow the Namely gem to access your account.
41
- #
42
- # @yieldparam [Configuration] configuration the Configuration
43
- # object, the attributes of which can be set in the block.
44
- #
45
- # @example
46
- # Namely.configure do |config|
47
- # config.access_token = "your_access_token"
48
- # config.subdomain = "your-organization"
49
- # end
50
- #
51
- # @return [void]
52
- def self.configure
53
- @configuration ||= Configuration.new
54
- yield configuration
55
- end
56
-
57
- # Return a resource gateway for interfacing with a given resource.
58
- #
59
- # @param [String] resource_name
60
- # @param [String] endpoint
61
- #
62
- # @return [ResourceGateway]
63
- def self.resource_gateway(resource_name, endpoint)
64
- configuration.resource_gateway(resource_name, endpoint)
65
- end
66
-
67
- class Configuration
68
- attr_writer :access_token, :subdomain
69
-
70
- # Get the access token.
71
- #
72
- # @raise [ImproperlyConfiguredError] if the access token hasn't
73
- # been configured.
74
- #
75
- # @return [String] the access token
76
- def access_token
77
- @access_token || raise_missing_variable_error(:access_token)
78
- end
79
-
80
- # Get the subdomain.
81
- #
82
- # @raise [ImproperlyConfiguredError] if the subdomain hasn't been
83
- # configured.
84
- #
85
- # @return [String] the subdomain
86
- def subdomain
87
- @subdomain || raise_missing_variable_error(:subdomain)
88
- end
89
-
90
- # Create a resource gateway for interfacing with a given resource.
91
- #
92
- # @param [String] resource_name
93
- # @param [String] endpoint
94
- #
95
- # @return [ResourceGateway]
96
- def resource_gateway(resource_name, endpoint)
97
- Namely::ResourceGateway.new(
98
- access_token: access_token,
99
- endpoint: endpoint,
100
- resource_name: resource_name,
101
- subdomain: subdomain
102
- )
103
- end
104
-
105
- private
106
-
107
- def raise_missing_variable_error(variable)
108
- raise(
109
- ImproperlyConfiguredError,
110
- "The Namely `#{variable}` configuration variable hasn't been set... did you set it when you called `Namely.configure`?"
111
- )
112
- end
113
- end
114
15
  end
@@ -67,7 +67,11 @@ module Namely
67
67
  #
68
68
  # @return [Hash]
69
69
  def retrieve_tokens(options)
70
- request_tokens(options, "authorization_code", options.fetch(:code))
70
+ request_tokens(
71
+ options,
72
+ grant_type: "authorization_code",
73
+ code: options.fetch(:code),
74
+ )
71
75
  end
72
76
 
73
77
  # Get an updated access token using the refresh token.
@@ -95,24 +99,66 @@ module Namely
95
99
  #
96
100
  # @return [Hash]
97
101
  def refresh_access_token(options)
98
- request_tokens(options, "refresh_token", options.fetch(:refresh_token))
102
+ request_tokens(
103
+ options,
104
+ grant_type: "refresh_token",
105
+ refresh_token: options.fetch(:refresh_token),
106
+ )
107
+ end
108
+
109
+ # Return the profile of the user accessing the API.
110
+ #
111
+ # @param [Hash] options
112
+ # @option options [String] access_token (required)
113
+ # @option options [String] subdomain (required)
114
+ #
115
+ # @return [Model] the profile of the current user.
116
+ def current_user(options)
117
+ access_token = options.fetch(:access_token)
118
+ subdomain = options.fetch(:subdomain)
119
+
120
+ user_url = URL.new(options.merge(
121
+ params: {
122
+ access_token: access_token,
123
+ },
124
+ path: "/api/v1/profiles/me",
125
+ )).to_s
126
+
127
+ response = RestClient.get(
128
+ user_url,
129
+ accept: :json,
130
+ )
131
+ build_profile(
132
+ access_token,
133
+ subdomain,
134
+ JSON.parse(response)["profiles"].first
135
+ )
99
136
  end
100
137
 
101
138
  private
102
139
 
103
140
  attr_reader :client_id, :client_secret
104
141
 
105
- def request_tokens(options, grant_type, token)
142
+ def request_tokens(url_options, post_params)
106
143
  response = RestClient.post(
107
- URL.new(options.merge(path: "/api/v1/oauth2/token")).to_s,
108
- grant_type: grant_type,
109
- client_id: client_id,
110
- client_secret: client_secret,
111
- refresh_token: token,
144
+ URL.new(url_options.merge(path: "/api/v1/oauth2/token")).to_s,
145
+ {
146
+ client_id: client_id,
147
+ client_secret: client_secret,
148
+ }.merge(post_params),
112
149
  )
113
150
  JSON.parse(response)
114
151
  end
115
152
 
153
+ def build_profile(access_token, subdomain, attributes)
154
+ profile_gateway = ResourceGateway.new(
155
+ access_token: access_token,
156
+ endpoint: "profiles",
157
+ subdomain: subdomain,
158
+ )
159
+ Model.new(profile_gateway, attributes)
160
+ end
161
+
116
162
  class URL
117
163
  def initialize(options)
118
164
  @options = options
@@ -149,14 +195,15 @@ module Namely
149
195
  end
150
196
 
151
197
  def params
152
- options.fetch(:params, {}).merge(redirect_uri_param)
198
+ options.fetch(:params, {}).merge(optional_params)
153
199
  end
154
200
 
155
- def redirect_uri_param
156
- if options.has_key?(:redirect_uri)
157
- { redirect_uri: options[:redirect_uri] }
158
- else
159
- {}
201
+ def optional_params
202
+ [:redirect_uri, :state].inject({}) do |additional_params, key|
203
+ if options.has_key?(key)
204
+ additional_params[key] = options[key]
205
+ end
206
+ additional_params
160
207
  end
161
208
  end
162
209
  end
@@ -0,0 +1,78 @@
1
+ require_relative "model"
2
+
3
+ module Namely
4
+ class Collection
5
+ def initialize(resource_gateway)
6
+ @resource_gateway = resource_gateway
7
+ end
8
+
9
+ # Return every instance of this model.
10
+ #
11
+ # A model might have quite a few instances. If this is the case,
12
+ # the query may take some time (several seconds) and the resulting
13
+ # array may be very large.
14
+ #
15
+ # @return [Array<Model>]
16
+ def all
17
+ resource_gateway.json_index.map { |model| build(model) }
18
+ end
19
+
20
+ # Instantiate (but don't save) a new Model with the given attributes.
21
+ #
22
+ # @param [Hash] attributes the attributes of the model being built.
23
+ #
24
+ # @return [Model]
25
+ def build(attributes)
26
+ Model.new(resource_gateway, attributes)
27
+ end
28
+
29
+ # Create a new Model on the server with the given attributes.
30
+ #
31
+ # @param [Hash] attributes the attributes of the model being created.
32
+ #
33
+ # @example
34
+ # profiles_collection.create!(
35
+ # first_name: "Beardsly",
36
+ # last_name: "McDog",
37
+ # email: "beardsly@namely.com"
38
+ # )
39
+ #
40
+ # @return [Model] the created model.
41
+ def create!(attributes)
42
+ build(attributes).save!
43
+ end
44
+
45
+ def endpoint
46
+ resource_gateway.endpoint
47
+ end
48
+
49
+ # Returns true if a Model with this ID exists, false otherwise.
50
+ #
51
+ # @param [#to_s] id
52
+ #
53
+ # @return [Boolean]
54
+ def exists?(id)
55
+ resource_gateway.show_head(id)
56
+ true
57
+ rescue RestClient::ResourceNotFound
58
+ false
59
+ end
60
+
61
+ # Fetch a model from the server by its ID.
62
+ #
63
+ # @param [#to_s] id
64
+ #
65
+ # @raise [NoSuchModelError] if the model wasn't found.
66
+ #
67
+ # @return [Model]
68
+ def find(id)
69
+ build(resource_gateway.json_show(id))
70
+ rescue RestClient::ResourceNotFound
71
+ raise NoSuchModelError, "Can't find any #{endpoint} with id \"#{id}\""
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :resource_gateway
77
+ end
78
+ end
@@ -0,0 +1,90 @@
1
+ module Namely
2
+ class Connection
3
+ # Instantiate a new connection to the server.
4
+ #
5
+ # @param [Hash] options
6
+ # @option options [String] access_token (required)
7
+ # @option options [String] subdomain (required)
8
+ #
9
+ # @example
10
+ # Namely.configure do |config|
11
+ # config.access_token = "your_access_token"
12
+ # config.subdomain = "your-organization"
13
+ # end
14
+ #
15
+ # @raise [KeyError] if access_token and subdomain aren't provided.
16
+ #
17
+ # @return [Connection]
18
+ def initialize(options)
19
+ @access_token = options.fetch(:access_token)
20
+ @subdomain = options.fetch(:subdomain)
21
+ rescue KeyError
22
+ raise ArgumentError, "Please supply an access_token and subdomain."
23
+ end
24
+
25
+ # Return a Collection of countries.
26
+ #
27
+ # @return [Collection]
28
+ def countries
29
+ collection("countries")
30
+ end
31
+
32
+ # Return a Collection of currency types.
33
+ #
34
+ # @return [Collection]
35
+ def currency_types
36
+ collection("currency_types")
37
+ end
38
+
39
+ # Return a Collection of countries.
40
+ #
41
+ # @return [Collection]
42
+ def events
43
+ collection("events")
44
+ end
45
+
46
+ # Return a Collection of profile fields.
47
+ #
48
+ # @return [Collection]
49
+ def fields
50
+ collection("profiles/fields")
51
+ end
52
+
53
+ # Return a Collection of job tiers.
54
+ #
55
+ # @return [Collection]
56
+ def job_tiers
57
+ collection("job_tiers")
58
+ end
59
+
60
+ # Return a Collection of profiles.
61
+ #
62
+ # @return [Collection]
63
+ def profiles
64
+ collection("profiles")
65
+ end
66
+
67
+ # Return a Collection of reports.
68
+ #
69
+ # @return [Collection]
70
+ def reports
71
+ collection("reports")
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :access_token, :subdomain
77
+
78
+ def collection(endpoint)
79
+ Namely::Collection.new(gateway(endpoint))
80
+ end
81
+
82
+ def gateway(endpoint)
83
+ ResourceGateway.new(
84
+ access_token: access_token,
85
+ endpoint: endpoint,
86
+ subdomain: subdomain,
87
+ )
88
+ end
89
+ end
90
+ end