tiny_dyno 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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.simplecov +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +70 -0
- data/LICENSE +41 -0
- data/README.md +121 -0
- data/Rakefile +6 -0
- data/bin/console +8 -0
- data/bin/fastcheck +25 -0
- data/bin/setup +7 -0
- data/bin/tracer +19 -0
- data/ci/tests.sh +52 -0
- data/lib/config/locales/en.yml +168 -0
- data/lib/tiny_dyno.rb +44 -0
- data/lib/tiny_dyno/adapter.rb +48 -0
- data/lib/tiny_dyno/adapter/items.rb +27 -0
- data/lib/tiny_dyno/adapter/tables.rb +103 -0
- data/lib/tiny_dyno/attributes.rb +157 -0
- data/lib/tiny_dyno/attributes/readonly.rb +56 -0
- data/lib/tiny_dyno/changeable.rb +68 -0
- data/lib/tiny_dyno/document.rb +100 -0
- data/lib/tiny_dyno/document_composition.rb +49 -0
- data/lib/tiny_dyno/errors.rb +4 -0
- data/lib/tiny_dyno/errors/attribute_errors.rb +25 -0
- data/lib/tiny_dyno/errors/hash_key_errors.rb +78 -0
- data/lib/tiny_dyno/errors/tiny_dyno_error.rb +85 -0
- data/lib/tiny_dyno/extensions.rb +1 -0
- data/lib/tiny_dyno/extensions/module.rb +28 -0
- data/lib/tiny_dyno/fields.rb +299 -0
- data/lib/tiny_dyno/fields/standard.rb +64 -0
- data/lib/tiny_dyno/hash_keys.rb +134 -0
- data/lib/tiny_dyno/loggable.rb +69 -0
- data/lib/tiny_dyno/persistable.rb +88 -0
- data/lib/tiny_dyno/range_attributes.rb +15 -0
- data/lib/tiny_dyno/stateful.rb +95 -0
- data/lib/tiny_dyno/tables.rb +98 -0
- data/lib/tiny_dyno/version.rb +3 -0
- data/tiny_dyno.gemspec +41 -0
- metadata +226 -0
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'tiny_dyno/document_composition'
|
2
|
+
|
3
|
+
module TinyDyno
|
4
|
+
module Document
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
extend ActiveModel::Naming
|
8
|
+
include ActiveModel::Dirty
|
9
|
+
include ActiveModel::Model
|
10
|
+
|
11
|
+
include DocumentComposition
|
12
|
+
|
13
|
+
included do
|
14
|
+
TinyDyno.register_model(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Instantiate a new +Document+, setting the Document's attributes if
|
18
|
+
# given. If no attributes are provided, they will be initialized with
|
19
|
+
# an empty +Hash+.
|
20
|
+
#
|
21
|
+
# The Hash Key must currently be provided from the applicationIf a HashKey is defined, the document's id will be set to that key,
|
22
|
+
#
|
23
|
+
# @example Create a new document.
|
24
|
+
# Person.new(hash_key: hash_key, title: "Sir")
|
25
|
+
#
|
26
|
+
# @param [ Hash ] attrs The attributes to set up the document with.
|
27
|
+
#
|
28
|
+
# @return [ Document ] A new document.
|
29
|
+
# @since 1.0.0
|
30
|
+
def initialize(attrs = nil)
|
31
|
+
@new_record = true
|
32
|
+
@attributes ||= {}
|
33
|
+
process_attributes(attrs) do
|
34
|
+
yield(self) if block_given?
|
35
|
+
end
|
36
|
+
# run_callbacks(:initialize) unless _initialize_callbacks.empty?
|
37
|
+
# raise ::TinyDyno::Errors::MissingHashKey.new(self.name) unless @hash_key.is_a?(Hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete
|
41
|
+
request_delete
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def request_delete
|
47
|
+
request = {
|
48
|
+
table_name: self.class.table_name,
|
49
|
+
key: hash_key_as_selector
|
50
|
+
}
|
51
|
+
TinyDyno::Adapter.delete_item(request: request)
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
|
56
|
+
def where(options = {})
|
57
|
+
valid_option_keys(options)
|
58
|
+
get_query = build_where_query(options)
|
59
|
+
attributes = TinyDyno::Adapter.get_item(get_item_request: get_query)
|
60
|
+
if attributes.nil?
|
61
|
+
return false
|
62
|
+
else
|
63
|
+
self.new(attributes)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# minimimum implementation for now
|
70
|
+
# check that each option key relates to a hash_key present on the model
|
71
|
+
# do not permit scan queries
|
72
|
+
def valid_option_keys(options)
|
73
|
+
options.keys.each do |name|
|
74
|
+
named = name.to_s
|
75
|
+
raise TinyDyno::Errors::HashKeysOnly.new(klass: self.class, name: named) unless hash_key_is_defined?(named)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# minimimum implementation for now
|
80
|
+
# build simple query to retrieve document
|
81
|
+
# via get_item
|
82
|
+
# http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#get_item-instance_method
|
83
|
+
def build_where_query(options)
|
84
|
+
query_keys = {}
|
85
|
+
options.each do |k,v|
|
86
|
+
# as expected by DynamoDB
|
87
|
+
typed_key = k.to_s
|
88
|
+
query_keys[typed_key] = dyno_typed_key(key: typed_key, val: v)
|
89
|
+
end
|
90
|
+
{
|
91
|
+
table_name: self.table_name,
|
92
|
+
attributes_to_get: attribute_names,
|
93
|
+
key: query_keys
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'tiny_dyno/attributes'
|
2
|
+
require 'tiny_dyno/changeable'
|
3
|
+
require 'tiny_dyno/fields'
|
4
|
+
require 'tiny_dyno/stateful'
|
5
|
+
require 'tiny_dyno/tables'
|
6
|
+
require 'tiny_dyno/hash_keys'
|
7
|
+
require 'tiny_dyno/persistable'
|
8
|
+
|
9
|
+
module TinyDyno
|
10
|
+
module DocumentComposition
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
include Attributes
|
14
|
+
include Changeable
|
15
|
+
include Fields
|
16
|
+
include HashKeys
|
17
|
+
include Persistable
|
18
|
+
include Stateful
|
19
|
+
include Tables
|
20
|
+
|
21
|
+
|
22
|
+
MODULES = [
|
23
|
+
Attributes,
|
24
|
+
Changeable,
|
25
|
+
Fields,
|
26
|
+
HashKeys,
|
27
|
+
Persistable,
|
28
|
+
Stateful,
|
29
|
+
Tables,
|
30
|
+
]
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
# Get a list of methods that would be a bad idea to define as field names
|
35
|
+
# or override when including TinyDyno::Document.
|
36
|
+
#
|
37
|
+
# @example Bad thing!
|
38
|
+
# TinyDyno::Components.prohibited_methods
|
39
|
+
#
|
40
|
+
# @return [ Array<Symbol> ]
|
41
|
+
def prohibited_methods
|
42
|
+
@prohibited_methods ||= MODULES.flat_map do |mod|
|
43
|
+
mod.instance_methods.map(&:to_sym)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TinyDyno
|
3
|
+
module Errors
|
4
|
+
|
5
|
+
# This error is raised when trying to set a value in TinyDyno that is not
|
6
|
+
# already set with dynamic attributes or the field is not defined.
|
7
|
+
class UnknownAttribute < TinyDynoError
|
8
|
+
|
9
|
+
# Create the new error.
|
10
|
+
#
|
11
|
+
# @example Instantiate the error.
|
12
|
+
# UnknownAttribute.new(Person, "gender")
|
13
|
+
#
|
14
|
+
# @param [ Class ] klass The model class.
|
15
|
+
# @param [ String, Symbol ] name The name of the attribute.
|
16
|
+
#
|
17
|
+
# @since 3.0.0
|
18
|
+
def initialize(klass, name)
|
19
|
+
super(
|
20
|
+
compose_message("unknown_attribute", { klass: klass.name, name: name })
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TinyDyno
|
3
|
+
module Errors
|
4
|
+
|
5
|
+
# This error is raised when trying to set a value in Mongoid that is not
|
6
|
+
# already set with dynamic attributes or the field is not defined.
|
7
|
+
class InvalidHashKey < TinyDynoError
|
8
|
+
|
9
|
+
# Create the new error.
|
10
|
+
#
|
11
|
+
# @example Instantiate the error.
|
12
|
+
# UnknownAttribute.new(Person, "gender")
|
13
|
+
#
|
14
|
+
# @param [ Class ] klass The model class.
|
15
|
+
# @param [ String, Symbol ] name The name of the attribute.
|
16
|
+
#
|
17
|
+
# @since 3.0.0
|
18
|
+
def initialize(klass:, name:)
|
19
|
+
super(
|
20
|
+
compose_message("invalid hash_key", { klass: klass.name, name: name })
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# encoding: utf-8
|
28
|
+
module TinyDyno
|
29
|
+
module Errors
|
30
|
+
|
31
|
+
# This error is raised when trying to set a value in Mongoid that is not
|
32
|
+
# already set with dynamic attributes or the field is not defined.
|
33
|
+
class MissingHashKey < TinyDynoError
|
34
|
+
|
35
|
+
# Create the new error.
|
36
|
+
#
|
37
|
+
# @example Instantiate the error.
|
38
|
+
# UnknownAttribute.new(Person, "gender")
|
39
|
+
#
|
40
|
+
# @param [ Class ] klass The model class.
|
41
|
+
# @param [ String, Symbol ] name The name of the attribute.
|
42
|
+
#
|
43
|
+
# @since 3.0.0
|
44
|
+
def initialize(klass:)
|
45
|
+
super(
|
46
|
+
compose_message("no hash key specified", { klass: klass.name })
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# encoding: utf-8
|
55
|
+
module TinyDyno
|
56
|
+
module Errors
|
57
|
+
|
58
|
+
# This error is raised, when a query is performed with fields specified
|
59
|
+
# that are not HashKeys, which would result in a table scan
|
60
|
+
class HashKeysOnly < TinyDynoError
|
61
|
+
|
62
|
+
# Create the new error.
|
63
|
+
#
|
64
|
+
# @example Instantiate the error.
|
65
|
+
# HashKeysOnly.new(Person, "gender")
|
66
|
+
#
|
67
|
+
# @param [ Class ] klass The model class.
|
68
|
+
# @param [ String, Symbol ] name The name of the attribute.
|
69
|
+
#
|
70
|
+
# @since 3.0.0
|
71
|
+
def initialize(klass:, name:)
|
72
|
+
super(
|
73
|
+
compose_message("only search for hash keys", { klass: klass.name, name: name })
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TinyDyno::Errors
|
3
|
+
class TinyDynoError < StandardError
|
4
|
+
|
5
|
+
BASE_KEY = "tiny_dyno.errors.messages"
|
6
|
+
|
7
|
+
# Compose the message.
|
8
|
+
#
|
9
|
+
# @example Create the message
|
10
|
+
# error.compose_message
|
11
|
+
#
|
12
|
+
# @return [ String ] The composed message.
|
13
|
+
def compose_message(key, attributes)
|
14
|
+
@problem = problem(key, attributes)
|
15
|
+
@summary = summary(key, attributes)
|
16
|
+
@resolution = resolution(key, attributes)
|
17
|
+
|
18
|
+
"\nProblem:\n #{@problem}"+
|
19
|
+
"\nSummary:\n #{@summary}"+
|
20
|
+
"\nResolution:\n #{@resolution}"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Given the key of the specific error and the options hash, translate the
|
26
|
+
# message.
|
27
|
+
#
|
28
|
+
# @example Translate the message.
|
29
|
+
# error.translate("errors", :key => value)
|
30
|
+
#
|
31
|
+
# @param [ String ] key The key of the error in the locales.
|
32
|
+
# @param [ Hash ] options The objects to pass to create the message.
|
33
|
+
#
|
34
|
+
# @return [ String ] A localized error message string.
|
35
|
+
def translate(key, options)
|
36
|
+
::I18n.translate("#{BASE_KEY}.#{key}", options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create the problem.
|
40
|
+
#
|
41
|
+
# @example Create the problem.
|
42
|
+
# error.problem("error", {})
|
43
|
+
#
|
44
|
+
# @param [ String, Symbol ] key The error key.
|
45
|
+
# @param [ Hash ] attributes The attributes to interpolate.
|
46
|
+
#
|
47
|
+
# @return [ String ] The problem.
|
48
|
+
#
|
49
|
+
# @since 3.0.0
|
50
|
+
def problem(key, attributes)
|
51
|
+
translate("#{key}.message", attributes)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Create the summary.
|
55
|
+
#
|
56
|
+
# @example Create the summary.
|
57
|
+
# error.summary("error", {})
|
58
|
+
#
|
59
|
+
# @param [ String, Symbol ] key The error key.
|
60
|
+
# @param [ Hash ] attributes The attributes to interpolate.
|
61
|
+
#
|
62
|
+
# @return [ String ] The summary.
|
63
|
+
#
|
64
|
+
# @since 3.0.0
|
65
|
+
def summary(key, attributes)
|
66
|
+
translate("#{key}.summary", attributes)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create the resolution.
|
70
|
+
#
|
71
|
+
# @example Create the resolution.
|
72
|
+
# error.resolution("error", {})
|
73
|
+
#
|
74
|
+
# @param [ String, Symbol ] key The error key.
|
75
|
+
# @param [ Hash ] attributes The attributes to interpolate.
|
76
|
+
#
|
77
|
+
# @return [ String ] The resolution.
|
78
|
+
#
|
79
|
+
# @since 3.0.0
|
80
|
+
def resolution(key, attributes)
|
81
|
+
translate("#{key}.resolution", attributes)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'tiny_dyno/extensions/module'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module TinyDyno
|
3
|
+
module Extensions
|
4
|
+
module Module
|
5
|
+
|
6
|
+
# Redefine the method. Will undef the method if it exists or simply
|
7
|
+
# just define it.
|
8
|
+
#
|
9
|
+
# @example Redefine the method.
|
10
|
+
# Object.re_define_method("exists?") do
|
11
|
+
# self
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# @param [ String, Symbol ] name The name of the method.
|
15
|
+
# @param [ Proc ] block The method body.
|
16
|
+
#
|
17
|
+
# @return [ Method ] The new method.
|
18
|
+
#
|
19
|
+
# @since 3.0.0
|
20
|
+
def re_define_method(name, &block)
|
21
|
+
undef_method(name) if method_defined?(name)
|
22
|
+
define_method(name, &block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
::Module.__send__(:include, TinyDyno::Extensions::Module)
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'tiny_dyno/fields/standard'
|
2
|
+
|
3
|
+
module TinyDyno
|
4
|
+
module Fields
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
TYPE_MAPPINGS= {
|
8
|
+
# binary_blob: 'B',
|
9
|
+
# bool: Boolean,
|
10
|
+
# binary_set: Array,
|
11
|
+
list: Array,
|
12
|
+
map: Hash,
|
13
|
+
number: Integer,
|
14
|
+
number_set: Array,
|
15
|
+
# null: Null,
|
16
|
+
string: String,
|
17
|
+
string_set: Array,
|
18
|
+
time: Time,
|
19
|
+
}
|
20
|
+
|
21
|
+
SUPPORTED_FIELD_TYPES = [Array, Hash, Integer, Array, String, Time].freeze
|
22
|
+
|
23
|
+
included do
|
24
|
+
class_attribute :fields
|
25
|
+
|
26
|
+
self.fields = {}
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class << self
|
31
|
+
|
32
|
+
# Stores the provided block to be run when the option name specified is
|
33
|
+
# defined on a field.
|
34
|
+
#
|
35
|
+
# No assumptions are made about what sort of work the handler might
|
36
|
+
# perform, so it will always be called if the `option_name` key is
|
37
|
+
# provided in the field definition -- even if it is false or nil.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# TinyDyno::Fields.option :required do |model, field, value|
|
41
|
+
# model.validates_presence_of field if value
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# @param [ Symbol ] option_name the option name to match against
|
45
|
+
# @param [ Proc ] block the handler to execute when the option is
|
46
|
+
# provided.
|
47
|
+
#
|
48
|
+
# @since 2.1.0
|
49
|
+
|
50
|
+
def option(option_name, &block)
|
51
|
+
options[option_name] = block
|
52
|
+
end
|
53
|
+
|
54
|
+
# Return a map of custom option names to their handlers.
|
55
|
+
#
|
56
|
+
# @example
|
57
|
+
# TinyDyno::Fields.options
|
58
|
+
# # => { :required => #<Proc:0x00000100976b38> }
|
59
|
+
#
|
60
|
+
# @return [ Hash ] the option map
|
61
|
+
#
|
62
|
+
# @since 2.1.0
|
63
|
+
def options
|
64
|
+
@options ||= {}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Get the name of the provided field as it is stored in the database.
|
69
|
+
# Used in determining if the field is aliased or not.
|
70
|
+
#
|
71
|
+
# @example Get the database field name.
|
72
|
+
# model.database_field_name(:authorization)
|
73
|
+
#
|
74
|
+
# @param [ String, Symbol ] name The name to get.
|
75
|
+
#
|
76
|
+
# @return [ String ] The name of the field as it's stored in the db.
|
77
|
+
#
|
78
|
+
# @since 3.0.7
|
79
|
+
def database_field_name(name)
|
80
|
+
self.class.database_field_name(name)
|
81
|
+
end
|
82
|
+
|
83
|
+
module ClassMethods
|
84
|
+
|
85
|
+
# Returns an array of names for the attributes available on this object.
|
86
|
+
#
|
87
|
+
# Provides the field names in an ORM-agnostic way. Rails v3.1+ uses this
|
88
|
+
# method to automatically wrap params in JSON requests.
|
89
|
+
#
|
90
|
+
# @example Get the field names
|
91
|
+
# Model.attribute_names
|
92
|
+
#
|
93
|
+
# @return [ Array<String> ] The field names
|
94
|
+
#
|
95
|
+
# @since 3.0.0
|
96
|
+
def attribute_names
|
97
|
+
fields.keys
|
98
|
+
end
|
99
|
+
|
100
|
+
# Get the name of the provided field as it is stored in the database.
|
101
|
+
# Used in determining if the field is aliased or not.
|
102
|
+
#
|
103
|
+
# @example Get the database field name.
|
104
|
+
# Model.database_field_name(:authorization)
|
105
|
+
#
|
106
|
+
# @param [ String, Symbol ] name The name to get.
|
107
|
+
#
|
108
|
+
# @return [ String ] The name of the field as it's stored in the db.
|
109
|
+
#
|
110
|
+
# @since 3.0.7
|
111
|
+
def database_field_name(name)
|
112
|
+
return nil unless name
|
113
|
+
normalized = name.to_s
|
114
|
+
end
|
115
|
+
|
116
|
+
# Defines all the fields that are accessible on the Document
|
117
|
+
# For each field that is defined, a getter and setter will be
|
118
|
+
# added as an instance method to the Document.
|
119
|
+
#
|
120
|
+
# @example Define a field.
|
121
|
+
# field :score, :type => Integer, :default => 0
|
122
|
+
#
|
123
|
+
# @param [ Symbol ] name The name of the field.
|
124
|
+
# @param [ Hash ] options The options to pass to the field.
|
125
|
+
#
|
126
|
+
# @option options [ Class ] :type The type of the field.
|
127
|
+
# @option options [ String ] :label The label for the field.
|
128
|
+
# @option options [ Object, Proc ] :default The field's default
|
129
|
+
#
|
130
|
+
# @return [ Field ] The generated field
|
131
|
+
def field(name, options = {})
|
132
|
+
named = name.to_s
|
133
|
+
added = add_field(named, options)
|
134
|
+
added
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
|
139
|
+
# Define a field attribute for the +Document+.
|
140
|
+
#
|
141
|
+
# @example Set the field.
|
142
|
+
# Person.add_field(:name, :default => "Test")
|
143
|
+
#
|
144
|
+
# @param [ Symbol ] name The name of the field.
|
145
|
+
# @param [ Hash ] options The hash of options.
|
146
|
+
def add_field(name, options = {})
|
147
|
+
field = field_for(name, options)
|
148
|
+
fields[name] = field
|
149
|
+
create_accessors(name, name, options)
|
150
|
+
process_options(field)
|
151
|
+
field
|
152
|
+
end
|
153
|
+
|
154
|
+
# Run through all custom options stored in TinyDyno::Fields.options and
|
155
|
+
# execute the handler if the option is provided.
|
156
|
+
#
|
157
|
+
# @example
|
158
|
+
# TinyDyno::Fields.option :custom do
|
159
|
+
# puts "called"
|
160
|
+
# end
|
161
|
+
#
|
162
|
+
# field = TinyDyno::Fields.new(:test, :custom => true)
|
163
|
+
# Person.process_options(field)
|
164
|
+
# # => "called"
|
165
|
+
#
|
166
|
+
# @param [ Field ] field the field to process
|
167
|
+
def process_options(field)
|
168
|
+
field_options = field.options
|
169
|
+
|
170
|
+
Fields.options.each_pair do |option_name, handler|
|
171
|
+
if field_options.key?(option_name)
|
172
|
+
handler.call(self, field, field_options[option_name])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def field_for(name, options)
|
178
|
+
opts = options.merge(klass: self)
|
179
|
+
Fields::Standard.new(name, opts)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Create the field accessors.
|
183
|
+
#
|
184
|
+
# @example Generate the accessors.
|
185
|
+
# Person.create_accessors(:name, "name")
|
186
|
+
# person.name #=> returns the field
|
187
|
+
# person.name = "" #=> sets the field
|
188
|
+
# person.name? #=> Is the field present?
|
189
|
+
# person.name_before_type_cast #=> returns the field before type cast
|
190
|
+
#
|
191
|
+
# @param [ Symbol ] name The name of the field.
|
192
|
+
# @param [ Symbol ] meth The name of the accessor.
|
193
|
+
# @param [ Hash ] options The options.
|
194
|
+
#
|
195
|
+
# @since 2.0.0
|
196
|
+
def create_accessors(name, meth, options = {})
|
197
|
+
field = fields[name]
|
198
|
+
|
199
|
+
create_field_getter(name, meth, field)
|
200
|
+
create_field_setter(name, meth, field)
|
201
|
+
create_field_check(name, meth)
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
# Create the getter method for the provided field.
|
206
|
+
#
|
207
|
+
# @example Create the getter.
|
208
|
+
# Model.create_field_getter("name", "name", field)
|
209
|
+
#
|
210
|
+
# @param [ String ] name The name of the attribute.
|
211
|
+
# @param [ String ] meth The name of the method.
|
212
|
+
# @param [ Field ] field The field.
|
213
|
+
def create_field_getter(name, meth, field)
|
214
|
+
generated_methods.module_eval do
|
215
|
+
re_define_method(meth) do
|
216
|
+
raw = read_attribute(name)
|
217
|
+
value = typed_value_for(name, raw)
|
218
|
+
attribute_will_change!(value)
|
219
|
+
value
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Create the getter_before_type_cast method for the provided field. If
|
225
|
+
# the attribute has been assigned, return the attribute before it was
|
226
|
+
# type cast. Otherwise, delegate to the getter.
|
227
|
+
#
|
228
|
+
# @example Create the getter_before_type_cast.
|
229
|
+
# Model.create_field_getter_before_type_cast("name", "name")
|
230
|
+
#
|
231
|
+
# @param [ String ] name The name of the attribute.
|
232
|
+
# @param [ String ] meth The name of the method.
|
233
|
+
#
|
234
|
+
def create_field_getter_before_type_cast(name, meth)
|
235
|
+
generated_methods.module_eval do
|
236
|
+
re_define_method("#{meth}_before_type_cast") do
|
237
|
+
if has_attribute_before_type_cast?(name)
|
238
|
+
read_attribute_before_type_cast(name)
|
239
|
+
else
|
240
|
+
send meth
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
# Create the setter method for the provided field.
|
246
|
+
#
|
247
|
+
# @example Create the setter.
|
248
|
+
# Model.create_field_setter("name", "name")
|
249
|
+
#
|
250
|
+
# @param [ String ] name The name of the attribute.
|
251
|
+
# @param [ String ] meth The name of the method.
|
252
|
+
# @param [ Field ] field The field.
|
253
|
+
def create_field_setter(name, meth, field)
|
254
|
+
generated_methods.module_eval do
|
255
|
+
re_define_method("#{meth}=") do |value|
|
256
|
+
val = write_attribute(name, value)
|
257
|
+
val
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Create the check method for the provided field.
|
263
|
+
#
|
264
|
+
# @example Create the check.
|
265
|
+
# Model.create_field_check("name", "name")
|
266
|
+
#
|
267
|
+
# @param [ String ] name The name of the attribute.
|
268
|
+
# @param [ String ] meth The name of the method.
|
269
|
+
#
|
270
|
+
# @since 2.4.0
|
271
|
+
def create_field_check(name, meth)
|
272
|
+
generated_methods.module_eval do
|
273
|
+
re_define_method("#{meth}?") do
|
274
|
+
attr = read_attribute(name)
|
275
|
+
attr == true || attr.present?
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# Include the field methods as a module, so they can be overridden.
|
281
|
+
#
|
282
|
+
# @example Include the fields.
|
283
|
+
# Person.generated_methods
|
284
|
+
#
|
285
|
+
# @return [ Module ] The module of generated methods.
|
286
|
+
#
|
287
|
+
# @since 2.0.0
|
288
|
+
def generated_methods
|
289
|
+
@generated_methods ||= begin
|
290
|
+
mod = Module.new
|
291
|
+
include(mod)
|
292
|
+
mod
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
end
|