chameleon-ruby 0.1.6
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +139 -0
- data/lib/chameleon/config.rb +10 -0
- data/lib/chameleon/errors.rb +42 -0
- data/lib/chameleon/model.rb +78 -0
- data/lib/chameleon/models/company.rb +43 -0
- data/lib/chameleon/models/deletion.rb +5 -0
- data/lib/chameleon/models/event.rb +24 -0
- data/lib/chameleon/models/profile.rb +54 -0
- data/lib/chameleon/models/search_item.rb +69 -0
- data/lib/chameleon/request.rb +64 -0
- data/lib/chameleon/support.rb +39 -0
- data/lib/chameleon/version.rb +3 -0
- data/lib/chameleon.rb +23 -0
- metadata +63 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 063b39158b4215a5571222f799ac1780895db5710d667507041eb984985ab62f
|
4
|
+
data.tar.gz: 65939c62ec99e9e0bcd177515aa6faf54d291264c5e2273c8f203f0e259b1e31
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 35920fd3e874f5a10aeaa6a1b2b9ac66186fa52f4a021cd9b8cdbe819c70ef6ee92b0c0e14c06e1356bb824891ee0717014a98c0f8e83b9bed7cf27117fbedd8
|
7
|
+
data.tar.gz: e13d11ac0ca4c0f8e0dbcf0031e44577253c610c6ab638153f6cf096ba8740bb9f3f3441674fb0127b564b08205060b831881dd29fdd9ffa31047e6969ce93cb
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Brian Norton
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
# chameleon-ruby
|
2
|
+
|
3
|
+
[](https://rubygems.org/gems/chameleon-ruby)
|
4
|
+
[](https://github.com/chamaeleonidae/chameleon-ruby/actions/workflows/ci.yml)
|
5
|
+
|
6
|
+
A Ruby gem for simple interactions with the Chameleon APIs
|
7
|
+
|
8
|
+
> Your API Secret is generated on the [Integrations page](https://app.chameleon.io/integrations/tokens). The Full API documentation can be found on our [Developer hub](https://developers.chameleon.io)!
|
9
|
+
|
10
|
+
---
|
11
|
+
|
12
|
+
- [Quick start](#quick-start)
|
13
|
+
- [Usage and Examples](#usage-and-examples)
|
14
|
+
- [Support](#support)
|
15
|
+
- [License](#license)
|
16
|
+
- [Code of conduct](#code-of-conduct)
|
17
|
+
- [Contribution guide](#contribution-guide)
|
18
|
+
|
19
|
+
## Quick start
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'chameleon-ruby' # in your Gemfile
|
23
|
+
```
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'chameleon' # if needed
|
27
|
+
```
|
28
|
+
|
29
|
+
## Usage and Examples
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# configs/initializers/chameleon/rb
|
33
|
+
Chameleon.configure do |config|
|
34
|
+
config.secret = ENV['CHAMELEON_SECRET']
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
##
|
40
|
+
# In a Background job when content is created / updated
|
41
|
+
#
|
42
|
+
Chameleon::SearchItem.index(uid, title: title, actions: actions)
|
43
|
+
|
44
|
+
##
|
45
|
+
# More options and inspiration for what you can do
|
46
|
+
#
|
47
|
+
# This example assumes you have a model in your database called `Document` but
|
48
|
+
# this can be anything users can create in your system
|
49
|
+
#
|
50
|
+
|
51
|
+
uid = "documents:#{model.id}" # or "#{model.class.name.underscore}:#{model.id}"
|
52
|
+
|
53
|
+
title = model.name # the main unit of searchability; for a document this is the user generated title; for a
|
54
|
+
description = model.description # user generated description; may include tags or other identifying info about this content
|
55
|
+
|
56
|
+
actions = [{ kind: 'navigate', url: "/documents/#{model.id}/edit" }] # navigate to /documents/3327/edit when clicked
|
57
|
+
# or
|
58
|
+
actions = [{ kind: 'navigate', url: "/#{model.class.name.underscore}/#{model.id}/edit" }] # navigate to /documents/3327/edit when clicked
|
59
|
+
|
60
|
+
icon = { kind: 'uid', uid: 'cog' } # the icon displayed for this item from the Chameleon icon library
|
61
|
+
# or
|
62
|
+
icon = { kind: 'image', image_url: 'https://cdn-icons-png.flaticon.com/512/9908/9908192.png' } # the icon displayed for this item from a public url
|
63
|
+
|
64
|
+
##
|
65
|
+
# Index the content by uid
|
66
|
+
#
|
67
|
+
Chameleon::SearchItem.index(uid, title: title, actions: actions)
|
68
|
+
|
69
|
+
# Remove when deleted/archived in your system or no longer searchable
|
70
|
+
#
|
71
|
+
Chameleon::SearchItem.remove(uid)
|
72
|
+
|
73
|
+
##
|
74
|
+
# Add a user-generated description, if you have it
|
75
|
+
#
|
76
|
+
Chameleon::SearchItem.index(uid, title: title, description: description, actions: actions)
|
77
|
+
|
78
|
+
##
|
79
|
+
# Customize the icon from the Chameleon icon library
|
80
|
+
#
|
81
|
+
icon = { kind: 'uid', uid: 'cog' } # the icon displayed for this item from the Chameleon icon library
|
82
|
+
Chameleon::SearchItem.index(uid, title: title, actions: actions, icon: icon)
|
83
|
+
|
84
|
+
##
|
85
|
+
# To restrict content to a specific Chameleon user, set the profile_uids to just that single user
|
86
|
+
# This is the only user that will be able to find this content
|
87
|
+
#
|
88
|
+
# Use cases:
|
89
|
+
# - This document is only visible to the creator
|
90
|
+
# - This design or prototype has not been shared yet
|
91
|
+
# - A part of the users' onboarding that is not yet complete
|
92
|
+
#
|
93
|
+
Chameleon::SearchItem.index(uid, title: title, description: description, actions: actions, profile_uids: [user.id])
|
94
|
+
|
95
|
+
##
|
96
|
+
# Now to extend the Use case to a Document that the creator shares with their co-workers
|
97
|
+
# This example assumes that Document has an array of User IDs (an ACL) to share with
|
98
|
+
#
|
99
|
+
Chameleon::SearchItem.index(uid, title: title, actions: actions, profile_uids: [user.id, *document.shared_to_user_ids])
|
100
|
+
|
101
|
+
##
|
102
|
+
# To restrict content to a specific Chameleon company, set the company_uids to just that single company
|
103
|
+
# This is the only company that will be able to find this content -- all users in this company are able to search for it
|
104
|
+
#
|
105
|
+
# Use cases:
|
106
|
+
# - This document is only visible to the Company/Account/Tenant of the creator
|
107
|
+
# - This design or prototype has been published/shared to your Company
|
108
|
+
# - A part of the company's onboarding that is not yet complete
|
109
|
+
#
|
110
|
+
Chameleon::SearchItem.index(uid, title: title, actions: actions, company_uids: [company.id])
|
111
|
+
|
112
|
+
##
|
113
|
+
# To restrict content to a specific Chameleon Segment, set the segment_ids to one or many Segments
|
114
|
+
# Only current members of these Segments will be able to search for this content
|
115
|
+
#
|
116
|
+
# Use cases:
|
117
|
+
# - This document is only visible to Company/Account/Tenant administrators (Segment is set to "roles contains admin")
|
118
|
+
# - This page is only available to the Enterprise plan (Segment is set to is "plan is enterprise")
|
119
|
+
# - An upsell prompt is only relevant for trialing users (Segment is set to "plan_status is trialing")
|
120
|
+
#
|
121
|
+
Chameleon::SearchItem.index(uid, title: title, actions: actions, segment_ids: [segment1_id, segment2_id])
|
122
|
+
```
|
123
|
+
|
124
|
+
#### Support
|
125
|
+
|
126
|
+
If you want to report a bug, or have ideas, feedback or questions about the gem itself, [let us know via GitHub issues](https://github.com/chamaeleonidae/chameleon-ruby/issues/new) and the Chameleon team will do their best to provide a helpful answer.
|
127
|
+
Or another to reach us is to [Contact us](https://app.chameleon.io/help) in your dashboard.
|
128
|
+
|
129
|
+
#### License
|
130
|
+
|
131
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
132
|
+
|
133
|
+
#### Code of conduct
|
134
|
+
|
135
|
+
Everyone interacting in this project’s codebases and issue trackers is expected to follow the [code of conduct](CODE_OF_CONDUCT.md).
|
136
|
+
|
137
|
+
#### Contribution guide
|
138
|
+
|
139
|
+
Pull requests are welcome!
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Chameleon
|
2
|
+
module Errors
|
3
|
+
# When a model does not have all of the required attributes
|
4
|
+
class Invalid < StandardError; end
|
5
|
+
|
6
|
+
# When the Chameleon API returns an error
|
7
|
+
#
|
8
|
+
class ApiError < StandardError
|
9
|
+
attr_reader :request, :response, :status, :body, :messages
|
10
|
+
|
11
|
+
def initialize(request, response)
|
12
|
+
@request = request
|
13
|
+
@response = response
|
14
|
+
@status = response.code
|
15
|
+
@body = JSON.parse(response.body) rescue response.body
|
16
|
+
@messages = @body['messages'].is_a?(Array) ? @body['messages'] : []
|
17
|
+
|
18
|
+
super("Chameleon API error with code: #{status}, message: #{messages[1] || 'Unknown error'}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class AuthError < ApiError; end # 403
|
23
|
+
class NotFoundError < ApiError; end # 404
|
24
|
+
class ConflictError < ApiError; end # 409
|
25
|
+
class InputError < ApiError; end # 422
|
26
|
+
class RateLimitError < ApiError; end # 429
|
27
|
+
class ServerError < ApiError; end # 500
|
28
|
+
class TimeoutError < ApiError; end # 503
|
29
|
+
|
30
|
+
VIA_STATUS_CODE = Hash.new(ApiError)
|
31
|
+
VIA_STATUS_CODE.merge!(
|
32
|
+
403 => AuthError,
|
33
|
+
404 => NotFoundError,
|
34
|
+
409 => ConflictError,
|
35
|
+
422 => InputError,
|
36
|
+
429 => RateLimitError,
|
37
|
+
500 => ServerError,
|
38
|
+
503 => TimeoutError,
|
39
|
+
)
|
40
|
+
VIA_STATUS_CODE.freeze
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Chameleon::Model
|
2
|
+
attr_accessor :id
|
3
|
+
attr_reader :attributes
|
4
|
+
|
5
|
+
def initialize(attributes={})
|
6
|
+
@attributes = {}
|
7
|
+
|
8
|
+
refresh(attributes)
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
@attributes[key.to_s]
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(key, value)
|
16
|
+
@attributes[key.to_s] = Chameleon::Support.value_of(value)
|
17
|
+
end
|
18
|
+
|
19
|
+
def model_name
|
20
|
+
Chameleon::Support.underscore(self.class.name.gsub('Chameleon::', ''))
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear
|
24
|
+
@attributes.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
def refresh(attributes)
|
28
|
+
return self unless attributes.respond_to?(:each_pair)
|
29
|
+
|
30
|
+
attributes.each_pair { |key, value| @attributes[key.to_s] = Chameleon::Support.value_of(value) }
|
31
|
+
@id = @attributes.delete('id') if @attributes.key?('id')
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def url
|
36
|
+
base = "/v3/edit/#{Chameleon::Support.pluralize(model_name)}"
|
37
|
+
base << "/#{id}" if id
|
38
|
+
base
|
39
|
+
end
|
40
|
+
|
41
|
+
def create
|
42
|
+
Chameleon::Request.post(self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch
|
46
|
+
Chameleon::Request.get(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete
|
50
|
+
Chameleon::Request.delete(self)
|
51
|
+
end
|
52
|
+
|
53
|
+
def inspect
|
54
|
+
string = "#<#{self.class.name}"
|
55
|
+
string << " id=#{id}" if id
|
56
|
+
string << " attributes=#{attributes}"
|
57
|
+
string << '>'
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def respond_to_missing?(symbol, other)
|
63
|
+
key = symbol.to_s
|
64
|
+
key[-1] == '=' || @attributes.key?(key)
|
65
|
+
end
|
66
|
+
|
67
|
+
def method_missing(symbol, *args)
|
68
|
+
key = symbol.to_s
|
69
|
+
|
70
|
+
if key[-1] == '=' # setter
|
71
|
+
self[key[0..-2]] = args.first
|
72
|
+
elsif @attributes.key?(key)
|
73
|
+
self[key]
|
74
|
+
else
|
75
|
+
super
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Chameleon
|
2
|
+
class Company < Chameleon::Model
|
3
|
+
# https://developers.chameleon.io/#/webhooks/companies
|
4
|
+
|
5
|
+
##
|
6
|
+
# Identify an account/Company/tenant to Chameleon
|
7
|
+
#
|
8
|
+
# @param uid The database ID for this Company (same as the uid passed to `chmln.identify` in the JS API)
|
9
|
+
# @param options Any additional user data you want Chameleon to know about
|
10
|
+
#
|
11
|
+
# @returns Chameleon::Company
|
12
|
+
#
|
13
|
+
def self.identify(uid, options={})
|
14
|
+
item = new(options.merge(uid: uid))
|
15
|
+
item.save
|
16
|
+
item
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Retrieve an Chameleon Company
|
21
|
+
#
|
22
|
+
# @param uid The database ID for this Company (same as the uid passed to `chmln.identify` in the JS API)
|
23
|
+
#
|
24
|
+
# @returns Chameleon::Company
|
25
|
+
#
|
26
|
+
def self.fetch(uid)
|
27
|
+
item = new(uid: uid)
|
28
|
+
item.fetch
|
29
|
+
item
|
30
|
+
end
|
31
|
+
|
32
|
+
def url
|
33
|
+
path = 'v3/analyze/company'
|
34
|
+
path.gsub!(/y$/, "ies/#{id}") if id
|
35
|
+
path
|
36
|
+
end
|
37
|
+
|
38
|
+
def save
|
39
|
+
Chameleon::Request.model_request(:post, self, path: 'v3/observe/hooks/companies')
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Chameleon
|
2
|
+
class Event < Chameleon::Model
|
3
|
+
# https://developers.chameleon.io/#/webhooks/events
|
4
|
+
|
5
|
+
##
|
6
|
+
# Track an Event user to Chameleon
|
7
|
+
#
|
8
|
+
# @param uid The database ID for this user (same as the uid passed to `chmln.identify` in the JS API)
|
9
|
+
# @param name The human-readable name for this event
|
10
|
+
# @param options Any additional user data you want Chameleon to know about
|
11
|
+
#
|
12
|
+
# @returns Chameleon::Event
|
13
|
+
#
|
14
|
+
def self.track(uid, name, options={})
|
15
|
+
item = new(options.merge(uid: uid, name: name))
|
16
|
+
item.save
|
17
|
+
item
|
18
|
+
end
|
19
|
+
|
20
|
+
def save
|
21
|
+
Chameleon::Request.model_request(:post, self, path: 'v3/observe/hooks/events')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Chameleon
|
2
|
+
class Profile < Chameleon::Model
|
3
|
+
# https://developers.chameleon.io/#/webhooks/profiles
|
4
|
+
|
5
|
+
##
|
6
|
+
# Identify a user to Chameleon
|
7
|
+
#
|
8
|
+
# @param uid The database ID for this user (same as the uid passed to `chmln.identify` in the JS API)
|
9
|
+
# @param options Any additional user data you want Chameleon to know about
|
10
|
+
#
|
11
|
+
# @returns Chameleon::Profile
|
12
|
+
#
|
13
|
+
def self.identify(uid, options={})
|
14
|
+
item = new(options.merge(uid: uid))
|
15
|
+
item.save
|
16
|
+
item
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Retrieve a Chameleon user profile
|
21
|
+
#
|
22
|
+
# @param uid The database ID for this user (same as the uid passed to `chmln.identify` in the JS API)
|
23
|
+
#
|
24
|
+
# @returns Chameleon::Profile
|
25
|
+
#
|
26
|
+
def self.fetch(uid)
|
27
|
+
item = new(uid: uid)
|
28
|
+
item.fetch
|
29
|
+
item
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Remove this user from Chameleon and purge all of their associated data
|
34
|
+
#
|
35
|
+
# @param uid The database ID for this user (same as the uid passed to `chmln.identify` in the JS API)
|
36
|
+
# @returns Chameleon::Deletion
|
37
|
+
#
|
38
|
+
def self.forget(uid)
|
39
|
+
response = Chameleon::Request.request(:delete, 'v3/edit/profiles/forget', options: { uid: uid })
|
40
|
+
Chameleon::Deletion.new(JSON.parse(response.body)['deletion'])
|
41
|
+
end
|
42
|
+
|
43
|
+
def url
|
44
|
+
path = 'v3/analyze/profile'
|
45
|
+
path << "s/#{id}" if id
|
46
|
+
path
|
47
|
+
end
|
48
|
+
|
49
|
+
def save
|
50
|
+
Chameleon::Request.model_request(:post, self, path: 'v3/observe/hooks/profiles')
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Chameleon
|
2
|
+
class SearchItem < Chameleon::Model
|
3
|
+
# https://developers.chameleon.io/#/apis/search
|
4
|
+
|
5
|
+
##
|
6
|
+
# Add a SearchItem to the HelpBar search index
|
7
|
+
#
|
8
|
+
# @param uid The unique ID you assign for this piece of content
|
9
|
+
#
|
10
|
+
# @param options
|
11
|
+
# @param options[:title] The title of the content
|
12
|
+
# @param options[:description] The description of the content; how your user would describe it in their words
|
13
|
+
#
|
14
|
+
# @param options[:actions] An Array of the actions to take when clicking/selecting this item
|
15
|
+
#
|
16
|
+
# Content in the HelpBar can be Pinned and will display as default items when the HelpBar is first opened
|
17
|
+
#
|
18
|
+
# @param options[:pinned_at] A timestamp representing the ordering of pinned content (1.hour.ago will display above 1.day.ago)
|
19
|
+
#
|
20
|
+
# To add a specific icon to this; by default icons are inherited from the search group they are added to.
|
21
|
+
#
|
22
|
+
# @param options[:icon] The hash specifying which icon to use
|
23
|
+
# @param options[:icon][:kind] One of 'uid' or 'image'
|
24
|
+
# @param options[:icon][:uid] Required with kind=uid: The Chameleon Icon uid from the Icon library
|
25
|
+
# @param options[:icon][:image_url] Required with kind=image: The URL of an icon to use
|
26
|
+
#
|
27
|
+
# For the following params, your end-user will first be matched against these groups to build the set
|
28
|
+
# of searchable content they have access to, then the search is performed on only the filtered content.
|
29
|
+
#
|
30
|
+
# @param options[:profile_uids] The UID (your database ID) of the Chameleon User Profiles this is searchable for (same as the uid passed to `chmln.identify` in the JS API)
|
31
|
+
# @param options[:company_uids] The UID (your database ID) of the Chameleon Companies this is searchable for (same as the uid passed to `chmln.identify` in the JS API)
|
32
|
+
# @param options[:segment_ids] The ID of the Chameleon Segments this is searchable for (found on https://app.chameleon.io/segments)
|
33
|
+
#
|
34
|
+
def self.index(uid, options)
|
35
|
+
item = new(options.merge(uid: uid))
|
36
|
+
item.create
|
37
|
+
item
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Retrieve a Chameleon Search item
|
42
|
+
#
|
43
|
+
# @param uid The unique ID you previously assigned to this piece of content
|
44
|
+
#
|
45
|
+
# @returns Chameleon::SearchItem
|
46
|
+
#
|
47
|
+
def self.fetch(uid)
|
48
|
+
item = new(uid: uid)
|
49
|
+
item.fetch
|
50
|
+
item
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Remove the SearchItem with the given uid from the HelpBar search index
|
55
|
+
#
|
56
|
+
# @param uid The unique ID you previously assigned to this piece of content
|
57
|
+
#
|
58
|
+
def self.remove(uid)
|
59
|
+
item = new(uid: uid)
|
60
|
+
item.delete
|
61
|
+
item
|
62
|
+
end
|
63
|
+
|
64
|
+
def url
|
65
|
+
'v3/edit/search_items'
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Chameleon
|
2
|
+
class Request
|
3
|
+
def self.get(model)
|
4
|
+
raise ArgumentError, 'Fetching a Chameleon model requires at `id` or `uid`' unless model.id || model.uid
|
5
|
+
|
6
|
+
model_request :get, model, options: model.attributes.slice(*%w[uid]).compact
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.post(model)
|
10
|
+
model_request :post, model
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.patch(model)
|
14
|
+
model_request :patch, model
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.delete(model)
|
18
|
+
model_request :delete, model, options: model.attributes.slice(*%w[uid]).compact
|
19
|
+
model.clear
|
20
|
+
model.deleted_at = Time.now
|
21
|
+
model
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.model_request(method, model, options: model.attributes, path: nil)
|
25
|
+
response = request(method, path || model.url, options: options)
|
26
|
+
body = JSON.parse(response.body)
|
27
|
+
|
28
|
+
model.refresh(body[model.model_name])
|
29
|
+
model
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.request(method, path, options: {})
|
33
|
+
url = "#{Chameleon.config.base_url}/#{path.gsub(/^\//, '')}"
|
34
|
+
url = staging_url(url) if Chameleon.config.use_staging
|
35
|
+
req_opts = { method: method, accept_encoding: 'gzip', headers: { 'X-Account-Secret' => Chameleon.config.secret, 'User-Agent' => Chameleon.config.user_agent } }
|
36
|
+
|
37
|
+
request = if %i[get delete].include?(method)
|
38
|
+
Typhoeus::Request.new(url, req_opts.merge(params: options))
|
39
|
+
else
|
40
|
+
req_opts[:headers]['Content-Type'] = 'application/json'
|
41
|
+
|
42
|
+
Typhoeus::Request.new(url, req_opts.merge(body: JSON.dump(options)))
|
43
|
+
end
|
44
|
+
|
45
|
+
response = request.run
|
46
|
+
error(request, response) if response.code > 300
|
47
|
+
|
48
|
+
response
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.error(request, response)
|
52
|
+
raise Chameleon::Errors::VIA_STATUS_CODE[response.code].new(request, response)
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Chameleon staging postfixes `-staging` on the environment
|
57
|
+
#
|
58
|
+
def self.staging_url(url)
|
59
|
+
env = /v3\/([^\/]+)\// =~ url && Regexp.last_match[1]
|
60
|
+
|
61
|
+
"https://#{env}-staging.#{url.split('.', 2)[1]}".gsub("/#{env}/", '/')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Chameleon::Support
|
2
|
+
def self.value_of(value)
|
3
|
+
value = case value
|
4
|
+
when Array then value.map { |v| value_of(v) }.compact
|
5
|
+
when Hash then value.dup.tap { |v| v.each_key { |k| v[k] = value_of(v[k]) } }
|
6
|
+
when String
|
7
|
+
value = value.strip
|
8
|
+
|
9
|
+
case value
|
10
|
+
when '' then nil
|
11
|
+
when /^true$/i then true
|
12
|
+
when /^false$/i then false
|
13
|
+
when '$now' then Time.now
|
14
|
+
when /^\d{4}-(0[1-9]|1[0-2])-([0-2][1-9]|[1-3]0|3[01])/ then DateTime.parse(value).to_time
|
15
|
+
when /^-?\d+\.\d+(e\d+)?$/ then value.to_f
|
16
|
+
when /^-?\d+$/ then value.to_i
|
17
|
+
else value
|
18
|
+
end
|
19
|
+
else value
|
20
|
+
end
|
21
|
+
|
22
|
+
value
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.underscore(string)
|
26
|
+
string.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
|
27
|
+
string.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
|
28
|
+
string.downcase!
|
29
|
+
string
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# super naive yes, but effective for the use case here; special cases can be added if needed
|
34
|
+
#
|
35
|
+
def self.pluralize(string)
|
36
|
+
string += 's'
|
37
|
+
string
|
38
|
+
end
|
39
|
+
end
|
data/lib/chameleon.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Chameleon
|
2
|
+
autoload :VERSION, 'chameleon/version'
|
3
|
+
|
4
|
+
def self.config
|
5
|
+
@config ||= Config.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configure(&block)
|
9
|
+
yield config
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'time'
|
14
|
+
require 'typhoeus'
|
15
|
+
require 'json'
|
16
|
+
|
17
|
+
require 'chameleon/config'
|
18
|
+
require 'chameleon/errors'
|
19
|
+
require 'chameleon/model'
|
20
|
+
require 'chameleon/request'
|
21
|
+
require 'chameleon/support'
|
22
|
+
|
23
|
+
Dir[File.expand_path('chameleon/models/*.rb', __dir__)].sort.each { |f| require(f) }
|
metadata
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chameleon-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brian Norton
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-03-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- brian.nort@gmail.com
|
16
|
+
- brian@chameleon.io
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- LICENSE.txt
|
22
|
+
- README.md
|
23
|
+
- lib/chameleon.rb
|
24
|
+
- lib/chameleon/config.rb
|
25
|
+
- lib/chameleon/errors.rb
|
26
|
+
- lib/chameleon/model.rb
|
27
|
+
- lib/chameleon/models/company.rb
|
28
|
+
- lib/chameleon/models/deletion.rb
|
29
|
+
- lib/chameleon/models/event.rb
|
30
|
+
- lib/chameleon/models/profile.rb
|
31
|
+
- lib/chameleon/models/search_item.rb
|
32
|
+
- lib/chameleon/request.rb
|
33
|
+
- lib/chameleon/support.rb
|
34
|
+
- lib/chameleon/version.rb
|
35
|
+
homepage: https://github.com/chamaeleonidae/chameleon-ruby
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
metadata:
|
39
|
+
bug_tracker_uri: https://github.com/chamaeleonidae/chameleon-ruby/issues
|
40
|
+
changelog_uri: https://github.com/chamaeleonidae/chameleon-ruby/releases
|
41
|
+
source_code_uri: https://github.com/chamaeleonidae/chameleon-ruby
|
42
|
+
homepage_uri: https://github.com/chamaeleonidae/chameleon-ruby
|
43
|
+
rubygems_mfa_required: 'true'
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '2.6'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
requirements: []
|
59
|
+
rubygems_version: 3.4.1
|
60
|
+
signing_key:
|
61
|
+
specification_version: 4
|
62
|
+
summary: A Ruby gem wrapper for the Chameleon REST API
|
63
|
+
test_files: []
|