guise 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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 = '>= 1.9.3'
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 'rake', '~> 10.1'
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 "factory_girl", "~> 4.4"
29
- gem.add_development_dependency "shoulda-matchers", "~> 2.5"
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 'guise/syntax'
6
- require 'guise/introspection'
13
+ require "guise/introspection"
14
+ require "guise/syntax"
7
15
 
8
16
  module Guise
9
17
  mattr_reader :registry
10
- @@registry = HashWithIndifferentAccess.new
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
- if defined?(ActiveRecord)
14
- ActiveRecord::Base.extend(Guise::Syntax)
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
@@ -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)
@@ -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
@@ -1,31 +1,43 @@
1
- require 'active_support/concern'
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
- extend ActiveSupport::Concern
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[:names].any? { |name| name == value }
15
+ unless guise_options.values.include?(value)
11
16
  raise ArgumentError, "no such guise #{value}"
12
17
  end
13
18
 
14
- guises.any? { |guise| !guise.marked_for_destruction? && guise[guise_options[:attribute]] == value }
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
@@ -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
@@ -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