appear 1.1.1 → 1.2.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.
data/lib/appear/join.rb DELETED
@@ -1,134 +0,0 @@
1
- module Appear
2
- # join objects based on hash value or method value.
3
- #
4
- # example:
5
- #
6
- # ```
7
- # foos = many_foos
8
- # bars = many_bars
9
- # foo_bars = Join.join(:common_attribute, foos, bars)
10
- #
11
- # # can still access all the properties on either a foo or a bar
12
- # foo_bars.first.common_attribute
13
- #
14
- # # can access attributes by symbol, too
15
- # foo_bars.first[:something_else]
16
- # ```
17
- #
18
- # foo_bars is an array of Join instances. Reads from a foo_bar will read
19
- # first from the foo, and then from the bar - this is based on the order of
20
- # "tables" passed to Join.join().
21
- class Join
22
- # @param field [Symbol] the method or hash field name to join on.
23
- # @param tables [Array<Any>] arrays of any sort of object, so long as it is
24
- # either a hash, or has a method named `field`.
25
- # @return [Array<Join>]
26
- def self.join(field, *tables)
27
- by_field = Hash.new { |h, k| h[k] = self.new }
28
-
29
- tables.each do |table|
30
- table.each do |row|
31
- field_value = access(row, field)
32
- joined = by_field[field_value]
33
- joined.push!(row)
34
- end
35
- end
36
-
37
- by_field.values.select do |joined|
38
- joined.joined_count >= tables.length
39
- end
40
- end
41
-
42
- # True if we can access the given field on an object, either by calling
43
- # that method on the object, or by accessing using []
44
- #
45
- # @param obj [Any]
46
- # @param field [Symbol, String]
47
- # @return [Boolean]
48
- def self.can_access?(obj, field)
49
- if obj.respond_to?(field)
50
- return true
51
- elsif obj.respond_to?(:[])
52
- return true
53
- end
54
- return false
55
- end
56
-
57
- # Access the given field on an object.
58
- # Raises an error if the field cannot be accessed.
59
- #
60
- # @param object [Any]
61
- # @param field [Symbol, String]
62
- # @return [Any] the value at that field
63
- def self.access(obj, field)
64
- if obj.respond_to?(field)
65
- obj.send(field)
66
- elsif obj.respond_to?(:[])
67
- obj[field]
68
- else
69
- raise "cannot access #{field.inspect} on #{object.inspect}"
70
- end
71
- end
72
-
73
- # A Join is a union of data objects. You can use a Join to group objects of
74
- # different types, so that you may read from whichever has a given field.
75
- #
76
- # It is more useful to use self.join to perform a join operation on
77
- # collections than to create Join objects directly.
78
- def initialize(*objs)
79
- @objs = objs
80
- end
81
-
82
- # add another data object to this join.
83
- #
84
- # @param obj [Any]
85
- def push!(obj)
86
- @objs << obj
87
- end
88
-
89
- # get the number of objects in this join
90
- #
91
- # @return [Fixnum]
92
- def joined_count
93
- @objs.length
94
- end
95
-
96
- # read a field from the join. Returns the first non-nil value we can read.
97
- # @see self.access for information about how fields are accessed.
98
- #
99
- # @param sym [String, Symbol] the field name
100
- # @return [Any, nil]
101
- def [](sym)
102
- result = nil
103
-
104
- @objs.each do |obj|
105
- if self.class.can_access?(obj, sym)
106
- result = self.class.access(obj, sym)
107
- end
108
- break unless result.nil?
109
- end
110
-
111
- result
112
- end
113
-
114
- # the {#method_missing} implementation on a Join allows you to access valid
115
- # fields with regular accessors.
116
- #
117
- # @param method [String, Symbol]
118
- # @param args [Array<Any>] should have none
119
- # @param block [Proc] should have none
120
- def method_missing(method, *args, &block)
121
- raise NoMethodError.new("Cannot access #{method.inspect}") unless respond_to?(method)
122
- raise ArgumentError.new("Passed args to accessor") if args.length > 0
123
- raise ArgumentError.new("Passed block to accessor") if block
124
- self[method]
125
- end
126
-
127
- # @param sym [String, Symbol] name of the method
128
- # @param priv [Boolean] default false
129
- # @return [Boolean] true if we can respond to the given method name
130
- def respond_to?(sym, priv = false)
131
- super(sym, priv) || (@objs.any? { |o| self.class.can_access?(o, sym) })
132
- end
133
- end
134
- end