evvnt 0.1.0

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.
data/lib/evvnt/base.rb ADDED
@@ -0,0 +1,166 @@
1
+ module Evvnt
2
+ # Internal: Base class for Evvnt API resource classes.
3
+ #
4
+ class Base
5
+ # frozen_string_literal: true
6
+
7
+ require "evvnt/actions"
8
+ require "evvnt/api"
9
+ require "evvnt/class_template_methods"
10
+ require "evvnt/instance_template_methods"
11
+ require "evvnt/logging"
12
+ require "evvnt/nested_resources"
13
+ require "evvnt/path_helpers"
14
+ require "evvnt/persistence"
15
+
16
+ include HTTParty
17
+ include Logging
18
+ include Api
19
+ include Persistence
20
+ extend PathHelpers
21
+ extend NestedResources
22
+ extend Actions
23
+
24
+ ##
25
+ # Test if a String is a date string.
26
+ DATE_STRING_REGEX = /^\d{4}-\d{2}-\d{2}$/
27
+
28
+ ##
29
+ # Test if a String is a datetime string.
30
+ DATETIME_STRING_REGEX = /^\d{4}-\d\d-\d\dT\d\d\:\d\d\:\d\d\+\d\d\:\d\d$/
31
+
32
+
33
+ if Evvnt.configuration.environment == :live
34
+ base_uri "https://api.evvnt.com"
35
+ else
36
+ base_uri "https://api.sandbox.evvnt.com"
37
+ end
38
+
39
+ ##
40
+ # The attributes for this given record
41
+ #
42
+ # Returns Hash
43
+ attr_reader :attributes
44
+
45
+ # =================
46
+ # = Class Methods =
47
+ # =================
48
+
49
+ ##
50
+ # The first record from the API index actions
51
+ #
52
+ # Returns {Evvnt::Base} subclass
53
+ def self.first
54
+ defined_actions.include?(:index) ? all.first : method_missing(:first)
55
+ end
56
+
57
+ ##
58
+ # The last record from the API index actions
59
+ #
60
+ # Returns {Evvnt::Base} subclass
61
+ def self.last
62
+ defined_actions.include?(:index) ? all.first : method_missing(:last)
63
+ end
64
+
65
+
66
+ # ====================
67
+ # = Instance methods =
68
+ # ====================
69
+
70
+ ##
71
+ # Initialize a new record
72
+ #
73
+ # attributes - A Hash of attributes for the given record. See {method_missing} for
74
+ # more info on how this is handled.
75
+ def initialize(attributes = {})
76
+ self.attributes = Hash[attributes.map { |k, v| [k, format_attribute(k, v)] }]
77
+ end
78
+
79
+ ##
80
+ # Set or change the attributes for this record
81
+ #
82
+ # hash - A Hash of attributes for this record.
83
+ #
84
+ # Returns Hash
85
+ def attributes=(hash)
86
+ @attributes = Hash(hash).with_indifferent_access
87
+ end
88
+
89
+ # The unique identifier for the given record. Tries +uuid+ followed by +id+.
90
+ #
91
+ # Returns String
92
+ def unique_identifier
93
+ attributes["uuid"] || attributes["id"]
94
+ end
95
+
96
+ private
97
+
98
+ def format_attribute(key, value)
99
+ case value
100
+ when String
101
+ format_string_attribute(value)
102
+ when Array
103
+ format_array_attribute(key, value)
104
+ when Hash
105
+ format_hash_attribute(key, value)
106
+ else
107
+ value
108
+ end
109
+ end
110
+
111
+ def format_hash_attribute(key, value)
112
+ unless Evvnt.const_defined?(key.singularize.classify)
113
+ raise ArgumentError, "Unknown object type: #{key}"
114
+ end
115
+ Evvnt.const_get(key.singularize.classify).new(value)
116
+ end
117
+
118
+ def format_array_attribute(key, value)
119
+ Array(value).map do |attributes|
120
+ unless Evvnt.const_defined?(key.singularize.classify)
121
+ raise ArgumentError, "Unknown object type: #{key}"
122
+ end
123
+ Evvnt.const_get(key.singularize.classify).new(attributes)
124
+ end
125
+ end
126
+
127
+ def format_string_attribute(value)
128
+ case value
129
+ when DATE_STRING_REGEX
130
+ value.to_date
131
+ when DATETIME_STRING_REGEX
132
+ value.to_datetime
133
+ else
134
+ value
135
+ end
136
+ end
137
+
138
+ ##
139
+ # Overrides method missing to catch undefined methods. If +method_name+ is one
140
+ # of the keys on +attributes+, returns the value of that attribute. If +method_name+
141
+ # is not one of +attributes+, passes up the chain to super.
142
+ #
143
+ # method_name - Symbol of the name of the method we're testing for.
144
+ # args - Array of arguments send with the original mesage.
145
+ # block - Proc of code passed with original message.
146
+ #
147
+ def method_missing(method_name, *args)
148
+ if method_name.to_s[/\=/]
149
+ attributes[method_name.to_s.gsub(/\=+/, "")] = args.first
150
+ else
151
+ attributes[method_name.to_s.gsub(/\=+/, "")]
152
+ end
153
+ end
154
+
155
+ ##
156
+ # Improve code instrospect. Allows `respond_to?` for dynamically added attribute
157
+ # methods.
158
+ #
159
+ # method - A Symbol with the method name
160
+ #
161
+ # Returns Boolean
162
+ def respond_to_missing?(method, *)
163
+ attributes.stringify_keys.keys.include?(method.to_s) || super
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,4 @@
1
+ module Evvnt
2
+ class Broadcast < Evvnt::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Evvnt
2
+ class BroadcastResult < Evvnt::Base
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ module Evvnt
2
+ # Fetch EVVNT Categories from the API
3
+ class Category < Evvnt::Base
4
+
5
+ ##
6
+ # The ID of the "All" category on Evvnt
7
+ DEFAULT_ID = 26
8
+
9
+ ##
10
+ # GET /categories List categories
11
+ # Returns Array
12
+ define_action :index
13
+
14
+ belongs_to :publisher
15
+
16
+ end
17
+ end
@@ -0,0 +1,101 @@
1
+ module Evvnt
2
+ # Internal: Template methods to provide default behaviour for API actions.
3
+ #
4
+ # These are defined on Evvnt::Base subclasses where required to map the Evvnt API
5
+ # actions.
6
+ module ClassTemplateMethods
7
+ # frozen_string_literal: true
8
+
9
+ ##
10
+ # Regular expression for params in URL strings
11
+ PARAM_REGEX = %r{\:[^\/$]+}
12
+
13
+ # Template method for creating a new record on the API.
14
+ #
15
+ # params - A Hash of params to send to the API.
16
+ #
17
+ # Returns {Evvnt::Base} subclass
18
+ def create(**params)
19
+ path = if params_include_parent_resource_id?(params)
20
+ nest_path_within_parent(plural_resource_path, params)
21
+ else
22
+ plural_resource_path
23
+ end
24
+ api_request(:post, path, params: params)
25
+ end
26
+
27
+ # Template method for fetching an index of record from the API.
28
+ #
29
+ # params - A Hash of params to send to the API.
30
+ #
31
+ # Returns Array
32
+ def index(**params)
33
+ params.stringify_keys!
34
+ if plural_resource_path.respond_to?(:call)
35
+ path = plural_resource_path.call
36
+ path.match(PARAM_REGEX) do |segment|
37
+ value = params.delete(segment.to_s[1..-1])
38
+ path = path.gsub!(/#{segment}/, value.to_s)
39
+ end
40
+ else
41
+ path = plural_resource_path
42
+ end
43
+ if params_include_parent_resource_id?(params)
44
+ path = nest_path_within_parent(path, params)
45
+ end
46
+ api_request(:get, path, params: params)
47
+ end
48
+
49
+ # Template method for creating a given record
50
+ #
51
+ # record_id - An Integer or String representing the record ID on the API.
52
+ # params - A Hash of params to send to the API.
53
+ #
54
+ # Returns {Evvnt::Base} subclass
55
+ def show(record_id = nil, **params)
56
+ if record_id.nil? && !singular_resource?
57
+ raise ArgumentError, "record_id cannot be nil"
58
+ end
59
+ path = singular_path_for_record(record_id, params)
60
+ api_request(:get, path, params: params)
61
+ end
62
+
63
+ # Template method for updating a given record
64
+ #
65
+ # record_id - An Integer or String representing the record ID on the API.
66
+ # params - A Hash of params to send to the API.
67
+ #
68
+ # Returns {Evvnt::Base} subclass
69
+ def update(record_id, **params)
70
+ if record_id.nil? && !singular_resource?
71
+ raise ArgumentError, "record_id cannot be nil"
72
+ end
73
+ path = singular_path_for_record(record_id, params)
74
+ api_request(:put, path, params: params)
75
+ end
76
+
77
+ # Template method for fetching _mine_ records from the API.
78
+ #
79
+ # record_id - An Integer or String representing the record ID on the API (optional).
80
+ # params - A Hash of params to send to the API.
81
+ #
82
+ # Returns Array
83
+ # Returns {Evvnt::Base}
84
+ def ours(record_id = nil, **params)
85
+ id_segment = record_id.to_s
86
+ segments = [plural_resource_path, "ours", id_segment].select(&:present?)
87
+ path = File.join(*segments).to_s
88
+ api_request(:get, path, params: params)
89
+ end
90
+
91
+ # Template method for fetching _mine_ records from the API.
92
+ #
93
+ # params - A Hash of params to send to the API.
94
+ #
95
+ # Returns Array
96
+ def mine(**params)
97
+ path = File.join(plural_resource_path, "mine").to_s
98
+ api_request(:get, path, params: params)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,4 @@
1
+ module Evvnt
2
+ class ClicksByDay < Evvnt::Base
3
+ end
4
+ end
@@ -0,0 +1,54 @@
1
+ module Evvnt
2
+ # Handles configuration for the gem
3
+ class Configuration
4
+
5
+ ##
6
+ #
7
+ ENVIRONMENTS = %i[sandbox live].freeze
8
+
9
+
10
+ ##
11
+ #
12
+ attr_writer :logger
13
+
14
+ ##
15
+ #
16
+ attr_writer :debug
17
+
18
+ ##
19
+ #
20
+ attr_accessor :api_key
21
+
22
+ ##
23
+ #
24
+ attr_accessor :api_secret
25
+
26
+ def initialize(&block)
27
+ instance_eval(&block) if block_given?
28
+ end
29
+
30
+ ##
31
+ #
32
+ def environment=(value)
33
+ raise ArgumentError unless value.to_sym.in?(ENVIRONMENTS)
34
+ @environment = value
35
+ end
36
+
37
+ def environment
38
+ @environment ||= :sandbox
39
+ end
40
+
41
+ ##
42
+ #
43
+ def logger
44
+ @logger ||= Logger.new($stdout)
45
+ end
46
+
47
+ ##
48
+ #
49
+ def debug
50
+ defined?(@debug) ? @debug : @debug = false
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,4 @@
1
+ module Evvnt
2
+ class Contact < Evvnt::Base
3
+ end
4
+ end
@@ -0,0 +1,18 @@
1
+ module Evvnt
2
+ # Public: Returns contract info from the EVVNT API
3
+ class Contract < Evvnt::Base
4
+
5
+ ##
6
+ # The JSON key for the package data from the API response.
7
+ OBJECT_KEY = "package_options".freeze
8
+
9
+ singular_resource!
10
+
11
+ ##
12
+ # GET /contract Get contract information
13
+ define_action :index do
14
+ api_request(:get, plural_resource_path, options: { object: OBJECT_KEY })
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ module Evvnt
2
+ # Public: Returns events info from the EVVNT API
3
+ class Event < Evvnt::Base
4
+
5
+ ##
6
+ # GET /events List Events
7
+ define_action :index
8
+
9
+ ##
10
+ # GET /events/:event_id Get one event
11
+ define_action :show
12
+
13
+ ##
14
+ # POST /events Create an event
15
+ define_action :create
16
+
17
+ ##
18
+ # PUT /events/:event_id Update an event
19
+ define_action :update
20
+
21
+ ##
22
+ # GET /events/ours(/:id) Get events of you and your created users
23
+ define_action :ours
24
+
25
+ ##
26
+ # GET /events/mine List my events
27
+ define_action :mine
28
+
29
+
30
+ private
31
+
32
+
33
+ def format_hash_attribute(key, value)
34
+ case key
35
+ when "links"
36
+ format_links_attribute(key, value)
37
+ when "prices"
38
+ format_prices_attribute(key, value)
39
+ else
40
+ super
41
+ end
42
+ end
43
+
44
+ def format_array_attribute(key, value)
45
+ case key
46
+ when /^(image\_urls|sub\_category\_ids)$/
47
+ send(:"format_#{key}_attribute", key, value)
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ def format_image_urls_attribute(_key, value)
54
+ value
55
+ end
56
+
57
+ def format_sub_category_ids_attribute(_key, value)
58
+ value
59
+ end
60
+
61
+ def format_links_attribute(_key, value)
62
+ value.to_a.map { |name, url| Evvnt::Link.new(name: name, url: url) }
63
+ end
64
+
65
+ def format_prices_attribute(_key, value)
66
+ value.to_a.map { |name, price| Evvnt::Price.new(name: name, value: price) }
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ module Evvnt
2
+ # Internal: Template methods to provide default behaviour for API actions.
3
+ #
4
+ # These are defined on Evvnt::Base subclasses where required to map the Evvnt API
5
+ # actions.
6
+ module InstanceTemplateMethods
7
+ # Template method for updating a given record
8
+ #
9
+ # record_id - An Integer or String representing the record ID on the API.
10
+ # params - A Hash of params to send to the API.
11
+ #
12
+ # Returns {Evvnt::Base} subclass
13
+ def update(**new_attributes)
14
+ self.class.update(id, new_attributes) unless new_attributes == attributes
15
+ end
16
+ end
17
+ end
data/lib/evvnt/link.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Evvnt
2
+ class Link < Evvnt::Base
3
+
4
+ end
5
+ end
@@ -0,0 +1,42 @@
1
+ module Evvnt
2
+ # Internal: Log messages for debugging
3
+ #
4
+ module Logging
5
+ extend ActiveSupport::Concern
6
+
7
+ ##
8
+ # The tag to print to the logger.
9
+ TAG_NAME = 'EVVNT'.freeze
10
+
11
+ private
12
+
13
+ # The Logger object to print out messages to.
14
+ def logger
15
+ Evvnt.configuration.logger
16
+ end
17
+
18
+ # Print a debug level message
19
+ #
20
+ # message - A String of the message to be printed.
21
+ def debug(message)
22
+ log_message(:debug, message)
23
+ end
24
+
25
+ # Log a message to the {logger} with the given log level
26
+ #
27
+ # level - A Symbol representing the logger level
28
+ # message - A String with the message to print to the log
29
+ def log_message(level, message)
30
+ if logger.respond_to?(:tagged)
31
+ logger.tagged(TAG_NAME) { |l| l.public_send(level, message) }
32
+ else
33
+ logger.public_send(level, message)
34
+ end
35
+ end
36
+
37
+ # rubocop:disable Naming/ConstantName
38
+ # Make these methods available to the class when module is included.
39
+ ClassMethods = self
40
+ # rubocop:enable Naming/ConstantName
41
+ end
42
+ end
@@ -0,0 +1,66 @@
1
+ module Evvnt
2
+ # Internal: Adds behaviour to {Evvnt::Base} for handling nested resources
3
+ #
4
+ # Examples
5
+ #
6
+ # class Thing < Evvnt::Base
7
+ # belongs_to :user
8
+ # end
9
+ # # Will find a Thing at "/users/456/things/123.json"
10
+ # @thing = Thing.find("123", user_id: "456")
11
+ #
12
+ module NestedResources
13
+ # frozen_string_literal: true
14
+
15
+ private
16
+
17
+ # Tell a class that it's resources may be nested within another named resource
18
+ #
19
+ # parent_resource - A Symbol with the name of the parent resource.
20
+ #
21
+ def belongs_to(parent_resource)
22
+ parent_resource = parent_resource.to_sym
23
+ parent_resources << parent_resource unless parent_resource.in?(parent_resources)
24
+ end
25
+
26
+ # A list of the parent resources for this class.
27
+ #
28
+ # Return Array
29
+ def parent_resources
30
+ @parent_resources ||= []
31
+ end
32
+
33
+ # A list of the param names that represent parent resources
34
+ #
35
+ # Returns Array
36
+ def parent_resource_id_params
37
+ parent_resources.map { |pr| :"#{pr}_id" }
38
+ end
39
+
40
+ # Does the params hash contain a parent resource ID?
41
+ #
42
+ # params - A Hash of params to send to the API.
43
+ #
44
+ # Returns Boolean
45
+ def params_include_parent_resource_id?(params)
46
+ parent_resource_param(params).present?
47
+ end
48
+
49
+ # The param that represents the parent record (e.g. +:user_id+)
50
+ #
51
+ # params - A Hash of params to send to the API.
52
+ #
53
+ # Returns Symbol
54
+ def parent_resource_param(params)
55
+ (params.symbolize_keys.keys & parent_resource_id_params).first
56
+ end
57
+
58
+ # The name of the parent record defined in the parms
59
+ #
60
+ # params - A Hash of params to send to the API.
61
+ #
62
+ def parent_resource_name(params)
63
+ parent_resource_param(params).to_s.gsub(/\_id/, '').pluralize
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,24 @@
1
+ module Evvnt
2
+ # Public: Represents the packages resources on the EVVNT API.
3
+ class Package < Evvnt::Base
4
+
5
+ ##
6
+ # GET /users/:user_id/packages Lists all of the packages belonging to a user
7
+ define_action :index
8
+
9
+ ##
10
+ # POST /packages Create a package
11
+ define_action :create
12
+
13
+ ##
14
+ # GET /packages/:package_id Get the details of a package
15
+ define_action :show
16
+
17
+ ##
18
+ # GET /packages/mine List my packages
19
+ define_action :mine
20
+
21
+ belongs_to :user
22
+
23
+ end
24
+ end