context-io 0.0.1

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 (51) hide show
  1. data/Gemfile +4 -0
  2. data/LICENSE +21 -0
  3. data/README.md +129 -0
  4. data/Rakefile +121 -0
  5. data/SPEC.md +49 -0
  6. data/context-io.gemspec +96 -0
  7. data/lib/context-io.rb +14 -0
  8. data/lib/context-io/account.rb +254 -0
  9. data/lib/context-io/authentication.rb +23 -0
  10. data/lib/context-io/config.rb +103 -0
  11. data/lib/context-io/connection.rb +45 -0
  12. data/lib/context-io/core_ext/hash.rb +31 -0
  13. data/lib/context-io/error.rb +24 -0
  14. data/lib/context-io/error/bad_request.rb +12 -0
  15. data/lib/context-io/error/client_error.rb +10 -0
  16. data/lib/context-io/error/forbidden.rb +12 -0
  17. data/lib/context-io/error/internal_server_error.rb +10 -0
  18. data/lib/context-io/error/not_found.rb +12 -0
  19. data/lib/context-io/error/payment_required.rb +13 -0
  20. data/lib/context-io/error/server_error.rb +10 -0
  21. data/lib/context-io/error/service_unavailable.rb +13 -0
  22. data/lib/context-io/error/unauthorized.rb +12 -0
  23. data/lib/context-io/file.rb +234 -0
  24. data/lib/context-io/folder.rb +90 -0
  25. data/lib/context-io/message.rb +160 -0
  26. data/lib/context-io/oauth_provider.rb +84 -0
  27. data/lib/context-io/request.rb +70 -0
  28. data/lib/context-io/request/oauth.rb +44 -0
  29. data/lib/context-io/resource.rb +16 -0
  30. data/lib/context-io/response.rb +5 -0
  31. data/lib/context-io/response/parse_json.rb +30 -0
  32. data/lib/context-io/response/raise_client_error.rb +59 -0
  33. data/lib/context-io/response/raise_server_error.rb +32 -0
  34. data/lib/context-io/source.rb +193 -0
  35. data/lib/context-io/version.rb +7 -0
  36. data/spec/account_spec.rb +247 -0
  37. data/spec/contextio_spec.rb +45 -0
  38. data/spec/file_spec.rb +101 -0
  39. data/spec/fixtures/accounts.json +21 -0
  40. data/spec/fixtures/files.json +41 -0
  41. data/spec/fixtures/files_group.json +47 -0
  42. data/spec/fixtures/folders.json +1 -0
  43. data/spec/fixtures/messages.json +1 -0
  44. data/spec/fixtures/oauth_providers.json +12 -0
  45. data/spec/fixtures/sources.json +1 -0
  46. data/spec/folder_spec.rb +48 -0
  47. data/spec/message_spec.rb +294 -0
  48. data/spec/oauth_provider_spec.rb +88 -0
  49. data/spec/source_spec.rb +248 -0
  50. data/spec/spec_helper.rb +4 -0
  51. metadata +214 -0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Henrik Hodne
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,129 @@
1
+ ContextIO - Extract data from email
2
+ ===================================
3
+
4
+ [![Build Status](https://secure.travis-ci.org/dvyjones/context-io.png)](http://travis-ci.org/dvyjones/context-io)
5
+ [![Dependency Status](https://gemnasium.com/dvyjones/context-io.png)](https://gemnasium.com/dvyjones/context-io)
6
+
7
+ ## Description
8
+
9
+ ContextIO is a Ruby wrapper for the [Context.IO][contextio] web service.
10
+
11
+ Context.IO is the missing email API that makes it easy and fast
12
+ to integrate your user's email data in your application.
13
+
14
+ ContextIO follows the rules of [Semantic Versioning][semver] and uses
15
+ [TomDoc][tomdoc] for inline documentation.
16
+
17
+ [contextio]: http://context.io
18
+ [semver]: http://semver.org
19
+ [tomdoc]: http://tomdoc.org
20
+
21
+
22
+ ## Installation
23
+
24
+ The best way to install ContextIO is through Rubygems:
25
+
26
+ ```
27
+ $ [sudo] gem install context-io
28
+ ```
29
+
30
+ If you're installing from source, you can use [Bundler][bundler] to pick up all
31
+ the gems:
32
+
33
+ ```
34
+ $ bundle install
35
+ ```
36
+
37
+ [bundler]: http://gembundler.org
38
+
39
+ ## Usage
40
+
41
+ The ContextIO classes map pretty much one-to-one to the Context.IO API
42
+ resources, which you can find [on their documentation site][contextiodocs].
43
+
44
+ [contextiodocs]: http://context.io/docs/2.0
45
+
46
+ ### Authenticate
47
+
48
+ ContextIO uses two-legged [OAuth][oauth] for authentication, which means you
49
+ need to get an API key from [Context.IO][contextio]. The API key consists of a
50
+ consumer key and a consumer secret, and once you have them you can set up
51
+ ContextIO like this:
52
+
53
+ ```ruby
54
+ ContextIO.configure do |config|
55
+ config.consumer_key = 'consumer key'
56
+ config.consumer_secret = 'consumer secret'
57
+ end
58
+ ```
59
+
60
+ [oauth]: http://oauth.net/
61
+
62
+ ### Get an Account object
63
+
64
+ Once you're authenticated, you can get an `Account` object, which is what you
65
+ will be dealing with most of the time.
66
+
67
+ ```ruby
68
+ account = ContextIO::Account.all.first
69
+ ```
70
+
71
+ You can also find accounts matching a given email address.
72
+
73
+ ```ruby
74
+ account = ContextIO::Account.all(:email => 'me@example.org').first
75
+ ```
76
+
77
+ ## Contributing
78
+
79
+ In the spirit of [free software][free-sw], **everyone** is encouraged to help
80
+ improve this project.
81
+
82
+ Here are some ways *you* can contribute:
83
+
84
+ * by using alpha, beta and prerelease versions
85
+ * by reporting bugs
86
+ * by suggesting new features
87
+ * by writing or editing documentation
88
+ * by writing specifications
89
+ * by writing code (**no patch is too small**: fix typos, add comments, clean up
90
+ inconsistent whitespace)
91
+ * by refactoring code
92
+ * by closing [issues][issues]
93
+ * by reviewing patches
94
+
95
+ ### Submitting an Issue
96
+
97
+ We use the [GitHub issue tracker][issues] to track bugs and features. Before
98
+ submitting a bug report or feature request, check to make sure it hasn't
99
+ already been submitted. You can indicate support for an existing issue by
100
+ writing a comment saying you have the same issue (please include what version
101
+ of the gem you are using, as well as what version of ruby). When submitting a
102
+ bug report, please include a [gist][gist] that includes a stack trace and any
103
+ details that may be necessary to reproduce the bug, including your gem version,
104
+ Ruby version and operating system. Ideally, a bug report should include a pull
105
+ request with failing specs.
106
+
107
+ ### Submitting a Pull Request
108
+
109
+ 1. Fork the project.
110
+ 2. Create a topic branch.
111
+ 3. Implement your feature or bug fix.
112
+ 4. Add documentation for your feature or bug fix.
113
+ 5. Run `bundle exec rake rdoc`. If your changes are not 100% documented, go
114
+ back to step 4.
115
+ 6. Add specs for your feature or bug fix.
116
+ 7. Run `bundle exec rake spec`. If your changes are not 100% covered, go back
117
+ to step 6.
118
+ 8. Commit your changes. If necessary, merge in upstream and rebase your
119
+ changes. Push your changes.
120
+ 9. Submit a pull request. Please do not include changes to the gemspec,
121
+ version or history file.
122
+
123
+ [free-sw]: http://www.gnu.org/philosophy/free-sw.html
124
+ [issues]: https://github.com/dvyjones/context-io/issues
125
+
126
+
127
+ ## Copyright
128
+
129
+ Copyright (c) 2012 Henrik Hodne. See LICENSE for details.
@@ -0,0 +1,121 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}/version.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'" }
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :spec
47
+
48
+ require 'rspec/core/rake_task'
49
+
50
+ RSpec::Core::RakeTask.new(:spec)
51
+
52
+ require 'yard'
53
+ YARD::Rake::YardocTask.new
54
+
55
+ #############################################################################
56
+ #
57
+ # Packaging tasks
58
+ #
59
+ #############################################################################
60
+
61
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
62
+ task :release => :build do
63
+ unless `git branch` =~ /^\* master$/
64
+ puts 'You must be on the master branch to release!'
65
+ exit!
66
+ end
67
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
68
+ sh "git tag v#{version}"
69
+ sh 'git push origin master'
70
+ sh "git push origin v#{version}"
71
+ sh "gem push pkg/#{gem_file}"
72
+ end
73
+
74
+ desc "Build #{gem_file} into the pkg directory"
75
+ task :build => :gemspec do
76
+ sh 'mkdir -p pkg'
77
+ sh "gem build #{gemspec_file}"
78
+ sh "mv #{gem_file} pkg"
79
+ end
80
+
81
+ desc "Generate #{gemspec_file}"
82
+ task :gemspec => :validate do
83
+ # read spec file and split out manifest section
84
+ spec = File.read(gemspec_file)
85
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
86
+
87
+ # replace name, version and date
88
+ replace_header(head, :name)
89
+ replace_header(head, :version)
90
+ replace_header(head, :date)
91
+ replace_header(head, :rubyforge_project)
92
+
93
+ # determine file list from git ls-files
94
+ files = `git ls-files`.
95
+ split("\n").
96
+ sort.
97
+ reject { |file| file =~ /^\./ }.
98
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
99
+ map { |file| " #{file}" }.
100
+ join("\n")
101
+
102
+ # piece file back together and write
103
+ manifest = " s.files = %w(\n#{files}\n )\n"
104
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
105
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
106
+ puts "Updated #{gemspec_file}"
107
+ end
108
+
109
+ desc "Validate #{gemspec_file}"
110
+ task :validate do
111
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
112
+ unless libfiles.empty?
113
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
114
+ exit!
115
+ end
116
+ unless Dir['VERSION*'].empty?
117
+ puts 'A `VERSION` file at root level violates Gem best practices.'
118
+ exit!
119
+ end
120
+ end
121
+
data/SPEC.md ADDED
@@ -0,0 +1,49 @@
1
+ ContextIO spec / roadmap
2
+ ========================
3
+
4
+ These are things that will be included in the "Usage" section of the README
5
+ file. Think of this as a roadmap.
6
+
7
+ ### Getting a list of messages
8
+
9
+ Once you have an `Account` object, you can get a list of messages as an array
10
+ of `Message` objects.
11
+
12
+ ```ruby
13
+ account.messages
14
+ # => [#<ContextIO::Message to: 'me@example.com', ...>, ...]
15
+ ```
16
+
17
+ You can also search for messages (see the documentation for
18
+ ContextIO::Message#find for the full list of attributes you can search for.
19
+
20
+ ```ruby
21
+ account.messages(:subject => 'Hello, world!').first
22
+ # => #<ContextIO::Message subject: 'Hello, world!', ...>
23
+ ```
24
+
25
+ ### The Message object
26
+
27
+ `Message` objects contains information about that message.
28
+
29
+ ```ruby
30
+ message = account.messages.first
31
+
32
+ message.subject
33
+ # => "Hello, world!"
34
+
35
+ message.from
36
+ # => "John Doe <john.doe@example.com>"
37
+
38
+ message.to
39
+ # => "me@example.com"
40
+
41
+ message.date
42
+ # => Thu Jan 05 23:30:22 +0100 2012
43
+
44
+ message.message_id
45
+ # => "abcdef0123456789"
46
+
47
+ message.email_message_id
48
+ # => "<20120105233022.abcdef0@mta1.mail.example.com>"
49
+
@@ -0,0 +1,96 @@
1
+ Gem::Specification.new do |s|
2
+ s.specification_version = 2 if s.respond_to? :specification_version=
3
+ s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
4
+ s.rubygems_version = '1.3.5'
5
+
6
+ s.name = 'context-io'
7
+ s.version = '0.0.1'
8
+ s.date = '2012-01-26'
9
+ s.rubyforge_project = 'context-io'
10
+
11
+ s.summary = 'Ruby wrapper for the context.io API.'
12
+ s.description = 'Ruby wrapper for the context.io API.'
13
+
14
+ s.authors = ['Henrik Hodne']
15
+ s.email = 'dvyjones@dvyjones.com'
16
+ s.homepage = 'https://github.com/dvyjones/context-io'
17
+
18
+ s.require_paths = %w(lib)
19
+
20
+ s.rdoc_options = ['--charset=UTF-8', '--markup tomdoc', '--main README.md']
21
+ s.extra_rdoc_files = %w(README.md LICENSE)
22
+
23
+ s.add_dependency('faraday', '~> 0.7.5')
24
+ s.add_dependency('simple_oauth', '~> 0.1.5')
25
+ s.add_dependency('multi_json', '~> 1.0.0')
26
+ s.add_dependency('webmock', '~> 1.7.10')
27
+
28
+ if RUBY_PLATFORM == 'java'
29
+ s.add_dependency('jruby-openssl', '>= 0')
30
+ end
31
+
32
+ s.add_development_dependency('rspec', '~> 2.8.0')
33
+ s.add_development_dependency('rake', '~> 0.9.0')
34
+ s.add_development_dependency('yard', '~> 0.7.4')
35
+ s.add_development_dependency('yard-rspec', '~> 0.1')
36
+ s.add_development_dependency('redcarpet', '~> 2.1.0')
37
+ s.add_development_dependency('github-markup', '~> 0.7.0')
38
+
39
+ # = MANIFEST =
40
+ s.files = %w(
41
+ Gemfile
42
+ LICENSE
43
+ README.md
44
+ Rakefile
45
+ SPEC.md
46
+ context-io.gemspec
47
+ lib/context-io.rb
48
+ lib/context-io/account.rb
49
+ lib/context-io/authentication.rb
50
+ lib/context-io/config.rb
51
+ lib/context-io/connection.rb
52
+ lib/context-io/core_ext/hash.rb
53
+ lib/context-io/error.rb
54
+ lib/context-io/error/bad_request.rb
55
+ lib/context-io/error/client_error.rb
56
+ lib/context-io/error/forbidden.rb
57
+ lib/context-io/error/internal_server_error.rb
58
+ lib/context-io/error/not_found.rb
59
+ lib/context-io/error/payment_required.rb
60
+ lib/context-io/error/server_error.rb
61
+ lib/context-io/error/service_unavailable.rb
62
+ lib/context-io/error/unauthorized.rb
63
+ lib/context-io/file.rb
64
+ lib/context-io/folder.rb
65
+ lib/context-io/message.rb
66
+ lib/context-io/oauth_provider.rb
67
+ lib/context-io/request.rb
68
+ lib/context-io/request/oauth.rb
69
+ lib/context-io/resource.rb
70
+ lib/context-io/response.rb
71
+ lib/context-io/response/parse_json.rb
72
+ lib/context-io/response/raise_client_error.rb
73
+ lib/context-io/response/raise_server_error.rb
74
+ lib/context-io/source.rb
75
+ lib/context-io/version.rb
76
+ spec/account_spec.rb
77
+ spec/contextio_spec.rb
78
+ spec/file_spec.rb
79
+ spec/fixtures/accounts.json
80
+ spec/fixtures/files.json
81
+ spec/fixtures/files_group.json
82
+ spec/fixtures/folders.json
83
+ spec/fixtures/messages.json
84
+ spec/fixtures/oauth_providers.json
85
+ spec/fixtures/sources.json
86
+ spec/folder_spec.rb
87
+ spec/message_spec.rb
88
+ spec/oauth_provider_spec.rb
89
+ spec/source_spec.rb
90
+ spec/spec_helper.rb
91
+ )
92
+ # = MANIFEST =
93
+
94
+ s.test_files = s.files.select { |path| path =~ /^spec\/spec_.*\.rb/ }
95
+ end
96
+
@@ -0,0 +1,14 @@
1
+ require 'context-io/config'
2
+ require 'context-io/authentication'
3
+ require 'context-io/account'
4
+ require 'context-io/file'
5
+ require 'context-io/oauth_provider'
6
+ require 'context-io/source'
7
+ require 'context-io/message'
8
+ require 'context-io/folder'
9
+
10
+ # The main ContextIO namespace.
11
+ module ContextIO
12
+ extend Config
13
+ extend Authentication
14
+ end
@@ -0,0 +1,254 @@
1
+ # encoding: utf-8
2
+
3
+ require 'context-io/resource'
4
+
5
+ module ContextIO
6
+ # An account. Create one of these for every user.
7
+ #
8
+ # This does not represent a mail account. An Account can have several mail
9
+ # accounts attached to it as {Source}s.
10
+ #
11
+ # Only the {#first_name} and {#last_name} can be changed after creation.
12
+ class Account < ContextIO::Resource
13
+ # @api public
14
+ # @return [String] The unique ID of the account.
15
+ attr_reader :id
16
+
17
+ # @api public
18
+ # @return [String] The username of the account.
19
+ attr_reader :username
20
+
21
+ # @api public
22
+ # @return [Time] When the account was created.
23
+ attr_reader :created
24
+
25
+ # @api public
26
+ # @return [Time, nil] When the account was suspended, or nil if the account
27
+ # isn't suspended.
28
+ attr_reader :suspended
29
+
30
+ # @api public
31
+ # @return [Array<String>] The email addresses associated with the account.
32
+ attr_reader :email_addresses
33
+
34
+ # @api public
35
+ # @return [String] The first name of the account holder.
36
+ attr_accessor :first_name
37
+
38
+ # @api public
39
+ # @return [String] The last name of the account holder.
40
+ attr_accessor :last_name
41
+
42
+ # @api public
43
+ # @return [Time, nil] When the account password expired, or nil if the
44
+ # password hasn't expired.
45
+ attr_reader :password_expired
46
+
47
+ # @api public
48
+ # @return [Array<Source>] The sources associated with this account.
49
+ attr_reader :sources
50
+
51
+ # Get all the accounts, optionally filtered with a query
52
+ #
53
+ # @api public
54
+ #
55
+ # @param [Hash] query The query to filter accounts by. All fields are
56
+ # optional.
57
+ # @option query [String] :email Only return accounts associated with this
58
+ # email address.
59
+ # @option query [:invalid_credentials, :connection_impossible,
60
+ # :no_access_to_all_mail, :ok, :temp_disabled, :disabled] :status Only
61
+ # return accounts with sources whose status is the one given. If an
62
+ # account has several sources, only those matching the given status will
63
+ # be included in the response.
64
+ # @option query [true, false] :status_ok Whether to only return accounts
65
+ # with sources that are working or not working properly (true/false,
66
+ # respectively). As with the `:status` filter above, only sources matching
67
+ # the specific value are included in the response.
68
+ # @option query [Integer] :limit The maximum number of results to return.
69
+ # @option query [Integer] :offset The offset to start the list at (0 is no
70
+ # offset).
71
+ #
72
+ # @example Fetch all accounts
73
+ # ContextIO::Account.all
74
+ #
75
+ # @example Fetch all accounts with the email address me@example.com
76
+ # ContextIO::Account.all(:email => 'me@example.com')
77
+ #
78
+ # @return [Array<Account>] The accounts matching the query, or all if no
79
+ # query is given.
80
+ def self.all(query={})
81
+ query[:status] = query[:status].to_s.upcase if query[:status]
82
+ if query.has_key?(:status_ok)
83
+ query[:status_ok] = query[:status_ok] ? '1' : '0'
84
+ end
85
+ get('/2.0/accounts', query).map do |account|
86
+ Account.from_json(account)
87
+ end
88
+ end
89
+
90
+ # Find an account given its ID
91
+ #
92
+ # @api public
93
+ #
94
+ # @param [String] id The ID of the account to look up.
95
+ #
96
+ # @example Find the account with the ID 'foobar'
97
+ # ContextIO::Account.find('foobar')
98
+ #
99
+ # @return [Account] The account with the given ID.
100
+ def self.find(id)
101
+ Account.from_json(get("/2.0/accounts/#{id}"))
102
+ end
103
+
104
+ # Initialize an Account
105
+ #
106
+ # @api public
107
+ #
108
+ # @param [Hash] attributes The attributes to set on the account (all values
109
+ # are optional).
110
+ # @option attributes [String] :email The primary email address of the
111
+ # account holder.
112
+ # @option attributes [String] :first_name The first name of the account
113
+ # holder.
114
+ # @option attributes [String] :last_name The last name of the account
115
+ # holder.
116
+ #
117
+ # @example Initialize an account with the email 'me@example.com'
118
+ # ContextIO::Account.new(:email => 'me@example.com')
119
+ def initialize(attributes={})
120
+ @email_addresses = [attributes[:email]] if attributes[:email]
121
+ @first_name = attributes[:first_name]
122
+ @last_name = attributes[:last_name]
123
+ end
124
+
125
+ # Send the account info to Context.IO
126
+ #
127
+ # If this is the first time the account is sent to Context.IO, the first
128
+ # email address set will be sent as the primary email address, and the first
129
+ # and last name will be sent if they are specified. You are required to
130
+ # specify one email address.
131
+ #
132
+ # If the account has been sent to Context.IO before, this will update the
133
+ # first and last name.
134
+ #
135
+ # @api public
136
+ #
137
+ # @raise [ArgumentError] If there isn't at least one email address specified
138
+ # in the {#email_addresses} field.
139
+ #
140
+ # @example Create an account
141
+ # account = ContextIO::Account.new(:email => 'me@example.com')
142
+ # account.save
143
+ #
144
+ # @return [true, false] Whether the save succeeded or not.
145
+ def save
146
+ self.id ? update_record : create_record
147
+ end
148
+
149
+ # Update attributes on the account object and then send them to Context.IO
150
+ #
151
+ # @api public
152
+ #
153
+ # @param [Hash] attributes The attributes to update.
154
+ # @option attributes [String] :first_name The first name of the account
155
+ # holder.
156
+ # @option attributes [String] :last_name The last name of the account
157
+ # holder.
158
+ #
159
+ # @example Update the account holder's name to "John Doe"
160
+ # account.update_attributes(:first_name => 'John', :last_name => 'Doe')
161
+ #
162
+ # @return [true, false] Whether the update succeeded or not.
163
+ def update_attributes(attributes)
164
+ @first_name = attributes[:first_name] if attributes[:first_name]
165
+ @last_name = attributes[:last_name] if attributes[:last_name]
166
+
167
+ response = put("/2.0/accounts/#{self.id}", attributes)
168
+
169
+ response['success']
170
+ end
171
+
172
+ # Create the account on Context.IO
173
+ #
174
+ # @api private
175
+ #
176
+ # This will only send the first email address in the email_addresses
177
+ # attribute (which is required) as well as the first and last name if they
178
+ # are not-falsey.
179
+ #
180
+ # @return [true, false] Whether the creation succeeded or not.
181
+ def create_record
182
+ unless self.email_addresses && self.email_addresses.first
183
+ raise ArgumentError.new('You must specify an email address')
184
+ end
185
+
186
+ attributes = { :email => self.email_addresses.first }
187
+ attributes[:first_name] = self.first_name if self.first_name
188
+ attributes[:last_name] = self.last_name if self.last_name
189
+
190
+ response = post('/2.0/accounts', attributes)
191
+ @id = response['id']
192
+
193
+ @saved = response['success']
194
+ end
195
+ private :create_record
196
+
197
+ # Update the account on Context.IO
198
+ #
199
+ # Only sends the first and last name, as they are the only attributes the
200
+ # Context.IO API allows you to update.
201
+ #
202
+ # @api private
203
+ #
204
+ # @return [true, false] Whether the update succeeded or not.
205
+ def update_record
206
+ attributes = {}
207
+ attributes[:first_name] = self.first_name if self.first_name
208
+ attributes[:last_name] = self.last_name if self.last_name
209
+ response = put("/2.0/accounts/#{self.id}", attributes)
210
+
211
+ response['success']
212
+ end
213
+ private :update_record
214
+
215
+ # Create an Account instance from the JSON returned by the Context.IO server
216
+ #
217
+ # @api private
218
+ #
219
+ # @param [Hash] json The parsed JSON object returned by a Context.IO API
220
+ # request. See their documentation for what keys are possible.
221
+ #
222
+ # @return [Account] An account with the given attributes.
223
+ def self.from_json(json)
224
+ account = new
225
+ account.instance_eval do
226
+ @id = json['id']
227
+ @username = json['username']
228
+ if json['created'] == 0
229
+ @created = nil
230
+ else
231
+ @created = Time.at(json['created'])
232
+ end
233
+ if json['suspended'] == 0
234
+ @suspended = nil
235
+ else
236
+ @suspended = Time.at(json['suspended'])
237
+ end
238
+ @email_addresses = json['email_addresses']
239
+ @first_name = json['first_name']
240
+ @last_name = json['last_name']
241
+ if json['password_expired'] == 0
242
+ @password_expired = nil
243
+ else
244
+ @password_expired = json['password_expired']
245
+ end
246
+ @sources = json['sources'].map do |source|
247
+ Source.from_json(@id, source)
248
+ end
249
+ end
250
+
251
+ account
252
+ end
253
+ end
254
+ end