tiny_dyno 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|