namely 0.0.1 → 0.1.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 (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