arkenstone-open 3.0.1
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/.github/workflows/codacy-analysis.yml +46 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +5 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +101 -0
- data/LICENSE.txt +11 -0
- data/README.md +87 -0
- data/Rakefile +36 -0
- data/arkenstone.gemspec +27 -0
- data/lib/arkenstone/associations/resources.rb +76 -0
- data/lib/arkenstone/associations.rb +389 -0
- data/lib/arkenstone/document.rb +289 -0
- data/lib/arkenstone/enumerable/query_list.rb +20 -0
- data/lib/arkenstone/errors/no_url_error.rb +14 -0
- data/lib/arkenstone/helpers.rb +18 -0
- data/lib/arkenstone/network/env.rb +19 -0
- data/lib/arkenstone/network/hook.rb +74 -0
- data/lib/arkenstone/network/network.rb +72 -0
- data/lib/arkenstone/query_builder.rb +84 -0
- data/lib/arkenstone/queryable.rb +38 -0
- data/lib/arkenstone/timestamps.rb +25 -0
- data/lib/arkenstone/validation/validation_error.rb +34 -0
- data/lib/arkenstone/validation/validations.rb +192 -0
- data/lib/arkenstone/version.rb +5 -0
- data/lib/arkenstone.rb +22 -0
- data/test/associations/test_document_overrides.rb +50 -0
- data/test/associations/test_has_and_belongs_to_many.rb +80 -0
- data/test/dummy/app/models/association.rb +35 -0
- data/test/dummy/app/models/superuser.rb +8 -0
- data/test/dummy/app/models/user.rb +10 -0
- data/test/spec_helper.rb +16 -0
- data/test/test_arkenstone.rb +354 -0
- data/test/test_arkenstone_hook_inheritance.rb +39 -0
- data/test/test_associations.rb +327 -0
- data/test/test_enumerables.rb +36 -0
- data/test/test_environment.rb +14 -0
- data/test/test_helpers.rb +18 -0
- data/test/test_hooks.rb +104 -0
- data/test/test_query_builder.rb +163 -0
- data/test/test_queryable.rb +59 -0
- data/test/test_validations.rb +197 -0
- metadata +133 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Raised if an Arkenstone document does not have a URL associated with it. A URL is set with the +url+ directive:
|
4
|
+
#
|
5
|
+
# class Widget
|
6
|
+
# url 'http://example.com/widgets'
|
7
|
+
# end
|
8
|
+
class NoUrlError < StandardError
|
9
|
+
class << self
|
10
|
+
def default_message
|
11
|
+
'A `url` must be defined for the class.'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
module Helpers
|
5
|
+
class << self
|
6
|
+
def included(base)
|
7
|
+
base.send :include, Arkenstone::Helpers::GeneralMethods
|
8
|
+
base.extend Arkenstone::Helpers::GeneralMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module GeneralMethods
|
13
|
+
def full_url(url)
|
14
|
+
url =~ %r{(/$)} ? url : "#{url}/"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
# Environment is a wrapper around most of the properties created in a network request.
|
5
|
+
# A raw net/http object doesn't allow for much customization after it is instantiated. This allows the caller to manipulate data via hooks before a request is created.
|
6
|
+
class Environment
|
7
|
+
attr_accessor :url, :verb, :body, :headers
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
options.each do |key, value|
|
11
|
+
send("#{key}=".to_sym, value) if respond_to? key
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"#{@verb} #{@url}\n#{@body}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
class Hook
|
5
|
+
def before_request(env); end
|
6
|
+
|
7
|
+
def after_complete(response); end
|
8
|
+
|
9
|
+
def encode_attributes(attributes); end
|
10
|
+
|
11
|
+
def on_error(response); end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
### Calls all of the available `before_request` hooks available for the class.
|
15
|
+
def call_request_hooks(klass, request)
|
16
|
+
call_hook klass, proc { |h| h.before_request request }
|
17
|
+
end
|
18
|
+
|
19
|
+
### Calls all of the available `after_complete` hooks available for the class.
|
20
|
+
def call_response_hooks(klass, response)
|
21
|
+
call_hook klass, proc { |h| h.after_complete response }
|
22
|
+
end
|
23
|
+
|
24
|
+
### Calls all of the available `on_error` hooks available for the class.
|
25
|
+
def call_error_hooks(klass, response)
|
26
|
+
call_hook klass, proc { |h| h.on_error response }
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_hooks_for_class(klass)
|
30
|
+
all_hooks = []
|
31
|
+
if klass.arkenstone_inherit_hooks
|
32
|
+
klass.ancestors.each do |ancestor|
|
33
|
+
break if ancestor == Arkenstone::Associations::InstanceMethods
|
34
|
+
break unless ancestor.respond_to?(:arkenstone_hooks)
|
35
|
+
|
36
|
+
all_hooks.concat ancestor.arkenstone_hooks unless ancestor.arkenstone_hooks.nil?
|
37
|
+
end
|
38
|
+
else
|
39
|
+
all_hooks = klass.arkenstone_hooks
|
40
|
+
end
|
41
|
+
all_hooks
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_hooks?(klass)
|
45
|
+
return true if klass_has_hooks? klass
|
46
|
+
if klass.respond_to?(:arkenstone_inherit_hooks) && klass.arkenstone_inherit_hooks
|
47
|
+
return klass.ancestors.any? { |ancestor| klass_has_hooks? ancestor }
|
48
|
+
end
|
49
|
+
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def klass_has_hooks?(klass)
|
54
|
+
klass.respond_to?(:arkenstone_hooks) and klass.arkenstone_hooks and klass.arkenstone_hooks.count.positive?
|
55
|
+
end
|
56
|
+
|
57
|
+
def call_hook(klass, enumerator)
|
58
|
+
hooks = []
|
59
|
+
if klass.arkenstone_inherit_hooks == true
|
60
|
+
klass.ancestors.each do |ancestor|
|
61
|
+
break if ancestor == Arkenstone::Associations::InstanceMethods
|
62
|
+
|
63
|
+
if ancestor.respond_to?(:arkenstone_hooks) && !ancestor.arkenstone_hooks.nil?
|
64
|
+
hooks.concat ancestor.arkenstone_hooks
|
65
|
+
end
|
66
|
+
end
|
67
|
+
else
|
68
|
+
hooks = klass.arkenstone_hooks
|
69
|
+
end
|
70
|
+
hooks&.each(&enumerator)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
module Network
|
5
|
+
module ClassMethods
|
6
|
+
def send_request(url, verb, data = nil, call_hooks = true)
|
7
|
+
env = Arkenstone::Environment.new url:, verb:, body: data
|
8
|
+
Arkenstone::Hook.call_request_hooks self, env if call_hooks
|
9
|
+
response = Arkenstone::Network.send_request env
|
10
|
+
handle_response response
|
11
|
+
response
|
12
|
+
end
|
13
|
+
|
14
|
+
### Takes appropriate action if the request was a success or failure.
|
15
|
+
def handle_response(response)
|
16
|
+
if Arkenstone::Network.response_is_success response
|
17
|
+
Arkenstone::Hook.call_response_hooks self, response
|
18
|
+
else
|
19
|
+
Arkenstone::Hook.call_error_hooks self, response
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class << self
|
25
|
+
def included(base)
|
26
|
+
base.extend Arkenstone::Network::ClassMethods
|
27
|
+
end
|
28
|
+
|
29
|
+
### All http requests go through here.
|
30
|
+
def send_request(request_env)
|
31
|
+
http = create_http request_env.url
|
32
|
+
request = build_request request_env.url, request_env.verb
|
33
|
+
set_request_data request, request_env.body
|
34
|
+
set_request_headers request, request_env.headers unless request_env.headers.nil?
|
35
|
+
http.request request
|
36
|
+
end
|
37
|
+
|
38
|
+
### Determines if the response was successful.
|
39
|
+
# TODO: Refactor this to handle more status codes.
|
40
|
+
# TODO: How do we handle redirects (30x)?
|
41
|
+
def response_is_success(response)
|
42
|
+
%w[200 204].include? response.code
|
43
|
+
end
|
44
|
+
|
45
|
+
### Creates the http object used for requests.
|
46
|
+
def create_http(url)
|
47
|
+
uri = URI(url)
|
48
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
49
|
+
http.use_ssl = true if uri.scheme == 'https'
|
50
|
+
http
|
51
|
+
end
|
52
|
+
|
53
|
+
### Builds a Net::HTTP request object for the appropriate verb.
|
54
|
+
def build_request(url, verb)
|
55
|
+
klass = Kernel.const_get('Net::HTTP').const_get(verb.capitalize)
|
56
|
+
klass.new URI(url)
|
57
|
+
end
|
58
|
+
|
59
|
+
### Fills in the body of a request with the appropriate serialized data.
|
60
|
+
def set_request_data(request, data)
|
61
|
+
data = data.to_json unless data.instance_of?(String)
|
62
|
+
request.body = data
|
63
|
+
request.content_type = 'application/json'
|
64
|
+
end
|
65
|
+
|
66
|
+
### Sets HTTP headers on the request.
|
67
|
+
def set_request_headers(request, headers)
|
68
|
+
headers.each { |key, val| request.add_field key, val }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
#
|
5
|
+
# = Builder
|
6
|
+
# Builds up the query from the DSL and returns a ruby hash.
|
7
|
+
class QueryBuilder
|
8
|
+
### Initializes the @hash variable, which is used to build complex queries.
|
9
|
+
def initialize
|
10
|
+
@cache = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
### Main entry point for processing the DSL.
|
14
|
+
def build(&)
|
15
|
+
result = instance_eval(&)
|
16
|
+
result = flush.merge result unless @cache.empty?
|
17
|
+
result.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
### Finds the entries that have a value for a column in the provided +value_array+.
|
21
|
+
def _in(value_array)
|
22
|
+
{ '$in' => value_array }
|
23
|
+
end
|
24
|
+
|
25
|
+
### Finds entries who's values for a column are greater than the value provided.
|
26
|
+
def _gt(val)
|
27
|
+
{ '$gt' => val }
|
28
|
+
end
|
29
|
+
|
30
|
+
### Finds entries who's values for a column are greater than or equal to the value provided.
|
31
|
+
def _gte(val)
|
32
|
+
{ '$gte' => val }
|
33
|
+
end
|
34
|
+
|
35
|
+
### Finds entries who's values for a column are less than the value provided.
|
36
|
+
def _lt(val)
|
37
|
+
{ '$lt' => val }
|
38
|
+
end
|
39
|
+
|
40
|
+
### Finds entries who's values for a column are less than or equal to the value provided.
|
41
|
+
def _lte(val)
|
42
|
+
{ '$lte' => val }
|
43
|
+
end
|
44
|
+
|
45
|
+
### Finds entries that match *all* expressions in the value provided.
|
46
|
+
def _and(*vals)
|
47
|
+
evaluate_expression '$and', vals
|
48
|
+
end
|
49
|
+
|
50
|
+
### Finds entries that match *any* expression in the value provided.
|
51
|
+
def _or(*vals)
|
52
|
+
evaluate_expression '$or', vals
|
53
|
+
end
|
54
|
+
|
55
|
+
### Finds entries that do *not* match any expression in the value provided.
|
56
|
+
def _not(*vals)
|
57
|
+
evaluate_expression '$not', vals
|
58
|
+
end
|
59
|
+
|
60
|
+
### Adds include statements for the database endpoint to parse.
|
61
|
+
def _include(values)
|
62
|
+
@cache = evaluate_expression '$include', values
|
63
|
+
end
|
64
|
+
|
65
|
+
### Sets a max number of results to return.
|
66
|
+
def _limit(max)
|
67
|
+
@cache = evaluate_expression '$limit', max
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
### Evaluates an expression that takes multiple arguments. Used to walk through nested boolean statements.
|
73
|
+
def evaluate_expression(bool_type, hash)
|
74
|
+
{ bool_type.to_s => hash }
|
75
|
+
end
|
76
|
+
|
77
|
+
### Spits out the stored expressions in the +hash+ and resets it.
|
78
|
+
def flush
|
79
|
+
result = @cache
|
80
|
+
@cache = {}
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
module Queryable
|
5
|
+
class << self
|
6
|
+
def included(base)
|
7
|
+
base.extend Arkenstone::Queryable::ClassMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def query_url
|
13
|
+
"#{full_url(arkenstone_url)}query"
|
14
|
+
end
|
15
|
+
|
16
|
+
def where(query = nil, &)
|
17
|
+
check_for_url
|
18
|
+
body = build_where_body(query, &)
|
19
|
+
return nil if body.nil?
|
20
|
+
|
21
|
+
# TODO: - refactor the network stuff into it's own module, so that we don't have `self` here
|
22
|
+
response = send_request query_url, :post, body
|
23
|
+
parse_all response.body if Arkenstone::Network.response_is_success response
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_where_body(query = nil, &)
|
27
|
+
if query.instance_of?(String)
|
28
|
+
body = query
|
29
|
+
elsif query.instance_of?(Hash)
|
30
|
+
body = query.to_json
|
31
|
+
elsif query.nil? && block_given?
|
32
|
+
builder = Arkenstone::QueryBuilder.new
|
33
|
+
body = builder.build(&)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
module Timestamps
|
5
|
+
class << self
|
6
|
+
def included(base)
|
7
|
+
base.send :include, Arkenstone::Timestamps::InstanceMethods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
attr_accessor :created_at, :updated_at
|
13
|
+
|
14
|
+
def timestampable
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def timestamp
|
19
|
+
current_time = Time.now
|
20
|
+
self.created_at = current_time unless created_at
|
21
|
+
self.updated_at = current_time
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
module Validation
|
5
|
+
class ValidationError
|
6
|
+
attr_accessor :messages
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@messages = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def count
|
13
|
+
@messages.count
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
@messages[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(key, val)
|
21
|
+
@messages[key] = val
|
22
|
+
end
|
23
|
+
|
24
|
+
def add(attr, message)
|
25
|
+
errors_for_attr = @messages[attr]
|
26
|
+
if errors_for_attr.nil?
|
27
|
+
@messages[attr] = [message]
|
28
|
+
else
|
29
|
+
errors_for_attr << message
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arkenstone
|
4
|
+
module Validation
|
5
|
+
class << self
|
6
|
+
def included(base)
|
7
|
+
base.send :include, Arkenstone::Validation::InstanceMethods
|
8
|
+
base.extend Arkenstone::Validation::ClassMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
attr_accessor :errors
|
14
|
+
|
15
|
+
### Does a model's attributes pass all of the validation requirements?
|
16
|
+
def valid?
|
17
|
+
validate
|
18
|
+
@errors.count.zero?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Run through all the validators.
|
22
|
+
def validate
|
23
|
+
@errors = Arkenstone::Validation::ValidationError.new
|
24
|
+
validate_with_validators
|
25
|
+
validate_with_custom_validators
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Loops through the provided validators. A validator is passed when a validation method returns nil. All other values are treated as errors.
|
31
|
+
def validate_with_validators
|
32
|
+
return if self.class.fields_to_validate.nil?
|
33
|
+
|
34
|
+
self.class.fields_to_validate.each do |attr, validators|
|
35
|
+
validators.each do |validator_hash, _arg|
|
36
|
+
key = validator_hash.keys.first
|
37
|
+
validation_method = "validate_#{key}"
|
38
|
+
options = validator_hash[key]
|
39
|
+
result = send validation_method, attr, options
|
40
|
+
@errors.add(attr, result) unless result.nil?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Loops through all the custom validators created with `validate`.
|
46
|
+
def validate_with_custom_validators
|
47
|
+
self.class.custom_validators&.each do |custom_validator|
|
48
|
+
send custom_validator
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Checks if the attribute is on the instance of the model. If the attribute is a string, checks if it's empty.
|
53
|
+
# Example:
|
54
|
+
#
|
55
|
+
# validates :name, presence: true
|
56
|
+
#
|
57
|
+
def validate_presence(attr, options)
|
58
|
+
message = options[:message] || "can't be blank"
|
59
|
+
test = options[:presence]
|
60
|
+
method_not_defined = test != self.class.method_defined?(attr)
|
61
|
+
if method_not_defined
|
62
|
+
message
|
63
|
+
else
|
64
|
+
val = send(attr)
|
65
|
+
value_is_nil = test == val.nil?
|
66
|
+
return message if value_is_nil
|
67
|
+
|
68
|
+
value_is_string = val.instance_of?(String)
|
69
|
+
message if value_is_string && val.empty?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_empty(attr, options)
|
74
|
+
message = options[:message] || 'must not be empty'
|
75
|
+
val = send(attr)
|
76
|
+
return message if val.nil?
|
77
|
+
return message if val.respond_to? :empty
|
78
|
+
return message if val.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
# Checks if an attribute is the appropriate boolean value.
|
82
|
+
#
|
83
|
+
# Example:
|
84
|
+
#
|
85
|
+
# validates :accepts_terms, acceptance: true
|
86
|
+
def validate_acceptance(attr, options)
|
87
|
+
acceptance = options[:acceptance]
|
88
|
+
message = options[:message] || "must be #{acceptance}"
|
89
|
+
val = send(attr)
|
90
|
+
message if val != acceptance
|
91
|
+
end
|
92
|
+
|
93
|
+
# Checks if an attribute is an instance of a specific type, or one of its descendents.
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
#
|
97
|
+
# validates :should_be_string, type: String
|
98
|
+
#
|
99
|
+
# That will check if `should_be_string` is a `String` or a subclass of `String`
|
100
|
+
def validate_type(attr, options)
|
101
|
+
type = options[:type]
|
102
|
+
message = options[:message] || "must be type #{type}"
|
103
|
+
val = send(attr)
|
104
|
+
message if !val.nil? && !(val.is_a? type)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Checks if the attribute conforms with the provided regular expression.
|
108
|
+
# Example:
|
109
|
+
#
|
110
|
+
# validates :name, with: format: { with: /\d+/, message: "must be lowercase" }
|
111
|
+
def validate_format(attr, options)
|
112
|
+
val = send attr
|
113
|
+
regex = options[:format][:with]
|
114
|
+
options[:format][:message] if regex.match(val).nil?
|
115
|
+
end
|
116
|
+
|
117
|
+
# Checks if the attribute is on the instance of the model. If the attribute is a string, checks if it's empty.
|
118
|
+
# Example:
|
119
|
+
# attr_accessor :email_confirmation
|
120
|
+
# attributes :email
|
121
|
+
#
|
122
|
+
# validates :email, confirmation: true
|
123
|
+
#
|
124
|
+
def validate_confirmation(attr, options)
|
125
|
+
message = options[:message] || "confirmation does not match #{attr}"
|
126
|
+
test = options[:confirmation]
|
127
|
+
attr_confirmation = "#{attr}_confirmation".to_sym
|
128
|
+
|
129
|
+
confirmation_not_found = test = !self.class.method_defined?(attr_confirmation) # attr_confirmation not defined
|
130
|
+
if confirmation_not_found
|
131
|
+
message
|
132
|
+
else
|
133
|
+
send(attr)
|
134
|
+
return message if send(attr) != send(attr_confirmation)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module ClassMethods
|
140
|
+
class << self
|
141
|
+
def extended(base)
|
142
|
+
base.fields_to_validate = {}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
attr_accessor :fields_to_validate, :custom_validators
|
147
|
+
|
148
|
+
# Adds a custom validator method. Custom validators are responsible for adding errors to the `errors` hash.
|
149
|
+
# Example:
|
150
|
+
#
|
151
|
+
# class MyClass
|
152
|
+
# validate :special_case
|
153
|
+
#
|
154
|
+
# def special_case
|
155
|
+
# if 1 == 2
|
156
|
+
# errors.add(:the_bad_property, "Error Message")
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
def validate(custom_validation_method)
|
161
|
+
self.custom_validators = [] if custom_validators.nil?
|
162
|
+
custom_validators << custom_validation_method
|
163
|
+
end
|
164
|
+
|
165
|
+
# Adds one of the provided validators to an attribute. Specify the validator in the `options` splat.
|
166
|
+
# Example:
|
167
|
+
#
|
168
|
+
# class MyClass
|
169
|
+
# validates :name, presence: true
|
170
|
+
# validates :email, with: format: { with: /[a-z]+/, message: "must be valid email" }
|
171
|
+
# end
|
172
|
+
def validates(attr, *options)
|
173
|
+
self.fields_to_validate = {} if fields_to_validate.nil?
|
174
|
+
sym = attr.downcase.to_sym
|
175
|
+
fields_for_attr = fields_to_validate[sym]
|
176
|
+
if fields_for_attr.nil?
|
177
|
+
fields_for_attr = []
|
178
|
+
fields_to_validate[sym] = fields_for_attr
|
179
|
+
end
|
180
|
+
fields_for_attr << create_validator(Hash[*options])
|
181
|
+
end
|
182
|
+
|
183
|
+
### Creates a validator hash from the options passed into a `validates` method.
|
184
|
+
def create_validator(options_hash)
|
185
|
+
key = options_hash.first[0]
|
186
|
+
validator = {}
|
187
|
+
validator[key] = options_hash
|
188
|
+
validator
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
data/lib/arkenstone.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'arkenstone/version'
|
5
|
+
require 'arkenstone/errors/no_url_error'
|
6
|
+
require 'arkenstone/enumerable/query_list'
|
7
|
+
require 'arkenstone/network/hook'
|
8
|
+
require 'arkenstone/network/env'
|
9
|
+
require 'arkenstone/network/network'
|
10
|
+
require 'arkenstone/associations'
|
11
|
+
require 'arkenstone/associations/resources'
|
12
|
+
require 'arkenstone/validation/validation_error'
|
13
|
+
require 'arkenstone/validation/validations'
|
14
|
+
require 'arkenstone/query_builder'
|
15
|
+
require 'arkenstone/queryable'
|
16
|
+
require 'arkenstone/document'
|
17
|
+
require 'arkenstone/timestamps'
|
18
|
+
require 'arkenstone/helpers'
|
19
|
+
|
20
|
+
module Arkenstone
|
21
|
+
# Your code goes here...
|
22
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
class DocumentOverridesTest < Test::Unit::TestCase
|
6
|
+
def test_reload_override
|
7
|
+
eval %(
|
8
|
+
class Brand
|
9
|
+
include Arkenstone::Document
|
10
|
+
|
11
|
+
url 'http://example.com/brands'
|
12
|
+
attributes :name
|
13
|
+
has_many :balls
|
14
|
+
end
|
15
|
+
|
16
|
+
class Ball
|
17
|
+
include Arkenstone::Document
|
18
|
+
|
19
|
+
attributes :color
|
20
|
+
belongs_to :brand
|
21
|
+
end
|
22
|
+
)
|
23
|
+
|
24
|
+
stub_request(:post, "#{Brand.arkenstone_url}/").to_return(status: '200', body: { id: 1, name: 'Foo' }.to_json)
|
25
|
+
stub_request(:get, "#{Brand.arkenstone_url}/1/balls").to_return(status: 200, body: [].to_json)
|
26
|
+
|
27
|
+
brand = Brand.create(name: 'Foo')
|
28
|
+
assert(!brand.arkenstone_data[:balls])
|
29
|
+
|
30
|
+
stub_request(:post, "#{Brand.arkenstone_url}/1/balls/").to_return(status: '200',
|
31
|
+
body: {
|
32
|
+
id: 1, color: 'blue', brand_id: brand.id
|
33
|
+
}.to_json)
|
34
|
+
|
35
|
+
ball = brand.balls.create(color: 'blue')
|
36
|
+
assert(brand.arkenstone_data[:balls])
|
37
|
+
|
38
|
+
stub_request(:get, "#{Ball.arkenstone_url}/1").to_return(status: 200,
|
39
|
+
body: {
|
40
|
+
id: 1, color: 'blue', brand_id: brand.id
|
41
|
+
}.to_json)
|
42
|
+
ball.reload
|
43
|
+
|
44
|
+
assert(ball.color == 'blue')
|
45
|
+
|
46
|
+
found_ball = Ball.find(ball.id)
|
47
|
+
assert(found_ball.color == 'blue')
|
48
|
+
assert_equal(ball.to_json, found_ball.to_json)
|
49
|
+
end
|
50
|
+
end
|