poly_belongs_to 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56d5f029c2d24b2474440dc487ca11d9da0592bd
4
- data.tar.gz: 626db50369225abcd48137ece11615554ea8ad77
3
+ metadata.gz: ab96e43c85c346057343a22a9e06b8cf532eef4c
4
+ data.tar.gz: af27f3c4ec9facc1577fdace270ebd3bbbb9f9ff
5
5
  SHA512:
6
- metadata.gz: 5344d97c85fb343ef385e647f1c2c043ea881e9a727d63774ca46de9e90ebccf1fcff5c24084ff8c9d058938f0587b57da8e76c4f47da854908fa4abd5709e03
7
- data.tar.gz: 7d0c57c277948b7d336ac2e882d2f1fee2e9330f653a9370265f2b1d7ca3158b62c850a2371c21ce6c95dd6aa94086339e8a579204c89b5ca1b4bd2c374c4182
6
+ metadata.gz: 4b3fb493356381a13b006f927f2c13c28bde9ae004c0bdff342f7ad5cd224a080e76776d21cb28317e707ca419afba8cf41e3aee66c2357cda753eb4b58746fc
7
+ data.tar.gz: 778008ce2888fc121386f93bb71242d33f92048164451dda3ee5db436894235334bfab5c15dcd09c3ea2de15bf2e133199ac8387e272bb133fa318b3e3418bb2
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![Code Climate](https://codeclimate.com/github/danielpclark/PolyBelongsTo/badges/gpa.svg)](https://codeclimate.com/github/danielpclark/PolyBelongsTo)
4
4
  [![Build Status](https://travis-ci.org/danielpclark/PolyBelongsTo.svg)](https://travis-ci.org/danielpclark/PolyBelongsTo)
5
5
  [![Test Coverage](https://codeclimate.com/github/danielpclark/PolyBelongsTo/badges/coverage.svg)](https://codeclimate.com/github/danielpclark/PolyBelongsTo)
6
+ [![Inline docs](http://inch-ci.org/github/danielpclark/PolyBelongsTo.svg?branch=master)](http://inch-ci.org/github/danielpclark/PolyBelongsTo)
6
7
 
7
8
  **Original Purpose:** A standard way to check belongs_to relations on any belongs_to Object and let you check your DB Objects polymorphism in a more across-the-board meta-programatically friendly way.
8
9
 
@@ -1,6 +1,3 @@
1
- # Poly Belongs To
2
- # The MIT License (MIT)
3
- # Copyright (C) 2015 by Daniel P. Clark
4
1
  $: << File.join(File.dirname(__FILE__), "/poly_belongs_to")
5
2
  require 'active_support/concern'
6
3
  require 'poly_belongs_to/version'
@@ -12,3 +9,19 @@ require 'poly_belongs_to/faked_collection'
12
9
  ActiveRecord::Base.send(:include, PolyBelongsTo::Core )
13
10
  ActiveRecord::Base.send(:include, PolyBelongsTo::Dup )
14
11
 
12
+
13
+
14
+ ##
15
+ #
16
+ # PolyBelongsTo
17
+ # @author Daniel P. Clark
18
+ #
19
+ # Creates uniform helper methods on all ActiveModel & ActiveRecord instances
20
+ # to allow universal access and control over (most) any kind of database
21
+ # relationship.
22
+ #
23
+ # PolyBelongsTo::Core and PolyBelongsTo::Dup methods are included on each instance.
24
+ # @see PolyBelongsTo::Core
25
+ # @see PolyBelongsTo::Dup
26
+ #
27
+ module PolyBelongsTo end
@@ -1,20 +1,29 @@
1
1
  module PolyBelongsTo
2
+ ##
3
+ # PolyBelongsTo::Core are the core set of methods included on all ActiveModel & ActiveRecord instances.
2
4
  module Core
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  included do
8
+ # @return [Symbol] first belongs_to relation
6
9
  def self.pbt
7
10
  reflect_on_all_associations(:belongs_to).first.try(:name)
8
11
  end
9
12
 
13
+ # @return [Array<Symbol>] belongs_to relations
10
14
  def self.pbts
11
15
  reflect_on_all_associations(:belongs_to).map(&:name)
12
16
  end
13
17
 
18
+ # Boolean reponse of current class being polymorphic
19
+ # @return [true, false]
14
20
  def self.poly?
15
21
  !!reflect_on_all_associations(:belongs_to).first.try {|i| i.options[:polymorphic] }
16
22
  end
17
23
 
24
+ # Symbol for html form params
25
+ # @param allow_as_nested [true, false] Allow parameter name to be nested attribute symbol
26
+ # @return [Symbol] The symbol for the form in the view
18
27
  def self.pbt_params_name(allow_as_nested = true)
19
28
  if poly? && allow_as_nested
20
29
  "#{table_name}_attributes".to_sym
@@ -23,37 +32,51 @@ module PolyBelongsTo
23
32
  end
24
33
  end
25
34
 
35
+ # The symbol as an id field for the first belongs_to relation
36
+ # @return [Symbol, nil] :`belongs_to_object`_id or nil
26
37
  def self.pbt_id_sym
27
38
  val = pbt
28
39
  val ? "#{val}_id".to_sym : nil
29
40
  end
30
41
 
42
+ # The symbol as an sym field for the polymorphic belongs_to relation, or nil
43
+ # @return [Symbol, nil] :`belongs_to_object`_sym or nil
31
44
  def self.pbt_type_sym
32
45
  poly? ? "#{pbt}_type".to_sym : nil
33
46
  end
34
47
  end
35
48
 
49
+ # @return [Symbol] first belongs_to relation
36
50
  def pbt
37
51
  self.class.pbt
38
52
  end
39
53
 
54
+ # @return [Array<Symbol>] belongs_to relations
40
55
  def pbts
41
56
  self.class.pbts
42
57
  end
43
58
 
59
+ # Boolean reponse of current class being polymorphic
60
+ # @return [true, false]
44
61
  def poly?
45
62
  self.class.poly?
46
63
  end
47
64
 
65
+ # Value of parent id. nil if no parent
66
+ # @return [Integer, nil]
48
67
  def pbt_id
49
68
  val = pbt
50
69
  val ? self.send("#{val}_id") : nil
51
70
  end
52
71
 
72
+ # Value of polymorphic relation type. nil if not polymorphic.
73
+ # @return [String, nil]
53
74
  def pbt_type
54
75
  poly? ? self.send("#{pbt}_type") : nil
55
76
  end
56
77
 
78
+ # Get the parent relation. Polymorphic relations are prioritized first.
79
+ # @return [Object] ActiveRecord object instasnce
57
80
  def pbt_parent
58
81
  val = pbt
59
82
  if val && !pbt_id.nil?
@@ -67,6 +90,9 @@ module PolyBelongsTo
67
90
  end
68
91
  end
69
92
 
93
+ # Climb up each parent object in the hierarchy until the top is reached.
94
+ # This has a no-repeat safety built in. Polymorphic parents have priority.
95
+ # @return [Object] top parent ActiveRecord object instace
70
96
  def pbt_top_parent
71
97
  record = self
72
98
  return nil unless record.pbt_parent
@@ -78,6 +104,8 @@ module PolyBelongsTo
78
104
  record
79
105
  end
80
106
 
107
+ # All belongs_to parents as class objects. One if polymorphic.
108
+ # @return [Array<Object>] ActiveRecord classes of parent objects.
81
109
  def pbt_parents
82
110
  if poly?
83
111
  Array[pbt_parent].compact
@@ -88,14 +116,21 @@ module PolyBelongsTo
88
116
  end
89
117
  end
90
118
 
119
+ # The symbol as an id field for the first belongs_to relation
120
+ # @return [Symbol, nil] :`belongs_to_object`_id or nil
91
121
  def pbt_id_sym
92
122
  self.class.pbt_id_sym
93
123
  end
94
124
 
125
+ # The symbol as an sym field for the polymorphic belongs_to relation, or nil
126
+ # @return [Symbol, nil] :`belongs_to_object`_sym or nil
95
127
  def pbt_type_sym
96
128
  self.class.pbt_type_sym
97
129
  end
98
130
 
131
+ # Symbol for html form params
132
+ # @param allow_as_nested [true, false] Allow parameter name to be nested attribute symbol
133
+ # @return [Symbol] The symbol for the form in the view
99
134
  def pbt_params_name(allow_as_nested = true)
100
135
  self.class.pbt_params_name(allow_as_nested)
101
136
  end
@@ -1,8 +1,13 @@
1
1
  module PolyBelongsTo
2
+ ##
3
+ # PolyBelongsTo::Dup contains duplication methods which is included on all ActiveModel & ActiveRecord instances
2
4
  module Dup
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  included do
8
+ # Build child object by dup'ing its attributes
9
+ # @param item_to_build_on [Object] Object on which to build on
10
+ # @param item_to_duplicate [Object] Object to duplicate as new child
6
11
  def self.pbt_dup_build(item_to_build_on, item_to_duplicate)
7
12
  singleton_record = yield if block_given?
8
13
  if PolyBelongsTo::Pbt::IsReflected[item_to_build_on, item_to_duplicate]
@@ -12,6 +17,9 @@ module PolyBelongsTo
12
17
  end
13
18
  end
14
19
 
20
+ # Build child object by dup'ing its attributes. Recursive for ancestry tree.
21
+ # @param item_to_build_on [Object] Object on which to build on
22
+ # @param item_to_duplicate [Object] Object to duplicate as new child along with child ancestors
15
23
  def self.pbt_deep_dup_build(item_to_build_on, item_to_duplicate)
16
24
  singleton_record = (block_given? ? yield : PolyBelongsTo::SingletonSet.new)
17
25
  pbt_dup_build(item_to_build_on, item_to_duplicate) {singleton_record}
@@ -29,10 +37,14 @@ module PolyBelongsTo
29
37
 
30
38
  end
31
39
 
40
+ # Build child object by dup'ing its attributes
41
+ # @param item_to_duplicate [Object] Object to duplicate as new child
32
42
  def pbt_dup_build(item_to_duplicate, &block)
33
43
  self.class.pbt_dup_build(self, item_to_duplicate, &block)
34
44
  end
35
45
 
46
+ # Build child object by dup'ing its attributes. Recursive for ancestry tree.
47
+ # @param item_to_duplicate [Object] Object to duplicate as new child along with child ancestors
36
48
  def pbt_deep_dup_build(item_to_duplicate, &block)
37
49
  self.class.pbt_deep_dup_build(self, item_to_duplicate, &block)
38
50
  end
@@ -1,5 +1,8 @@
1
1
  module PolyBelongsTo
2
+ ##
3
+ # PolyBelongsTo::FakedCollection emulates an ActiveRecord::CollectionProxy of exactly one or zero items
2
4
  class FakedCollection
5
+ # Create the Faked Colllection Proxy
3
6
  def initialize(obj = nil, child = nil)
4
7
  @obj = obj
5
8
  @child = child
@@ -12,75 +15,109 @@ module PolyBelongsTo
12
15
  self
13
16
  end
14
17
 
18
+ # @return [Integer, nil]
15
19
  def id
16
20
  @instance.try(:id)
17
21
  end
18
22
 
23
+ # @return [Array]
19
24
  def all
20
25
  Array[@instance].compact
21
26
  end
22
27
 
28
+ # Boolean of empty?
29
+ # @return [true, false]
23
30
  def empty?
24
31
  all.empty?
25
32
  end
26
33
 
34
+ # Boolean of not empty?
35
+ # @return [true, false]
27
36
  def present?
28
37
  !empty?
29
38
  end
30
39
 
40
+ # @return [self, nil]
31
41
  def presence
32
42
  all.first ? self : nil
33
43
  end
34
44
 
45
+ # Boolean of validity
46
+ # @return [true, false]
35
47
  def valid?
36
48
  !!@instance.try(&:valid?)
37
49
  end
38
50
 
51
+ # @return [Object, nil]
39
52
  def first
40
53
  @instance
41
54
  end
42
55
 
56
+ # @return [Object, nil]
43
57
  def last
44
58
  @instance
45
59
  end
46
60
 
61
+ # @return [Integer] 1 or 0
47
62
  def size
48
63
  all.size
49
64
  end
50
65
 
66
+ # @return [Array<Object>]
51
67
  def ancestors
52
68
  klass.ancestors.unshift(PolyBelongsTo::FakedCollection)
53
69
  end
54
-
70
+
71
+ # Boolean of kind match
72
+ # @param thing [Object] object class
73
+ # @return [true, false]
55
74
  def kind_of?(thing)
56
75
  ancestors.include? thing
57
76
  end
58
77
 
78
+ # Boolean of kind match
79
+ # @param thing [Object] object class
80
+ # @return [true, false]
59
81
  def is_a?(thing)
60
82
  kind_of?(thing)
61
83
  end
62
84
 
85
+ # alias for size
86
+ # @return [Integer] 1 or 0
63
87
  def count
64
88
  size
65
89
  end
66
90
 
91
+ # @return [Object] object class
67
92
  def klass
68
93
  @instance.class
69
94
  end
70
95
 
96
+ # build preforms calculated build command and returns self
97
+ # @param args [Array<Symbol>] arguments
98
+ # @return [self]
71
99
  def build(*args)
72
100
  @instance = @obj.send(PolyBelongsTo::Pbt::BuildCmd[@obj, @child], *args)
73
101
  self
74
102
  end
75
103
 
104
+ # @param args [Array<Symbol>] arguments
105
+ # @return [Enumerator]
76
106
  def each(*args, &block)
77
107
  all.each(*args, &block)
78
108
  end
79
109
 
110
+ # Returns whether this or super responds to method
111
+ # @param method_name [Symbol]
112
+ # @return [true, false]
80
113
  def respond_to?(method_name)
81
114
  @instance.respond_to?(method_name) || super
82
115
  end
83
116
 
117
+ # Hands method and argument on to instance if it responds to method, otherwise calls super
118
+ # @param method_name [Symbol]
119
+ # @param args [Array<Symbol>] arguments
120
+ # @return [true, false]
84
121
  def method_missing(method_name, *args, &block)
85
122
  if @instance.respond_to?(method_name)
86
123
  @instance.send method_name, *args, &block
@@ -1,5 +1,11 @@
1
1
  module PolyBelongsTo
2
+ ##
3
+ # PolyBelongsTo::Pbt is where internal methods are for implementing core and duplication methods; available on all instances.
4
+ # They are available for other use if the need should arise.
2
5
  module Pbt
6
+ # Strips date and id fields dup'd attributes
7
+ # @param obj [Object] ActiveRecord instance that has attributes
8
+ # @return [Hash] attributes
3
9
  AttrSanitizer = lambda {|obj|
4
10
  return {} unless obj
5
11
  obj.dup.attributes.delete_if {|ky,vl|
@@ -7,12 +13,19 @@ module PolyBelongsTo
7
13
  }
8
14
  }
9
15
 
16
+ # Discerns which type of build command is used between obj and child
17
+ # @param obj [Object] ActiveRecord instance to build on
18
+ # @param child [Object] ActiveRecord child relation as class or instance
19
+ # @return [String, nil] build command for relation, or nil
10
20
  BuildCmd = lambda {|obj, child|
11
21
  return nil unless obj && child
12
22
  dup_name = CollectionProxy[obj, child]
13
23
  IsSingular[obj, child] ? "build_#{dup_name}" : IsPlural[obj, child] ? "#{dup_name}.build" : nil
14
24
  }
15
25
 
26
+ # Returns all has_one and has_many relationships as symbols (reflec_on_all_associations)
27
+ # @param obj [Object] ActiveRecord instance to reflect on
28
+ # @return [Array<Symbol>]
16
29
  Reflects = lambda {|obj|
17
30
  return [] unless obj
18
31
  [:has_one, :has_many].map { |has|
@@ -20,16 +33,27 @@ module PolyBelongsTo
20
33
  }.flatten
21
34
  }
22
35
 
36
+ # Returns all has_one and has_many relationships as class objects (reflec_on_all_associations)
37
+ # @param obj [Object] ActiveRecord instance to reflect on
38
+ # @return [Array<Object>]
23
39
  ReflectsAsClasses = lambda {|obj|
24
40
  Reflects[obj].map {|ref|
25
41
  (obj.send(ref).try(:klass) || obj.send(ref).class).name.constantize
26
42
  }
27
43
  }
28
44
 
45
+ # Check if the child object is a kind of child that belongs to obj
46
+ # @param obj [Object] ActiveRecord instance
47
+ # @param child [Object] ActiveRecord instance or model class
48
+ # @return [true, false]
29
49
  IsReflected = lambda {|obj, child|
30
50
  !!CollectionProxy[obj, child]
31
51
  }
32
52
 
53
+ # Plurality of object relationship (plural for has_many)
54
+ # @param obj [Object] ActiveRecord instance
55
+ # @param child [Object] ActiveRecord instance or model class
56
+ # @return [Symbol, nil] :plural, :singular, or nil
33
57
  SingularOrPlural = lambda {|obj, child|
34
58
  return nil unless obj && child
35
59
  reflects = Reflects[obj]
@@ -42,24 +66,44 @@ module PolyBelongsTo
42
66
  end
43
67
  }
44
68
 
69
+ # Singularity of object relationship (for has_one)
70
+ # @param obj [Object] ActiveRecord instance
71
+ # @param child [Object] ActiveRecord instance or model class
72
+ # @return [true, false]
45
73
  IsSingular = lambda {|obj, child|
46
74
  SingularOrPlural[obj, child] == :singular
47
75
  }
48
76
 
77
+ # Plurality of object relationship (for has_many)
78
+ # @param obj [Object] ActiveRecord instance
79
+ # @param child [Object] ActiveRecord instance or model class
80
+ # @return [true, false]
49
81
  IsPlural = lambda {|obj,child|
50
82
  SingularOrPlural[obj, child] == :plural
51
83
  }
52
84
 
85
+ # Returns the symbol of the child relation object
86
+ # @param obj [Object] ActiveRecord instance
87
+ # @param child [Object] ActiveRecord instance or model class
88
+ # @return [Symbol] working relation on obj
53
89
  CollectionProxy = lambda {|obj, child|
54
90
  return nil unless obj && child
55
91
  names = [ActiveModel::Naming.singular(child).to_s, ActiveModel::Naming.plural(child).to_s].uniq
56
92
  Reflects[obj].detect {|ref| "#{ref}"[/(?:#{ names.join('|') }).{,3}/]}
57
93
  }
58
94
 
95
+ # Returns either a ActiveRecord::CollectionProxy or a PolyBelongsTo::FakedCollection as a proxy
96
+ # @param obj [Object] ActiveRecord instance
97
+ # @param child [Object] ActiveRecord instance or model class
98
+ # @return [Object]
59
99
  AsCollectionProxy = lambda {|obj, child|
60
100
  return PolyBelongsTo::FakedCollection.new() unless obj && child
61
101
  return PolyBelongsTo::FakedCollection.new(obj, child) if IsSingular[obj, child]
62
- !!CollectionProxy[obj, child] ? obj.send(PolyBelongsTo::Pbt::CollectionProxy[obj, child]) : []
102
+ if CollectionProxy[obj, child]
103
+ obj.send(PolyBelongsTo::Pbt::CollectionProxy[obj, child])
104
+ else
105
+ PolyBelongsTo::FakedCollection.new()
106
+ end
63
107
  }
64
108
  end
65
109
 
@@ -1,16 +1,26 @@
1
1
  require 'set'
2
2
  module PolyBelongsTo
3
+ ##
4
+ # PolyBelongsTo::SingletonSet is a modified instance of Set for maintaining singletons of ActiveRecord objects during any recursive flow.
3
5
  class SingletonSet
6
+ # Creates two internal Sets
7
+ # @return [self]
4
8
  def initialize
5
9
  @set = Set.new
6
10
  @flagged = Set.new
7
11
  self
8
12
  end
9
13
 
14
+ # Takes ActiveRecord object and returns string of class name and object id
15
+ # @param record [Object] ActiveRecord object instance
16
+ # @return [String]
10
17
  def formatted_name(record)
11
18
  "#{record.class.name}-#{record.id}"
12
19
  end
13
20
 
21
+ # Add record to set. Flag if covered already.
22
+ # @param record [Object] ActiveRecord object instance
23
+ # @return [Object, nil] Object if added safely, nil otherwise
14
24
  def add?(record)
15
25
  result = @set.add?( formatted_name( record ) )
16
26
  return result if result
@@ -18,20 +28,31 @@ module PolyBelongsTo
18
28
  result
19
29
  end
20
30
 
31
+ # Boolean of record inclusion
32
+ # @param record [Object] ActiveRecord object instance
33
+ # @return [true, false]
21
34
  def include?(record)
22
35
  return nil unless record
23
36
  @set.include?(formatted_name(record))
24
37
  end
25
38
 
39
+ # Add record to flagged Set list
40
+ # @param record [Object] ActiveRecord object instance
41
+ # @return [Set]
26
42
  def flag(record)
27
43
  @flagged << formatted_name(record)
28
44
  end
29
45
 
46
+ # Boolean of record inclusion in flagged Set
47
+ # @param record [Object] ActiveRecord object instance
48
+ # @return [true, false]
30
49
  def flagged?(record)
31
50
  return nil unless record
32
51
  @flagged.include?(formatted_name(record))
33
52
  end
34
53
 
54
+ # method_missing will transform any record argument into a formatted string and pass the
55
+ # method and arguments on to the internal Set. Also will flag any existing records covered.
35
56
  def method_missing(mthd, *args, &block)
36
57
  new_recs = args.reduce([]) {|a, i| a.push(formatted_name(i)) if i.class.ancestors.include?(ActiveRecord::Base); a}
37
58
  result = @set.send(mthd,