timestamp_api 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +64 -11
- data/lib/timestamp_api/collection.rb +10 -0
- data/lib/timestamp_api/errors.rb +84 -1
- data/lib/timestamp_api/model.rb +31 -0
- data/lib/timestamp_api/model_attributes.rb +27 -0
- data/lib/timestamp_api/model_default_api_methods.rb +31 -0
- data/lib/timestamp_api/model_registry.rb +27 -0
- data/lib/timestamp_api/model_relations.rb +33 -0
- data/lib/timestamp_api/models/client.rb +7 -0
- data/lib/timestamp_api/models/project.rb +10 -0
- data/lib/timestamp_api/utils.rb +7 -0
- data/lib/timestamp_api/version.rb +1 -1
- data/lib/timestamp_api.rb +35 -13
- data/timestamp_api.gemspec +4 -2
- metadata +40 -4
- data/lib/timestamp_api/project.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a9b651bd6a1f81bb14b9edc23be1403ee37a4d3
|
4
|
+
data.tar.gz: 50238b9ee2ab12eb51b8fd4e5f82657c3cfb0385
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a72476235ef88e59f68f3e8d56a0d34c657581cf2f0cd4e2bbe626db7ec8d4193f89b5e90d45a14b7a299f326d431c93d204cd0d9ac4bfc9cd2ed974a46caa3
|
7
|
+
data.tar.gz: f6b28f91da78b36c7d8240fc1c37c5d16b21c2fac7242d4397fdcdb09cb87e0bf5773bcc60b4f46aa865333c337b7305ef3114277f72dce03f37942013e1d4c2
|
data/README.md
CHANGED
@@ -32,18 +32,72 @@ Configure your Timestamp API key by setting environment variable `TIMESTAMP_API_
|
|
32
32
|
TimestampAPi.api_key = "YOUR_TIMESTAMP_API_KEY"
|
33
33
|
```
|
34
34
|
|
35
|
+
#### Projects
|
36
|
+
|
35
37
|
List all projects:
|
36
38
|
```ruby
|
37
|
-
|
38
|
-
projects.map(&:name) # => ["A project", "Another project", "One more project"]
|
39
|
+
TimestampAPI::Project.all
|
39
40
|
```
|
40
41
|
|
41
42
|
Find a given project:
|
42
43
|
```ruby
|
43
|
-
project = TimestampAPI::Project.find(
|
44
|
+
project = TimestampAPI::Project.find(123)
|
45
|
+
|
46
|
+
project.name # => "My awesome project"
|
47
|
+
```
|
48
|
+
|
49
|
+
#### Clients
|
50
|
+
|
51
|
+
List all clients:
|
52
|
+
```ruby
|
53
|
+
TimestampAPI::Client.all
|
54
|
+
```
|
55
|
+
|
56
|
+
Find a given client:
|
57
|
+
```ruby
|
58
|
+
client = TimestampAPI::Client.find(123)
|
59
|
+
|
60
|
+
client.name # => "My beloved customer"
|
61
|
+
```
|
62
|
+
|
63
|
+
Find the client of a project
|
64
|
+
```ruby
|
65
|
+
project = TimestampAPI::Project.find(123)
|
66
|
+
|
44
67
|
project.client.name # => "My beloved customer"
|
45
68
|
```
|
46
69
|
|
70
|
+
## Models
|
71
|
+
|
72
|
+
The objects are represented by model classes (that inherits from `TimestampAPI::Model`):
|
73
|
+
```ruby
|
74
|
+
project = TimestampAPI::Project.find(123456)
|
75
|
+
|
76
|
+
project.class # => TimestampAPI::Project
|
77
|
+
project.is_a? TimestampAPI::Model # => true
|
78
|
+
```
|
79
|
+
|
80
|
+
Collections of objects are represented by `TimestampAPI::Collection` that inherits from `Array` (and implement the chainable `.where(conditions)` filter method described above). It means any `Array` method works on `TimestampAPI::Collection`:
|
81
|
+
```ruby
|
82
|
+
projects = TimestampAPI::Project.all
|
83
|
+
|
84
|
+
projects.class # => TimestampAPI::Collection
|
85
|
+
projects.map(&:name) # => ["A project", "Another project", "One more project"]
|
86
|
+
```
|
87
|
+
|
88
|
+
## Filtering
|
89
|
+
|
90
|
+
You can filter any object collection using the handy `.where()` syntax:
|
91
|
+
```ruby
|
92
|
+
projects = TimestampAPI::Project.all
|
93
|
+
|
94
|
+
projects.where(is_public: true) # => returns all public projects
|
95
|
+
projects.where(is_public: true, is_billable: true) # => returns all projects that are both public and billable
|
96
|
+
projects.where(is_public: true).where(is_billable: true) # => same as above: `where` is chainable \o/
|
97
|
+
```
|
98
|
+
|
99
|
+
:information_source: This does not filter objects **before** the network call (like ActiveRecord does), it's only a more elegant way of calling `Array#select` on the `Collection`
|
100
|
+
|
47
101
|
### Low level API calls
|
48
102
|
|
49
103
|
The above methods are simple wrappers around the generic low-level-ish API request method `TimestampAPI.request` that take a HTTP `method` (verb) and a `path` (to be appended to preconfigured API endpoint URL):
|
@@ -52,12 +106,9 @@ TimestampAPI.request(:get, "/projects") # Same as TimestampAPI::Project.a
|
|
52
106
|
TimestampAPI.request(:get, "/projects/123456") # Same as TimestampAPI::Project.find(123456)
|
53
107
|
```
|
54
108
|
|
55
|
-
|
109
|
+
To output all network requests done, you can set verbosity on:
|
56
110
|
```ruby
|
57
|
-
|
58
|
-
project.id # => 123456
|
59
|
-
project.name # => "Awesome project"
|
60
|
-
project.client.name # => "My beloved customer"
|
111
|
+
TimestampAPI.verbose = true
|
61
112
|
```
|
62
113
|
|
63
114
|
## Reverse engineering
|
@@ -74,12 +125,14 @@ There's also a `bin/console` executable provided with this gem, if you want a RE
|
|
74
125
|
|
75
126
|
### What's implemented already ?
|
76
127
|
|
77
|
-
* [x] Project#all
|
78
|
-
* [x] Project#find
|
128
|
+
* [x] `Project#all`
|
129
|
+
* [x] `Project#find`
|
130
|
+
* [x] `belongs_to` relationships
|
79
131
|
|
80
132
|
### What's not implemented yet ?
|
81
133
|
|
82
|
-
* [ ]
|
134
|
+
* [ ] _all other models_ :scream:
|
135
|
+
* [ ] `has_many` relationships
|
83
136
|
|
84
137
|
## Development
|
85
138
|
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module TimestampAPI
|
2
|
+
class Collection < Array
|
3
|
+
def where(conditions)
|
4
|
+
raise TimestampAPI::InvalidWhereContitions unless conditions.is_a? Hash
|
5
|
+
conditions.each_with_object(self.dup) do |condition, acc|
|
6
|
+
acc.select! { |i| i.send(condition.first.to_sym) == condition.last }
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/timestamp_api/errors.rb
CHANGED
@@ -5,9 +5,92 @@ module TimestampAPI
|
|
5
5
|
end
|
6
6
|
end
|
7
7
|
|
8
|
+
class InvalidAPIKey < StandardError
|
9
|
+
def message
|
10
|
+
"Configured API key is invalid."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
8
14
|
class InvalidServerResponse < StandardError
|
9
15
|
def message
|
10
|
-
"Server responded with invalid JSON.
|
16
|
+
"Server responded with invalid JSON."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidModelData < StandardError
|
21
|
+
attr_reader :caller_class, :json_data
|
22
|
+
|
23
|
+
def initialize(caller_class, json_data)
|
24
|
+
@caller_class = caller_class
|
25
|
+
@json_data = json_data
|
26
|
+
end
|
27
|
+
|
28
|
+
def message
|
29
|
+
if json_data.is_a? Hash
|
30
|
+
"A `#{caller_class}` class was initialized with JSON data for a `#{json_data["object"] || "unknown"}` object."
|
31
|
+
else
|
32
|
+
"A `#{caller_class}` class was initialized with data which is not a `Hash` (it was a `#{json_data.class}`, actually)."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class UnknownModelData < StandardError
|
38
|
+
attr_reader :json_object
|
39
|
+
|
40
|
+
def initialize(json_object = nil)
|
41
|
+
@json_object = json_object
|
42
|
+
end
|
43
|
+
|
44
|
+
def message
|
45
|
+
if json_object
|
46
|
+
"JSON data with object type `#{json_object}` has no matching model implemented."
|
47
|
+
else
|
48
|
+
"JSON data doesn't have an `object` field or it's not valid JSON data."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class InvalidWhereContitions < StandardError
|
54
|
+
def message
|
55
|
+
"Conditions passed to `Collection#where` must be a hash."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class UnknownAssociation < StandardError
|
60
|
+
attr_reader :klass, :association_name
|
61
|
+
|
62
|
+
def initialize(klass, association_name)
|
63
|
+
@klass = klass
|
64
|
+
@association_name = association_name
|
65
|
+
end
|
66
|
+
|
67
|
+
def message
|
68
|
+
"Association `#{association_name}` could not be found on model `#{klass}`."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class ResourceNotFound < StandardError
|
73
|
+
attr_reader :klass, :id
|
74
|
+
|
75
|
+
def initialize(klass, id)
|
76
|
+
@klass = klass
|
77
|
+
@id = id
|
78
|
+
end
|
79
|
+
|
80
|
+
def message
|
81
|
+
"No `#{klass}` was found with id `#{id}` (API returned 404)"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class APIPathNotSet < StandardError
|
86
|
+
attr_reader :klass
|
87
|
+
|
88
|
+
def initialize(klass)
|
89
|
+
@klass = klass
|
90
|
+
end
|
91
|
+
|
92
|
+
def message
|
93
|
+
"API path not defined in `#{klass}` model. Use `api_path` to define it."
|
11
94
|
end
|
12
95
|
end
|
13
96
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module TimestampAPI
|
2
|
+
class Model
|
3
|
+
attr_reader :json_data
|
4
|
+
|
5
|
+
include Hooks
|
6
|
+
define_hooks :after_initialize, :after_inherited
|
7
|
+
|
8
|
+
include Utils
|
9
|
+
include ModelAttributes
|
10
|
+
include ModelRelations
|
11
|
+
include ModelDefaultAPIMethods
|
12
|
+
|
13
|
+
def self.inherited(subclass)
|
14
|
+
ModelRegistry.register(subclass)
|
15
|
+
run_hook :after_inherited, subclass
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(json_data)
|
19
|
+
@json_data = json_data
|
20
|
+
validate_init_data!
|
21
|
+
run_hook :after_initialize
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def validate_init_data!
|
27
|
+
class_basename = self.class.name.split("::").last
|
28
|
+
raise InvalidModelData.new(class_basename, json_data) unless json_data.is_a?(Hash) && json_data["object"] == class_basename.downcase
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module TimestampAPI
|
2
|
+
module ModelAttributes
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.class_eval do
|
6
|
+
after_initialize do
|
7
|
+
self.class.class_variable_get(:@@attributes).each do |attribute|
|
8
|
+
instance_variable_set(:"@#{attribute}", json_data[camelize(attribute)])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
after_inherited do |subclass|
|
13
|
+
subclass.class_variable_set(:@@attributes, [])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def has_attributes(*attributes)
|
20
|
+
# Add those attributes to the list of attributes for this class
|
21
|
+
self.class_variable_set(:@@attributes, self.class_variable_get(:@@attributes) + attributes)
|
22
|
+
# Define getters for those attributes
|
23
|
+
self.send(:attr_accessor, *attributes)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module TimestampAPI
|
2
|
+
module ModelDefaultAPIMethods
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.class_eval do
|
6
|
+
after_inherited do |subclass|
|
7
|
+
subclass.class_variable_set(:@@api_path, nil)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def api_path(path = nil)
|
14
|
+
path.nil? ? self.class_variable_get(:@@api_path) : self.class_variable_set(:@@api_path, path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def all
|
18
|
+
raise APIPathNotSet.new(self) if api_path.nil?
|
19
|
+
TimestampAPI.request(:get, api_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find(id)
|
23
|
+
return nil if id.nil?
|
24
|
+
raise APIPathNotSet.new(self) if api_path.nil?
|
25
|
+
TimestampAPI.request(:get, "#{api_path}/#{id}")
|
26
|
+
rescue RestClient::ResourceNotFound
|
27
|
+
raise ResourceNotFound.new(self, id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module TimestampAPI
|
2
|
+
class ModelRegistry
|
3
|
+
class << self
|
4
|
+
|
5
|
+
@@registry = {}
|
6
|
+
|
7
|
+
def register(klass)
|
8
|
+
@@registry[registry_key(klass)] = klass unless klass.name.nil?
|
9
|
+
end
|
10
|
+
|
11
|
+
def registry
|
12
|
+
@@registry
|
13
|
+
end
|
14
|
+
|
15
|
+
def model_for(json_data)
|
16
|
+
raise UnknownModelData.new unless json_data.is_a? Hash
|
17
|
+
registry[json_data["object"]] || raise(UnknownModelData.new(json_data["object"]))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def registry_key(klass)
|
23
|
+
klass.name.split("::").last.gsub(/(.)([A-Z])/, '\1_\2').downcase
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module TimestampAPI
|
2
|
+
module ModelRelations
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
base.class_eval do
|
6
|
+
after_initialize do
|
7
|
+
self.class.class_variable_get(:@@belongs_to).each do |association_name|
|
8
|
+
instance_variable_set(:"@_#{association_name}_id", json_data[camelize(association_name)]["id"]) if json_data.has_key? camelize(association_name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
after_inherited do |subclass|
|
13
|
+
subclass.class_variable_set(:@@belongs_to, [])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def belongs_to(association_name)
|
20
|
+
# Add this association to the list of associations for this class
|
21
|
+
self.class_variable_set(:@@belongs_to, self.class_variable_get(:@@belongs_to) + [association_name])
|
22
|
+
# Define a memoizing getter for this association
|
23
|
+
define_method(association_name) do
|
24
|
+
return instance_variable_get(:"@#{association_name}") unless instance_variable_get(:"@#{association_name}").nil?
|
25
|
+
unknown_association_error = UnknownAssociation.new(self, association_name)
|
26
|
+
associationship_id = instance_variable_get(:"@_#{association_name}_id")
|
27
|
+
association_class = ModelRegistry.registry[association_name.to_s] || raise(unknown_association_error)
|
28
|
+
instance_variable_set(:"@#{association_name}", association_class.find(associationship_id))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module TimestampAPI
|
2
|
+
class Project < Model
|
3
|
+
api_path "/projects"
|
4
|
+
|
5
|
+
has_attributes :id, :created_at, :updated_at, :account_id, :name, :code, :color, :initiation_date,
|
6
|
+
:target_completion_date, :is_archived, :is_billable, :is_public, :is_approvable
|
7
|
+
|
8
|
+
belongs_to :client
|
9
|
+
end
|
10
|
+
end
|
data/lib/timestamp_api.rb
CHANGED
@@ -1,41 +1,63 @@
|
|
1
1
|
require "rest-client"
|
2
|
-
require "
|
2
|
+
require "colorize"
|
3
|
+
require "hooks"
|
3
4
|
|
4
5
|
require "timestamp_api/version"
|
5
6
|
require "timestamp_api/errors"
|
6
|
-
require "timestamp_api/
|
7
|
+
require "timestamp_api/utils"
|
8
|
+
require "timestamp_api/model_registry"
|
9
|
+
require "timestamp_api/model_attributes"
|
10
|
+
require "timestamp_api/model_relations"
|
11
|
+
require "timestamp_api/model_default_api_methods"
|
12
|
+
require "timestamp_api/model"
|
13
|
+
require "timestamp_api/collection"
|
14
|
+
require "timestamp_api/models/project"
|
15
|
+
require "timestamp_api/models/client"
|
7
16
|
|
8
17
|
module TimestampAPI
|
9
18
|
@api_endpoint = "https://api.ontimestamp.com/api"
|
10
19
|
|
11
20
|
class << self
|
12
|
-
attr_accessor :api_endpoint, :api_key
|
21
|
+
attr_accessor :api_endpoint, :api_key, :verbose
|
13
22
|
end
|
14
23
|
|
15
|
-
def self.request(method,
|
16
|
-
|
17
|
-
|
24
|
+
def self.request(method, path, query_params = {})
|
25
|
+
output(method, path, query_params) if verbose
|
26
|
+
response = RestClient::Request.execute(request_options(method, path, query_params))
|
27
|
+
modelify(JSON.parse(response))
|
28
|
+
rescue RestClient::Forbidden
|
29
|
+
raise InvalidAPIKey
|
18
30
|
rescue JSON::ParserError
|
19
31
|
raise InvalidServerResponse
|
20
32
|
end
|
21
33
|
|
22
|
-
|
34
|
+
private
|
35
|
+
|
36
|
+
def self.request_options(method, path, query_params)
|
23
37
|
{
|
24
38
|
method: method,
|
25
|
-
url: api_endpoint +
|
39
|
+
url: api_endpoint + path,
|
26
40
|
headers: {
|
27
41
|
"X-API-Key" => api_key || ENV["TIMESTAMP_API_KEY"] || raise(MissingAPIKey),
|
28
42
|
:accept => :json,
|
29
|
-
:user_agent => "TimestampAPI Ruby gem https://github.com/alpinelab/timestamp_api"
|
43
|
+
:user_agent => "TimestampAPI Ruby gem (https://github.com/alpinelab/timestamp_api)",
|
44
|
+
:params => query_params
|
30
45
|
}
|
31
46
|
}
|
32
47
|
end
|
33
48
|
|
34
|
-
def self.
|
49
|
+
def self.modelify(json)
|
35
50
|
case json
|
36
|
-
when Array then json.map { |item|
|
37
|
-
when Hash then
|
38
|
-
else json
|
51
|
+
when Array then Collection.new(json.map { |item| modelify(item) })
|
52
|
+
when Hash then ModelRegistry.model_for(json).new(json)
|
39
53
|
end
|
40
54
|
end
|
55
|
+
|
56
|
+
def self.output(method, path, query_params)
|
57
|
+
print "TimestampAPI ".colorize(:red)
|
58
|
+
print "#{method.upcase} ".colorize(:yellow)
|
59
|
+
full_path = path
|
60
|
+
full_path += "?#{query_params.each_with_object([]) { |p, acc| acc << "#{p[0]}=#{p[1]}" }.join("&")}" unless query_params.empty?
|
61
|
+
puts full_path.colorize(:yellow)
|
62
|
+
end
|
41
63
|
end
|
data/timestamp_api.gemspec
CHANGED
@@ -19,12 +19,14 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.add_runtime_dependency "rest-client"
|
23
|
-
spec.add_runtime_dependency "
|
22
|
+
spec.add_runtime_dependency "rest-client", ">= 1.7.3"
|
23
|
+
spec.add_runtime_dependency "colorize"
|
24
|
+
spec.add_runtime_dependency "hooks"
|
24
25
|
|
25
26
|
spec.add_development_dependency "bundler", "~> 1.9"
|
26
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
28
|
spec.add_development_dependency "rspec"
|
28
29
|
spec.add_development_dependency "pry"
|
29
30
|
spec.add_development_dependency "awesome_print"
|
31
|
+
spec.add_development_dependency "webmock", "~> 1.22"
|
30
32
|
end
|
metadata
CHANGED
@@ -1,17 +1,31 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timestamp_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Baudino
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.7.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.7.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: colorize
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - ">="
|
@@ -25,7 +39,7 @@ dependencies:
|
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
42
|
+
name: hooks
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - ">="
|
@@ -108,6 +122,20 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: webmock
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '1.22'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '1.22'
|
111
139
|
description: 'Timestamp is "real-time project tracking for you and your clients" according
|
112
140
|
to their website: https://www.timestamphq.com'
|
113
141
|
email:
|
@@ -129,8 +157,16 @@ files:
|
|
129
157
|
- bin/console
|
130
158
|
- bin/setup
|
131
159
|
- lib/timestamp_api.rb
|
160
|
+
- lib/timestamp_api/collection.rb
|
132
161
|
- lib/timestamp_api/errors.rb
|
133
|
-
- lib/timestamp_api/
|
162
|
+
- lib/timestamp_api/model.rb
|
163
|
+
- lib/timestamp_api/model_attributes.rb
|
164
|
+
- lib/timestamp_api/model_default_api_methods.rb
|
165
|
+
- lib/timestamp_api/model_registry.rb
|
166
|
+
- lib/timestamp_api/model_relations.rb
|
167
|
+
- lib/timestamp_api/models/client.rb
|
168
|
+
- lib/timestamp_api/models/project.rb
|
169
|
+
- lib/timestamp_api/utils.rb
|
134
170
|
- lib/timestamp_api/version.rb
|
135
171
|
- timestamp_api.gemspec
|
136
172
|
homepage: https://github.com/alpinelab/timestamp_api
|