moco-ruby 0.1.2 → 1.0.0.alpha
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -3
- data/CHANGELOG.md +65 -4
- data/Gemfile +2 -1
- data/Gemfile.lock +70 -23
- data/README.md +199 -55
- data/examples/v2_api_example.rb +73 -0
- data/lib/moco/client.rb +47 -0
- data/lib/moco/collection_proxy.rb +190 -0
- data/lib/moco/connection.rb +62 -0
- data/lib/moco/entities/activity.rb +96 -0
- data/lib/moco/entities/base_entity.rb +303 -0
- data/lib/moco/entities/company.rb +28 -0
- data/lib/moco/entities/deal.rb +24 -0
- data/lib/moco/entities/expense.rb +29 -0
- data/lib/moco/entities/holiday.rb +25 -0
- data/lib/moco/entities/invoice.rb +53 -0
- data/lib/moco/entities/planning_entry.rb +26 -0
- data/lib/moco/entities/presence.rb +30 -0
- data/lib/moco/entities/project.rb +39 -0
- data/lib/moco/entities/schedule.rb +26 -0
- data/lib/moco/entities/task.rb +20 -0
- data/lib/moco/entities/user.rb +33 -0
- data/lib/moco/entities/web_hook.rb +27 -0
- data/lib/moco/entities.rb +11 -4
- data/lib/moco/entity_collection.rb +59 -0
- data/lib/moco/helpers.rb +1 -0
- data/lib/moco/nested_collection_proxy.rb +40 -0
- data/lib/moco/sync.rb +70 -29
- data/lib/moco/version.rb +1 -1
- data/lib/moco.rb +26 -2
- data/mocurl.rb +51 -34
- data/sync_activity.rb +4 -4
- metadata +43 -10
- data/lib/moco/api.rb +0 -194
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cda64cf9e66f0409e61b5fc1b0ad5b0bc440acfe3e1202d9baab08c87c21d6b1
|
4
|
+
data.tar.gz: cc6e0952ee9c89a4ac73f08c0093f1723563346f8bd2cfec27901faeed943ae8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3bc2329dca452e3efbecc83b015532f5f32102d4e26a8bd787e036df3ac2384ac6f3ae95b0a67999cb9858197357cc0ee5beb744ad07168268e3067d6f774e7
|
7
|
+
data.tar.gz: 8189ab5b3015b78d1c93c0231497283e1d1d89ef1673c02d0762f161fcb6d277e4350f052fe799b80993b14cd645db0290ee0379c9455c840abe3646afa54415
|
data/.rubocop.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
AllCops:
|
2
|
-
TargetRubyVersion: 2
|
2
|
+
TargetRubyVersion: 3.2
|
3
3
|
SuggestExtensions: false
|
4
4
|
NewCops: enable
|
5
5
|
|
@@ -15,12 +15,19 @@ Naming/MethodParameterName:
|
|
15
15
|
AllowedNames:
|
16
16
|
- a
|
17
17
|
- b
|
18
|
+
- id
|
18
19
|
|
19
20
|
Layout/LineLength:
|
20
21
|
Max: 130
|
21
22
|
|
22
23
|
Metrics/BlockLength:
|
23
|
-
|
24
|
+
Enabled: false
|
24
25
|
|
25
26
|
Metrics/ClassLength:
|
26
|
-
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Metrics/MethodLength:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Metrics/AbcSize:
|
33
|
+
Enabled: false
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,64 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [1.0.0.alpha] - 2025-04-10
|
6
|
+
|
7
|
+
### Added
|
8
|
+
- Added support for nested resources with `NestedCollectionProxy` class:
|
9
|
+
- Enables ActiveRecord-style operations on nested resources (e.g., `project.tasks.create`)
|
10
|
+
- Supports proper path construction for nested API endpoints
|
11
|
+
- Implements `destroy_all` method for bulk deletion of nested resources
|
12
|
+
|
13
|
+
## [1.0.0] - 2025-04-10
|
14
|
+
|
15
|
+
### Added
|
16
|
+
- Implemented ActiveRecord-style query interface (`where`, `find`, `find_by`, `first`, `all`, `each`) via `CollectionProxy`.
|
17
|
+
- Added ActiveRecord-style persistence methods to `BaseEntity`:
|
18
|
+
- `save` - Persist changes to an entity
|
19
|
+
- `update` - Update attributes and save in one step
|
20
|
+
- `destroy` - Delete an entity
|
21
|
+
- `reload` - Refresh an entity from the API
|
22
|
+
- Added `has_many` method to `BaseEntity` for handling one-to-many associations, complementing the existing `association` method for one-to-one associations.
|
23
|
+
- Refactored entity association methods in `Project`, `User`, and `Company` classes to use the new `has_many` method.
|
24
|
+
- Added comprehensive project lifecycle test that creates a project, adds tasks and activities, then cleans up.
|
25
|
+
- Added support for nested resources with `NestedCollectionProxy` class:
|
26
|
+
- Enables ActiveRecord-style operations on nested resources (e.g., `project.tasks.create`)
|
27
|
+
- Supports proper path construction for nested API endpoints
|
28
|
+
- Implements `destroy_all` method for bulk deletion of nested resources
|
29
|
+
- Complete redesign with Ruby-esque API
|
30
|
+
- Chainable methods for fluent interface
|
31
|
+
- Dynamic entity creation with Rails-style inflection
|
32
|
+
- Comprehensive entity coverage for all MOCO API endpoints:
|
33
|
+
- Project
|
34
|
+
- Activity
|
35
|
+
- User
|
36
|
+
- Company
|
37
|
+
- Task
|
38
|
+
- Invoice
|
39
|
+
- Deal
|
40
|
+
- Expense
|
41
|
+
- WebHook
|
42
|
+
- Schedule
|
43
|
+
- Presence
|
44
|
+
- Holiday
|
45
|
+
- PlanningEntry
|
46
|
+
- Entity association methods for related entities
|
47
|
+
- Automatic entity creation from API responses
|
48
|
+
- Struct-based fallback for unknown entity types
|
49
|
+
- Generic association method for handling relationships between entities
|
50
|
+
|
51
|
+
### Changed
|
52
|
+
- Added ActiveSupport dependency for inflection methods
|
53
|
+
- Reorganized code structure for better maintainability
|
54
|
+
- Updated documentation with new API examples
|
55
|
+
- `MOCO::Activity#to_s` now uses `Helpers.decimal_hours_to_civil` for improved time display.
|
56
|
+
- Refined core components like `Client`, `CollectionProxy`, and `Connection`.
|
57
|
+
- Updated various entity classes (`Activity`, `BaseEntity`, `Presence`, `Project`) with internal improvements.
|
58
|
+
- Updated utility scripts (`mocurl.rb`, `sync_activity.rb`).
|
59
|
+
|
60
|
+
### Removed
|
61
|
+
- Legacy API (`lib/moco/api.rb`) as part of the transition to the new client.
|
62
|
+
|
5
63
|
## [0.1.2] - 2025-04-02
|
6
64
|
|
7
65
|
### Added
|
@@ -19,18 +77,21 @@
|
|
19
77
|
## [0.1.1] - 2024-02-27
|
20
78
|
|
21
79
|
### Added
|
22
|
-
|
23
80
|
- Prepared for Gem release
|
24
81
|
|
25
82
|
### Changed
|
26
|
-
|
27
83
|
- Changed target Ruby version to 2.6 (from 3.x)
|
28
84
|
- Applied Rubocop configuration and fixed style errors
|
29
85
|
|
30
86
|
### Security
|
31
|
-
|
32
87
|
- Bumped `uri` dependency to 0.13.0
|
33
88
|
|
34
89
|
## [0.1.0] - 2024-02-27
|
35
|
-
|
36
90
|
- Initial release
|
91
|
+
|
92
|
+
[Unreleased]: https://github.com/starsong-consulting/moco-ruby/compare/v1.0.0.alpha...HEAD
|
93
|
+
[1.0.0.alpha]: https://github.com/starsong-consulting/moco-ruby/compare/v1.0.0...v1.0.0.alpha
|
94
|
+
[1.0.0]: https://github.com/starsong-consulting/moco-ruby/compare/v0.1.2...v1.0.0
|
95
|
+
[0.1.2]: https://github.com/starsong-consulting/moco-ruby/compare/v0.1.1...v0.1.2
|
96
|
+
[0.1.1]: https://github.com/starsong-consulting/moco-ruby/compare/v0.1.0...v0.1.1
|
97
|
+
[0.1.0]: https://github.com/starsong-consulting/moco-ruby/releases/tag/v0.1.0
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,48 +1,93 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
moco-ruby (0.
|
4
|
+
moco-ruby (1.0.0.alpha)
|
5
|
+
activesupport (~> 7.0)
|
5
6
|
faraday (~> 2.9.0)
|
6
7
|
fuzzy_match (~> 2.1.0)
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
10
11
|
specs:
|
11
|
-
|
12
|
-
|
12
|
+
activesupport (7.2.2.1)
|
13
|
+
base64
|
14
|
+
benchmark (>= 0.3)
|
15
|
+
bigdecimal
|
16
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
17
|
+
connection_pool (>= 2.2.5)
|
18
|
+
drb
|
19
|
+
i18n (>= 1.6, < 2)
|
20
|
+
logger (>= 1.4.2)
|
21
|
+
minitest (>= 5.1)
|
22
|
+
securerandom (>= 0.3)
|
23
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
24
|
+
addressable (2.8.7)
|
25
|
+
public_suffix (>= 2.0.2, < 7.0)
|
26
|
+
ast (2.4.3)
|
27
|
+
base64 (0.2.0)
|
28
|
+
benchmark (0.4.0)
|
29
|
+
bigdecimal (3.1.9)
|
30
|
+
concurrent-ruby (1.3.5)
|
31
|
+
connection_pool (2.5.0)
|
32
|
+
crack (1.0.0)
|
33
|
+
bigdecimal
|
34
|
+
rexml
|
35
|
+
drb (2.2.1)
|
36
|
+
faraday (2.9.2)
|
13
37
|
faraday-net_http (>= 2.0, < 3.2)
|
14
|
-
faraday-net_http (3.1.
|
38
|
+
faraday-net_http (3.1.1)
|
15
39
|
net-http
|
16
40
|
fuzzy_match (2.1.0)
|
17
|
-
|
18
|
-
|
19
|
-
|
41
|
+
hashdiff (1.1.2)
|
42
|
+
i18n (1.14.7)
|
43
|
+
concurrent-ruby (~> 1.0)
|
44
|
+
json (2.10.2)
|
45
|
+
language_server-protocol (3.17.0.4)
|
46
|
+
lint_roller (1.1.0)
|
47
|
+
logger (1.7.0)
|
48
|
+
minitest (5.25.5)
|
49
|
+
net-http (0.6.0)
|
20
50
|
uri
|
21
|
-
parallel (1.
|
22
|
-
parser (3.3.
|
51
|
+
parallel (1.26.3)
|
52
|
+
parser (3.3.7.4)
|
23
53
|
ast (~> 2.4.1)
|
24
54
|
racc
|
25
|
-
|
55
|
+
power_assert (2.0.5)
|
56
|
+
prism (1.4.0)
|
57
|
+
public_suffix (6.0.1)
|
58
|
+
racc (1.8.1)
|
26
59
|
rainbow (3.1.1)
|
27
|
-
rake (13.1
|
28
|
-
regexp_parser (2.
|
29
|
-
rexml (3.
|
30
|
-
rubocop (1.
|
60
|
+
rake (13.2.1)
|
61
|
+
regexp_parser (2.10.0)
|
62
|
+
rexml (3.4.1)
|
63
|
+
rubocop (1.75.2)
|
31
64
|
json (~> 2.3)
|
32
|
-
language_server-protocol (
|
65
|
+
language_server-protocol (~> 3.17.0.2)
|
66
|
+
lint_roller (~> 1.1.0)
|
33
67
|
parallel (~> 1.10)
|
34
68
|
parser (>= 3.3.0.2)
|
35
69
|
rainbow (>= 2.2.2, < 4.0)
|
36
|
-
regexp_parser (>=
|
37
|
-
|
38
|
-
rubocop-ast (>= 1.30.0, < 2.0)
|
70
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
71
|
+
rubocop-ast (>= 1.44.0, < 2.0)
|
39
72
|
ruby-progressbar (~> 1.7)
|
40
|
-
unicode-display_width (>= 2.4.0, <
|
41
|
-
rubocop-ast (1.
|
42
|
-
parser (>= 3.
|
73
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
74
|
+
rubocop-ast (1.44.0)
|
75
|
+
parser (>= 3.3.7.2)
|
76
|
+
prism (~> 1.4)
|
43
77
|
ruby-progressbar (1.13.0)
|
44
|
-
|
45
|
-
|
78
|
+
securerandom (0.4.1)
|
79
|
+
test-unit (3.6.8)
|
80
|
+
power_assert
|
81
|
+
tzinfo (2.0.6)
|
82
|
+
concurrent-ruby (~> 1.0)
|
83
|
+
unicode-display_width (3.1.4)
|
84
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
85
|
+
unicode-emoji (4.0.4)
|
86
|
+
uri (1.0.3)
|
87
|
+
webmock (3.25.1)
|
88
|
+
addressable (>= 2.8.0)
|
89
|
+
crack (>= 0.3.2)
|
90
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
46
91
|
|
47
92
|
PLATFORMS
|
48
93
|
arm64-darwin-22
|
@@ -52,6 +97,8 @@ DEPENDENCIES
|
|
52
97
|
moco-ruby!
|
53
98
|
rake (~> 13.0)
|
54
99
|
rubocop (~> 1.21)
|
100
|
+
test-unit (~> 3.5)
|
101
|
+
webmock (~> 3.18)
|
55
102
|
|
56
103
|
BUNDLED WITH
|
57
104
|
2.4.1
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# MOCO Ruby Gem
|
2
2
|
|
3
|
-
|
3
|
+
[](https://badge.fury.io/rb/moco-ruby)
|
4
|
+
|
5
|
+
A Ruby Gem to interact with the [MOCO API](https://hundertzehn.github.io/mocoapp-api-docs/). This gem provides a modern, Ruby-esque interface (`MOCO::Client`) for interacting with the MOCO API.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -14,88 +16,230 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
14
16
|
|
15
17
|
## Usage
|
16
18
|
|
17
|
-
###
|
19
|
+
### Initialization
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'moco'
|
23
|
+
|
24
|
+
moco = MOCO::Client.new(
|
25
|
+
subdomain: "your-subdomain", # Your MOCO subdomain
|
26
|
+
api_key: "your-api-key" # Your MOCO API key
|
27
|
+
)
|
28
|
+
```
|
29
|
+
|
30
|
+
### Accessing Collections
|
31
|
+
|
32
|
+
Collections are accessed dynamically using pluralized entity names (following Rails conventions):
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
projects = moco.projects
|
36
|
+
activities = moco.activities
|
37
|
+
users = moco.users
|
38
|
+
# ... and so on for all supported entities
|
39
|
+
```
|
40
|
+
|
41
|
+
### Fetching Entities
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
# Get all entities in a collection
|
45
|
+
all_projects = moco.projects.all
|
46
|
+
|
47
|
+
# Get entities matching specific criteria
|
48
|
+
active_projects = moco.projects.where(active: true)
|
49
|
+
recent_activities = moco.activities.where(date: ">=2024-01-01")
|
50
|
+
|
51
|
+
# Get a specific entity by ID
|
52
|
+
project = moco.projects.find(12345)
|
53
|
+
user = moco.users.find(678)
|
54
|
+
```
|
55
|
+
|
56
|
+
### Creating Entities
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# Create a new project
|
60
|
+
new_project = moco.projects.create(
|
61
|
+
name: "New Website Project",
|
62
|
+
customer_id: 987,
|
63
|
+
billable: true
|
64
|
+
)
|
65
|
+
puts "Created project: #{new_project.name} (ID: #{new_project.id})"
|
66
|
+
|
67
|
+
# Create a new time entry (activity)
|
68
|
+
new_activity = moco.activities.create(
|
69
|
+
date: "2024-04-10",
|
70
|
+
project_id: new_project.id,
|
71
|
+
task_id: new_project.tasks.first.id, # Assumes project has tasks
|
72
|
+
hours: 3.5,
|
73
|
+
description: "Implemented feature X"
|
74
|
+
)
|
75
|
+
puts "Created activity: #{new_activity.description} on #{new_activity.date}"
|
76
|
+
```
|
77
|
+
|
78
|
+
### Updating Entities
|
79
|
+
|
80
|
+
Modify attributes and call `save`:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
project = moco.projects.find(12345)
|
84
|
+
project.name = "Updated Project Name"
|
85
|
+
project.billable = false
|
86
|
+
|
87
|
+
if project.save
|
88
|
+
puts "Project updated successfully."
|
89
|
+
else
|
90
|
+
puts "Failed to update project: #{project.errors.full_messages.join(", ")}" # Assuming error handling exists
|
91
|
+
end
|
92
|
+
|
93
|
+
# You can also update directly via the collection
|
94
|
+
moco.projects.update(12345, name: "Another Update", active: false)
|
95
|
+
```
|
96
|
+
|
97
|
+
### Deleting Entities
|
18
98
|
|
19
99
|
```ruby
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
puts "
|
24
|
-
|
25
|
-
|
26
|
-
|
100
|
+
# Delete by object
|
101
|
+
activity = moco.activities.find(9876)
|
102
|
+
if activity&.delete
|
103
|
+
puts "Activity deleted."
|
104
|
+
end
|
105
|
+
|
106
|
+
# Delete by ID via collection
|
107
|
+
if moco.activities.delete(9876)
|
108
|
+
puts "Activity deleted."
|
27
109
|
end
|
28
110
|
```
|
29
111
|
|
30
|
-
###
|
112
|
+
### Entity Associations
|
113
|
+
|
114
|
+
Entities provide methods to access related entities easily:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
project = moco.projects.find(12345)
|
118
|
+
|
119
|
+
# Get tasks associated with the project
|
120
|
+
tasks = project.tasks # Returns a collection proxy for tasks
|
121
|
+
puts "Tasks for project '#{project.name}': #{tasks.map(&:name).join(', ')}"
|
31
122
|
|
32
|
-
|
123
|
+
# Get activities for the project
|
124
|
+
project_activities = project.activities
|
125
|
+
puts "Activities count: #{project_activities.size}"
|
33
126
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
- Customer (:id, :name)
|
38
|
-
- User (:id, :firstname, :lastname)
|
127
|
+
# Get the customer (company) for the project
|
128
|
+
customer = project.customer # Returns a MOCO::Company object
|
129
|
+
puts "Customer: #{customer.name}"
|
39
130
|
|
40
|
-
|
131
|
+
# ---
|
41
132
|
|
42
|
-
|
133
|
+
activity = moco.activities.find(9876)
|
134
|
+
|
135
|
+
# Get the associated project, task, and user
|
136
|
+
act_project = activity.project
|
137
|
+
act_task = activity.task
|
138
|
+
act_user = activity.user
|
139
|
+
puts "Activity by #{act_user.firstname} on project '#{act_project.name}' (Task: #{act_task.name})"
|
140
|
+
```
|
43
141
|
|
44
|
-
|
45
|
-
|
46
|
-
|
142
|
+
### Nested Resources
|
143
|
+
|
144
|
+
The gem supports ActiveRecord-style operations on nested resources:
|
47
145
|
|
48
146
|
```ruby
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
source: options.slice(:from, :to, :project_id, :company_id, :term),
|
59
|
-
target: options.slice(:from, :to)
|
60
|
-
},
|
61
|
-
dry_run: options[:dry_run]
|
147
|
+
project = moco.projects.find(12345)
|
148
|
+
|
149
|
+
# Create a new task for the project
|
150
|
+
new_task = project.tasks.create(
|
151
|
+
name: "New Feature Development",
|
152
|
+
billable: true,
|
153
|
+
active: true,
|
154
|
+
budget: 40,
|
155
|
+
hourly_rate: 120
|
62
156
|
)
|
157
|
+
puts "Created task: #{new_task.name} (ID: #{new_task.id})"
|
158
|
+
|
159
|
+
# Delete all tasks for a project
|
160
|
+
project.tasks.destroy_all
|
161
|
+
|
162
|
+
# Query tasks with conditions
|
163
|
+
billable_tasks = project.tasks.where(billable: true).all
|
164
|
+
puts "Billable tasks: #{billable_tasks.map(&:name).join(', ')}"
|
165
|
+
|
166
|
+
# Find a specific task
|
167
|
+
dev_task = project.tasks.find_by(name: "Development")
|
63
168
|
```
|
64
169
|
|
170
|
+
### Supported Entities
|
171
|
+
|
172
|
+
The gem supports all MOCO API entities with a Ruby-esque interface:
|
173
|
+
|
174
|
+
- `Project`
|
175
|
+
- `Activity`
|
176
|
+
- `User`
|
177
|
+
- `Company`
|
178
|
+
- `Task`
|
179
|
+
- `Invoice`
|
180
|
+
- `Deal`
|
181
|
+
- `Expense`
|
182
|
+
- `WebHook`
|
183
|
+
- `Schedule`
|
184
|
+
- `Presence`
|
185
|
+
- `Holiday`
|
186
|
+
- `PlanningEntry`
|
187
|
+
|
188
|
+
Access them via the moco using their plural, snake_case names (e.g., `moco.planning_entries`).
|
189
|
+
|
65
190
|
## Utilities
|
66
191
|
|
67
|
-
|
192
|
+
These command-line utilities provide helpful shortcuts. They can use credentials and configuration from a `config.yml` file (see `config.yml.sample`) in the current directory or accept parameters.
|
68
193
|
|
69
|
-
### mocurl
|
194
|
+
### `mocurl`
|
70
195
|
|
71
|
-
|
72
|
-
Use config.yml or specify api key with `-a`.
|
196
|
+
A wrapper around `curl` to easily make authenticated requests to your MOCO instance API. Useful for testing or exploring endpoints.
|
73
197
|
|
74
198
|
```
|
75
199
|
Usage: mocurl.rb [options] url
|
76
200
|
mocurl.rb [options] subdomain path
|
77
|
-
-X, --method METHOD Set HTTP method to use
|
78
|
-
-d, --data DATA Data to send to server
|
79
|
-
-a, --api-key API_KEY Manually specify MOCO API key
|
80
|
-
-n, --no-format Disable JSON pretty-printing
|
201
|
+
-X, --method METHOD Set HTTP method to use (GET, POST, PUT, DELETE)
|
202
|
+
-d, --data DATA Data to send to server (JSON format) for POST/PUT
|
203
|
+
-a, --api-key API_KEY Manually specify MOCO API key (overrides config.yml)
|
204
|
+
-n, --no-format Disable JSON pretty-printing of the response
|
81
205
|
-v, --verbose Show additional request and response information
|
82
206
|
-h, --help Show this message
|
83
207
|
```
|
208
|
+
**Example:** `mocurl.rb your-subdomain projects/12345`
|
209
|
+
|
210
|
+
### `sync_activity`
|
84
211
|
|
85
|
-
|
212
|
+
Syncs activity data (time entries) between two MOCO instances (source and target). It uses fuzzy matching to map projects and tasks between the instances.
|
86
213
|
|
87
|
-
|
88
|
-
|
214
|
+
**Important:**
|
215
|
+
* Always use `--dry-run` first to verify the matching and intended actions.
|
216
|
+
* Use date filters (`--from`, `--to`) to limit the scope.
|
89
217
|
|
90
218
|
```
|
91
|
-
Usage: sync_activity.rb [options]
|
92
|
-
-f, --from DATE Start date (YYYY-MM-DD)
|
93
|
-
-t, --to DATE End date (YYYY-MM-DD)
|
94
|
-
-p, --project PROJECT_ID Project ID to filter by
|
95
|
-
-c, --company COMPANY_ID Company ID to filter by
|
96
|
-
-g, --term TERM Term to filter
|
97
|
-
-n, --dry-run
|
219
|
+
Usage: sync_activity.rb [options] source_subdomain target_subdomain
|
220
|
+
-f, --from DATE Start date for sync (YYYY-MM-DD)
|
221
|
+
-t, --to DATE End date for sync (YYYY-MM-DD)
|
222
|
+
-p, --project PROJECT_ID Source Project ID to filter by (optional)
|
223
|
+
-c, --company COMPANY_ID Source Company ID to filter by (optional)
|
224
|
+
-g, --term TERM Term to filter source activities by (optional)
|
225
|
+
-n, --dry-run Perform matching and show planned actions, but do not modify target instance
|
98
226
|
--match-project-threshold VALUE
|
99
|
-
|
100
|
-
--match-task-threshold VALUE
|
227
|
+
Fuzzy match threshold for projects (0.0 - 1.0), default 0.8
|
228
|
+
--match-task-threshold VALUE Fuzzy match threshold for tasks (0.0 - 1.0), default 0.45
|
229
|
+
-h, --help Show this message
|
101
230
|
```
|
231
|
+
**Example:** `sync_activity.rb --from 2024-04-01 --to 2024-04-10 --dry-run source-instance target-instance`
|
232
|
+
|
233
|
+
## Development
|
234
|
+
|
235
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
236
|
+
|
237
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
238
|
+
|
239
|
+
## Contributing
|
240
|
+
|
241
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/starsong-consulting/moco-ruby.
|
242
|
+
|
243
|
+
## License
|
244
|
+
|
245
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "moco"
|
6
|
+
require "yaml"
|
7
|
+
|
8
|
+
# Load configuration from config.yml
|
9
|
+
config = YAML.load_file(File.join(File.dirname(__FILE__), "..", "config.yml"))
|
10
|
+
instance = config["instances"].first
|
11
|
+
|
12
|
+
# Initialize client
|
13
|
+
moco = MOCO::Client.new(
|
14
|
+
subdomain: instance["subdomain"],
|
15
|
+
api_key: instance["api_key"]
|
16
|
+
)
|
17
|
+
|
18
|
+
puts "Connected to MOCO instance: #{instance["subdomain"]}"
|
19
|
+
|
20
|
+
# Get all active projects
|
21
|
+
puts "\nActive Projects:"
|
22
|
+
projects = moco.projects.where(active: "true")
|
23
|
+
projects.each do |project|
|
24
|
+
puts "- #{project.id}: #{project.name} (#{project.customer&.name})"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get a specific project
|
28
|
+
if projects.any?
|
29
|
+
project = projects.first
|
30
|
+
puts "\nProject Details for #{project.name}:"
|
31
|
+
puts " Customer: #{project.customer&.name}"
|
32
|
+
|
33
|
+
# Get tasks for the project
|
34
|
+
puts " Tasks:"
|
35
|
+
project.tasks.each do |task|
|
36
|
+
puts " - #{task.name} (#{task.billable ? "Billable" : "Non-billable"})"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get recent activities for the project
|
40
|
+
puts "\nRecent Activities for #{project.name}:"
|
41
|
+
activities = project.activities
|
42
|
+
activities.each do |activity|
|
43
|
+
puts " - #{activity.date}: #{activity.hours}h - #{activity.description} (#{activity.user&.full_name})"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Demonstrate chaining (commented out to avoid modifying data)
|
47
|
+
# project.archive.assign_to_group(123).unarchive
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get users
|
51
|
+
puts "\nUsers:"
|
52
|
+
users = moco.users.all
|
53
|
+
users.each do |user|
|
54
|
+
puts "- #{user.id}: #{user.full_name}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Dynamic access to any collection
|
58
|
+
puts "\nDemonstrating dynamic collection access:"
|
59
|
+
collections = %w[companies deals invoices expenses schedules presences holidays planning_entries]
|
60
|
+
collections.each do |collection|
|
61
|
+
if moco.respond_to?(collection)
|
62
|
+
count = begin
|
63
|
+
moco.send(collection).count
|
64
|
+
rescue StandardError
|
65
|
+
0
|
66
|
+
end
|
67
|
+
puts "- #{collection}: #{count} items"
|
68
|
+
else
|
69
|
+
puts "- #{collection}: not available"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
puts "\nExample completed successfully!"
|
data/lib/moco/client.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MOCO
|
4
|
+
# Main client class for interacting with the MOCO API
|
5
|
+
# Provides dynamic access to all API endpoints through method_missing
|
6
|
+
class Client
|
7
|
+
attr_reader :connection
|
8
|
+
|
9
|
+
def initialize(subdomain:, api_key:)
|
10
|
+
@connection = Connection.new(self, subdomain, api_key)
|
11
|
+
@collections = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Dynamically handle entity collection access (e.g., client.projects)
|
15
|
+
def method_missing(name, *args, &)
|
16
|
+
# Check if the method name corresponds to a known plural entity type
|
17
|
+
if collection_name?(name)
|
18
|
+
# Return a CollectionProxy directly for chainable queries
|
19
|
+
# Cache it so subsequent calls return the same proxy instance
|
20
|
+
@collections[name] ||= CollectionProxy.new(
|
21
|
+
self,
|
22
|
+
name.to_s, # Pass the plural name (e.g., "projects") as the path hint
|
23
|
+
ActiveSupport::Inflector.classify(name.to_s) # Get class name (e.g., "Project")
|
24
|
+
)
|
25
|
+
else
|
26
|
+
# Delegate to superclass for non-collection methods
|
27
|
+
super
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def respond_to_missing?(name, include_private = false)
|
32
|
+
collection_name?(name) || super
|
33
|
+
end
|
34
|
+
|
35
|
+
# Check if the method name looks like a collection name (plural)
|
36
|
+
def collection_name?(name)
|
37
|
+
name.to_s == ActiveSupport::Inflector.pluralize(name.to_s)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Delegate HTTP methods to connection
|
41
|
+
%i[get post put patch delete].each do |method|
|
42
|
+
define_method(method) do |path, params = {}|
|
43
|
+
connection.send(method, path, params)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|