guise 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.codeclimate.yml +36 -0
- data/.rubocop.yml +1063 -0
- data/.travis.yml +25 -5
- data/.yardopts +1 -0
- data/Appraisals +5 -0
- data/README.md +120 -29
- data/gemfiles/3.1.gemfile +1 -1
- data/gemfiles/3.1.gemfile.lock +39 -30
- data/gemfiles/3.2.gemfile +1 -1
- data/gemfiles/3.2.gemfile.lock +47 -38
- data/gemfiles/4.0.gemfile +1 -1
- data/gemfiles/4.0.gemfile.lock +47 -38
- data/gemfiles/4.1.gemfile +1 -1
- data/gemfiles/4.1.gemfile.lock +45 -38
- data/gemfiles/4.2.gemfile +1 -1
- data/gemfiles/4.2.gemfile.lock +44 -37
- data/gemfiles/5.0.gemfile +8 -0
- data/gemfiles/5.0.gemfile.lock +94 -0
- data/guise.gemspec +9 -14
- data/lib/guise.rb +29 -5
- data/lib/guise/builders.rb +129 -0
- data/lib/guise/callbacks.rb +3 -0
- data/lib/guise/errors.rb +32 -0
- data/lib/guise/introspection.rb +23 -11
- data/lib/guise/options.rb +75 -0
- data/lib/guise/registry.rb +19 -0
- data/lib/guise/scopes.rb +65 -0
- data/lib/guise/syntax.rb +158 -83
- data/lib/guise/version.rb +1 -1
- data/spec/guise_spec.rb +112 -46
- data/spec/spec_helper.rb +12 -8
- metadata +50 -29
- data/spec/factories.rb +0 -39
data/guise.gemspec
CHANGED
@@ -19,22 +19,17 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.version = Guise::VERSION
|
20
20
|
gem.license = 'MIT'
|
21
21
|
|
22
|
-
gem.required_ruby_version =
|
22
|
+
gem.required_ruby_version = ">= 2.0.0"
|
23
23
|
|
24
24
|
gem.add_dependency "activerecord", ">= 3.1", "< 5.0"
|
25
25
|
gem.add_dependency "activesupport", ">= 3.1", "< 5.0"
|
26
|
-
gem.add_development_dependency
|
26
|
+
gem.add_development_dependency "appraisal", ">= 1.0"
|
27
|
+
gem.add_development_dependency "byebug", "~> 5.0"
|
28
|
+
gem.add_development_dependency "codeclimate-test-reporter", "~> 0.3"
|
29
|
+
gem.add_development_dependency "pry", "~> 0.9"
|
30
|
+
gem.add_development_dependency "rake", "~> 10.1"
|
31
|
+
gem.add_development_dependency "redcarpet", "~> 3.2"
|
27
32
|
gem.add_development_dependency "rspec", "~> 3.0"
|
28
|
-
gem.add_development_dependency "
|
29
|
-
gem.add_development_dependency "
|
30
|
-
gem.add_development_dependency "appraisal", '~> 0.5'
|
31
|
-
gem.add_development_dependency 'pry', '~> 0.9'
|
32
|
-
gem.add_development_dependency 'codeclimate-test-reporter', '~> 0.3'
|
33
|
-
|
34
|
-
if RUBY_PLATFORM == 'java'
|
35
|
-
gem.add_development_dependency 'activerecord-jdbcsqlite3-adapter'
|
36
|
-
gem.add_development_dependency 'jdbc-sqlite3'
|
37
|
-
else
|
38
|
-
gem.add_development_dependency 'sqlite3', '~> 1.3'
|
39
|
-
end
|
33
|
+
gem.add_development_dependency "sqlite3", "~> 1.3"
|
34
|
+
gem.add_development_dependency "yard", "~> 0.8"
|
40
35
|
end
|
data/lib/guise.rb
CHANGED
@@ -1,15 +1,39 @@
|
|
1
|
+
require "active_support/core_ext/string/inflections"
|
2
|
+
require "active_support/core_ext/array/extract_options"
|
1
3
|
require 'active_support/core_ext/module/attribute_accessors'
|
4
|
+
require "active_support/lazy_load_hooks"
|
2
5
|
|
3
6
|
require 'guise/version'
|
7
|
+
require "guise/errors"
|
8
|
+
require "guise/registry"
|
9
|
+
require "guise/options"
|
10
|
+
require "guise/builders"
|
11
|
+
require "guise/scopes"
|
4
12
|
require 'guise/callbacks'
|
5
|
-
require
|
6
|
-
require
|
13
|
+
require "guise/introspection"
|
14
|
+
require "guise/syntax"
|
7
15
|
|
8
16
|
module Guise
|
9
17
|
mattr_reader :registry
|
10
|
-
@@registry =
|
18
|
+
@@registry = Registry.new
|
19
|
+
|
20
|
+
DEFAULT_ASSOCIATION_NAME = "guises"
|
21
|
+
DEFAULT_ATTRIBUTE_NAME = "value"
|
22
|
+
|
23
|
+
def self.register_source(source_class, *guises)
|
24
|
+
options = Options.new(source_class, *guises)
|
25
|
+
registry[source_class.name] = options
|
26
|
+
|
27
|
+
HasGuisesBuilder.new(options).build!
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.register_association(association_class, source_class_name, association_options)
|
31
|
+
options = registry[source_class_name]
|
32
|
+
|
33
|
+
GuiseForBuilder.new(association_class, options, association_options).build!
|
34
|
+
end
|
11
35
|
end
|
12
36
|
|
13
|
-
|
14
|
-
|
37
|
+
ActiveSupport.on_load(:active_record) do
|
38
|
+
extend Guise::Syntax
|
15
39
|
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require "guise/scopes"
|
2
|
+
|
3
|
+
module Guise
|
4
|
+
# @api private
|
5
|
+
class HasGuisesBuilder
|
6
|
+
def initialize(options)
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def build!
|
11
|
+
set_guise_options!
|
12
|
+
define_association!
|
13
|
+
define_scopes!
|
14
|
+
|
15
|
+
if @options.association_options != DEFAULT_ASSOCIATION_NAME
|
16
|
+
define_association_aliases!
|
17
|
+
end
|
18
|
+
|
19
|
+
define_introspection_aliases!
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def set_guise_options!
|
25
|
+
source_class.class_attribute :guise_options, instance_writer: false
|
26
|
+
source_class.guise_options = @options
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_association!
|
30
|
+
source_class.has_many(@options.association_name, @options.association_options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_scopes!
|
34
|
+
@options.values.each do |value|
|
35
|
+
method_name = value.underscore
|
36
|
+
scope_name = method_name.pluralize
|
37
|
+
|
38
|
+
source_class.scope scope_name, HasGuisesScope.new(value, @options)
|
39
|
+
|
40
|
+
source_class.class_eval <<-METHOD
|
41
|
+
def #{method_name}?
|
42
|
+
has_guise?('#{value}')
|
43
|
+
end
|
44
|
+
METHOD
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def define_association_aliases!
|
49
|
+
source_class.class_eval <<-ASSOCIATION_ALIASES
|
50
|
+
alias_method :guises, :#{association_name}
|
51
|
+
alias_method :guises=, :#{association_name}=
|
52
|
+
alias_method :guise_ids, :#{association_name_singular}_ids
|
53
|
+
alias_method :guise_ids=, :#{association_name_singular}_ids=
|
54
|
+
ASSOCIATION_ALIASES
|
55
|
+
end
|
56
|
+
|
57
|
+
def define_introspection_aliases!
|
58
|
+
source_class.class_eval <<-INTROSPECTION_ALIASES
|
59
|
+
alias_method :has_#{association_name_singular}?, :has_guise?
|
60
|
+
alias_method :has_#{association_name}?, :has_guises?
|
61
|
+
alias_method :has_any_#{association_name}?, :has_any_guises?
|
62
|
+
INTROSPECTION_ALIASES
|
63
|
+
end
|
64
|
+
|
65
|
+
def source_class
|
66
|
+
@options.source_class
|
67
|
+
end
|
68
|
+
|
69
|
+
def association_name
|
70
|
+
@options.association_name
|
71
|
+
end
|
72
|
+
|
73
|
+
def association_name_singular
|
74
|
+
@options.association_name_singular
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
class GuiseForBuilder
|
80
|
+
def initialize(association_class, options, association_options)
|
81
|
+
@association_class = association_class
|
82
|
+
@options = options
|
83
|
+
@association_options = association_options.reverse_merge!(@options.default_association_options)
|
84
|
+
@define_validations = !@association_options.delete(:validate)
|
85
|
+
end
|
86
|
+
|
87
|
+
def build!
|
88
|
+
update_guise_options!
|
89
|
+
define_association!
|
90
|
+
define_scopes!
|
91
|
+
|
92
|
+
if define_validations?
|
93
|
+
define_validations!
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def define_validations?
|
100
|
+
@define_validations
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_guise_options!
|
104
|
+
@options.association_class = @association_class
|
105
|
+
end
|
106
|
+
|
107
|
+
def define_association!
|
108
|
+
@association_class.belongs_to(@options.source_association_name, @association_options)
|
109
|
+
end
|
110
|
+
|
111
|
+
def define_scopes!
|
112
|
+
@options.values.each do |value|
|
113
|
+
@association_class.scope(
|
114
|
+
value.underscore.pluralize,
|
115
|
+
GuiseForScope.new(value, @options)
|
116
|
+
)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def define_validations!
|
121
|
+
@association_class.validates(
|
122
|
+
@options.attribute,
|
123
|
+
uniqueness: { scope: @options.association_options[:foreign_key] },
|
124
|
+
presence: true,
|
125
|
+
inclusion: { in: @options.values }
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/guise/callbacks.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
module Guise
|
2
|
+
# @api private
|
2
3
|
class Callback
|
3
4
|
def initialize(guise, attribute)
|
4
5
|
@guise = guise
|
@@ -6,6 +7,7 @@ module Guise
|
|
6
7
|
end
|
7
8
|
end
|
8
9
|
|
10
|
+
# @api private
|
9
11
|
class SourceCallback < Callback
|
10
12
|
def after_initialize(record)
|
11
13
|
if record.new_record?
|
@@ -14,6 +16,7 @@ module Guise
|
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
19
|
+
# @api private
|
17
20
|
class AssociationCallback < Callback
|
18
21
|
def after_initialize(record)
|
19
22
|
record.assign_attributes(@attribute => @guise)
|
data/lib/guise/errors.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Guise
|
2
|
+
class DefinitionNotFound < StandardError
|
3
|
+
def initialize(name)
|
4
|
+
@name = name
|
5
|
+
end
|
6
|
+
|
7
|
+
def message
|
8
|
+
"no guises defined for #{@name.inspect}"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class DuplicateDefinition < StandardError
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
def message
|
18
|
+
"guise definition for #{@name.inspect} already exists"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class InvalidGuiseValue < ArgumentError
|
23
|
+
def initialize(guise_value, klass)
|
24
|
+
@guise_value = guise_value
|
25
|
+
@klass = klass
|
26
|
+
end
|
27
|
+
|
28
|
+
def message
|
29
|
+
"`#{guise_value}' is not a defined guise value for #{klass}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/guise/introspection.rb
CHANGED
@@ -1,31 +1,43 @@
|
|
1
|
-
require
|
1
|
+
require "active_support/core_ext/string/inflections"
|
2
2
|
|
3
3
|
module Guise
|
4
|
+
# {Introspection} handles checking if a record has one or more `guise` records
|
5
|
+
# associated with it.
|
4
6
|
module Introspection
|
5
|
-
|
6
|
-
|
7
|
+
# Checks if the record has a `guise` record identified by on the specified
|
8
|
+
# `value`.
|
9
|
+
#
|
10
|
+
# @param [String, Class, Symbol] value `guise` to check
|
11
|
+
# @return [true, false]
|
7
12
|
def has_guise?(value)
|
8
13
|
value = value.to_s.classify
|
9
14
|
|
10
|
-
unless guise_options
|
15
|
+
unless guise_options.values.include?(value)
|
11
16
|
raise ArgumentError, "no such guise #{value}"
|
12
17
|
end
|
13
18
|
|
14
|
-
|
19
|
+
association(guise_options.association_name).reader.any? do |record|
|
20
|
+
!record.marked_for_destruction? &&
|
21
|
+
record[guise_options.attribute] == value
|
22
|
+
end
|
15
23
|
end
|
16
24
|
|
25
|
+
# Checks if the record has any `guise` records with identified by any of
|
26
|
+
# the specified `values`.
|
27
|
+
#
|
28
|
+
# @param [Array<String, Class, Symbol>] value `guise` to check
|
29
|
+
# @return [true, false]
|
17
30
|
def has_any_guises?(*values)
|
18
31
|
values.any? { |value| has_guise?(value) }
|
19
32
|
end
|
20
33
|
|
34
|
+
# Checks if the record has `guise` records for all of the specified
|
35
|
+
# `values`.
|
36
|
+
#
|
37
|
+
# @param [Array<String, Class, Symbol>] value `guise` to check
|
38
|
+
# @return [true, false]
|
21
39
|
def has_guises?(*values)
|
22
40
|
values.all? { |value| has_guise?(value) }
|
23
41
|
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def guise_options
|
28
|
-
Guise.registry[self.class.table_name.classify]
|
29
|
-
end
|
30
42
|
end
|
31
43
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Guise
|
2
|
+
# @api private
|
3
|
+
class Options
|
4
|
+
attr_reader(
|
5
|
+
:source_class,
|
6
|
+
:values,
|
7
|
+
:association_name,
|
8
|
+
:association_name_singular,
|
9
|
+
:attribute,
|
10
|
+
:association_options,
|
11
|
+
:scopes
|
12
|
+
)
|
13
|
+
|
14
|
+
attr_writer :association_class
|
15
|
+
|
16
|
+
def initialize(source_class, *values)
|
17
|
+
options = values.extract_options!
|
18
|
+
|
19
|
+
if values.empty?
|
20
|
+
raise ArgumentError, "must specify values in `has_guises`"
|
21
|
+
end
|
22
|
+
|
23
|
+
@source_class = source_class
|
24
|
+
@values = values.map(&:to_s).to_set
|
25
|
+
@association_name =
|
26
|
+
options.delete(:association) || DEFAULT_ASSOCIATION_NAME
|
27
|
+
@association_name_singular = @association_name.to_s.singularize
|
28
|
+
@attribute = options.delete(:attribute) || DEFAULT_ATTRIBUTE_NAME
|
29
|
+
@association_options = options.reverse_merge!(default_association_options)
|
30
|
+
|
31
|
+
@scopes = values.inject(HashWithIndifferentAccess.new) do |all, value|
|
32
|
+
all.merge!(value => {})
|
33
|
+
end
|
34
|
+
@scopes.freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
def scope(guise_value, scope_type)
|
38
|
+
value_scopes = @scopes.fetch(guise_value) do
|
39
|
+
raise InvalidGuiseValue.new(guise_value, source_class)
|
40
|
+
end
|
41
|
+
|
42
|
+
value_scopes.fetch(scope_type) do
|
43
|
+
raise ArgumentError, "`#{scope_type}' is not a valid type of scope"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def register_scope(guise_value, scope)
|
48
|
+
value_scopes = @scopes.fetch(guise_value) do
|
49
|
+
raise InvalidGuiseValue.new(guise_value, source_class)
|
50
|
+
end
|
51
|
+
|
52
|
+
if value_scopes.key?(scope.type)
|
53
|
+
raise "`#{scope.type}' scope already defined for #{source_class}"
|
54
|
+
end
|
55
|
+
|
56
|
+
value_scopes[scope.type] = scope
|
57
|
+
end
|
58
|
+
|
59
|
+
def association_class
|
60
|
+
if defined?(@association_class)
|
61
|
+
@association_class
|
62
|
+
else
|
63
|
+
raise "`guise_for` was not called on the association class"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def source_association_name
|
68
|
+
source_class.model_name.singular.to_sym
|
69
|
+
end
|
70
|
+
|
71
|
+
def default_association_options
|
72
|
+
{ foreign_key: "#{source_association_name}_id".to_sym }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/guise/registry.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "active_support/hash_with_indifferent_access"
|
2
|
+
|
3
|
+
module Guise
|
4
|
+
class Registry
|
5
|
+
def initialize
|
6
|
+
@registry = HashWithIndifferentAccess.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](name)
|
10
|
+
@registry.fetch(name) do
|
11
|
+
raise DefinitionNotFound.new(name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def []=(name, definition)
|
16
|
+
@registry[name] = definition
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/guise/scopes.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
module Guise
|
2
|
+
# @api private
|
3
|
+
class Scope
|
4
|
+
def initialize(value, options)
|
5
|
+
@value = value
|
6
|
+
@options = options
|
7
|
+
|
8
|
+
@options.register_scope(@value, self)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# @api private
|
13
|
+
class HasGuisesScope < Scope
|
14
|
+
def call
|
15
|
+
relation.create_with(association_name => [association_relation.new])
|
16
|
+
end
|
17
|
+
|
18
|
+
def type
|
19
|
+
:has_guises
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def relation
|
25
|
+
source_class.
|
26
|
+
select(source_class.arel_table[Arel.star]).
|
27
|
+
joins(association_name).
|
28
|
+
merge(association_relation)
|
29
|
+
end
|
30
|
+
|
31
|
+
def source_class
|
32
|
+
@options.source_class
|
33
|
+
end
|
34
|
+
|
35
|
+
def association_name
|
36
|
+
@options.association_name
|
37
|
+
end
|
38
|
+
|
39
|
+
def association_relation
|
40
|
+
@options.scope(@value, :guise_for).call
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @api private
|
45
|
+
class GuiseOfScope < HasGuisesScope
|
46
|
+
def call
|
47
|
+
relation
|
48
|
+
end
|
49
|
+
|
50
|
+
def type
|
51
|
+
:guise_of
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @api private
|
56
|
+
class GuiseForScope < Scope
|
57
|
+
def call
|
58
|
+
@options.association_class.where(@options.attribute => @value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def type
|
62
|
+
:guise_for
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|