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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 781b3fd8aed069892a3c387056616917556da295b17878e23e3ec3b8aa8dd8fb
4
- data.tar.gz: 1a818a32e9910e7ab30fd978e4e39348dcb7e970717b60e3f2f2b7dd755c6fbb
3
+ metadata.gz: cda64cf9e66f0409e61b5fc1b0ad5b0bc440acfe3e1202d9baab08c87c21d6b1
4
+ data.tar.gz: cc6e0952ee9c89a4ac73f08c0093f1723563346f8bd2cfec27901faeed943ae8
5
5
  SHA512:
6
- metadata.gz: aa1352b7183998b30397668eca16ca9eefbb99659f72fb60812eb14585f284789808defa0e8dfb147e6327a4d540bccf1e0531c3c3946f6753447bee347e3942
7
- data.tar.gz: 367888eed8cb0a9bc91d2301a28e9f57ab1cc57f9226e5bd09a91fb71fe52c4220ff65bcb0e2fd7aae903fc12107be37f46e5df7ce27636451a1e7d3800e6807
6
+ metadata.gz: f3bc2329dca452e3efbecc83b015532f5f32102d4e26a8bd787e036df3ac2384ac6f3ae95b0a67999cb9858197357cc0ee5beb744ad07168268e3067d6f774e7
7
+ data.tar.gz: 8189ab5b3015b78d1c93c0231497283e1d1d89ef1673c02d0762f161fcb6d277e4350f052fe799b80993b14cd645db0290ee0379c9455c840abe3646afa54415
data/.rubocop.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.6
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
- Max: 40
24
+ Enabled: false
24
25
 
25
26
  Metrics/ClassLength:
26
- Max: 130
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
@@ -5,5 +5,6 @@ source "https://rubygems.org/"
5
5
  gemspec
6
6
 
7
7
  gem "rake", "~> 13.0"
8
-
9
8
  gem "rubocop", "~> 1.21"
9
+ gem "test-unit", "~> 3.5"
10
+ gem "webmock", "~> 3.18"
data/Gemfile.lock CHANGED
@@ -1,48 +1,93 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- moco-ruby (0.1.1)
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
- ast (2.4.2)
12
- faraday (2.9.0)
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.0)
38
+ faraday-net_http (3.1.1)
15
39
  net-http
16
40
  fuzzy_match (2.1.0)
17
- json (2.7.1)
18
- language_server-protocol (3.17.0.3)
19
- net-http (0.4.1)
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.24.0)
22
- parser (3.3.0.5)
51
+ parallel (1.26.3)
52
+ parser (3.3.7.4)
23
53
  ast (~> 2.4.1)
24
54
  racc
25
- racc (1.7.3)
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.0)
28
- regexp_parser (2.9.0)
29
- rexml (3.2.6)
30
- rubocop (1.60.2)
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 (>= 3.17.0)
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 (>= 1.8, < 3.0)
37
- rexml (>= 3.2.5, < 4.0)
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, < 3.0)
41
- rubocop-ast (1.30.0)
42
- parser (>= 3.2.1.0)
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
- unicode-display_width (2.5.0)
45
- uri (0.13.0)
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
- # moco-ruby
1
+ # MOCO Ruby Gem
2
2
 
3
- A Ruby Gem to interact with the [MOCO API](https://hundertzehn.github.io/mocoapp-api-docs/).
3
+ [![Gem Version](https://badge.fury.io/rb/moco-ruby.svg)](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
- ### MOCO::API
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
- moco = MOCO::API.new(subdomain, api_key)
21
- assigned_projects = moco.get_assigned_projects(active: 'true')
22
- assigned_projects.each do |project|
23
- puts "Project \##{project.id} #{project.name} for customer #{project.customer.name}"
24
- project.tasks.each do |task|
25
- puts "- Task #{task.name} is #{task.billable ? 'billable' : 'not billable'}"
26
- end
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
- ### MOCO::Entities
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
- The following entities are currently defined:
123
+ # Get activities for the project
124
+ project_activities = project.activities
125
+ puts "Activities count: #{project_activities.size}"
33
126
 
34
- - Project (:id, :active, :name, :customer, :tasks)
35
- - Task (:id, :active, :name, :project_id, :billable)
36
- - Activity (:id, :active, :date, :description, :project, :task, :seconds, :hours, :billable, :billed, :user, :customer, :tag)
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
- The entities implement comparison, hash and JSON conversion.
131
+ # ---
41
132
 
42
- ### MOCO::Sync
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
- Intelligently matches and syncs data from one MOCO instance to another.
45
- Currently supports activities (time entries) only.
46
- See `sync_activity.rb` for a more detailed example.
142
+ ### Nested Resources
143
+
144
+ The gem supports ActiveRecord-style operations on nested resources:
47
145
 
48
146
  ```ruby
49
- source_api = MOCO::API.new(source_instance, source_api_key)
50
- target_api = MOCO::API.new(target_instance, target_api_key)
51
-
52
- syncer = MOCO::Sync.new(
53
- source_api,
54
- target_api,
55
- project_match_threshold: options[:match_project_threshold],
56
- task_match_threshold: options[:match_task_threshold],
57
- filters: {
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
- Utilities can use `config.yml` to fetch instance data and other configuration. For format, see `config.yml.sample`.
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
- Run an API request against a MOCO instance and return the result nicely formatted.
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, JSON format
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
- ### sync_activity
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
- Sync activity data (time entries) from one instance to another, fuzzy matching projects and tasks.
88
- It is highly recommended to use filter options (`--from`, `--to`) and to use `--dry-run` first to check the matching performance.
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] source target
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 for
97
- -n, --dry-run Match only, but do not edit data
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
- Project matching threshold (0.0 - 1.0), default 0.8
100
- --match-task-threshold VALUE Task matching threshold (0.0 - 1.0), default 0.45
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!"
@@ -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