linkage 0.0.1 → 0.0.2

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/Gemfile CHANGED
@@ -6,12 +6,13 @@ group :development do
6
6
  gem "bundler", "~> 1.0.0"
7
7
  gem "jeweler", "~> 1.6.4"
8
8
  gem "rcov", ">= 0"
9
- gem "guard-test"
10
9
  gem "test-unit", "2.3.2"
11
10
  gem "mocha"
12
11
  gem "sqlite3"
13
12
  gem "yard"
14
13
  gem "rake"
15
14
  gem "versionomy"
16
- gem "guard-yard", :platforms => :ruby_19
15
+ gem "mysql2"
16
+ gem 'pry'
17
+ gem 'rdiscount'
17
18
  end
data/Gemfile.lock CHANGED
@@ -2,41 +2,49 @@ GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
4
  blockenspiel (0.4.3)
5
+ coderay (0.9.8)
5
6
  git (1.2.5)
6
- guard (0.6.2)
7
- thor (~> 0.14.6)
8
- guard-test (0.3.0)
9
- guard (>= 0.2.2)
10
- test-unit (~> 2.2)
11
- guard-yard (1.0.1)
12
- guard (>= 0.2.2)
13
- yard (>= 0.7.0)
14
7
  jeweler (1.6.4)
15
8
  bundler (~> 1.0)
16
9
  git (>= 1.2.5)
17
10
  rake
18
- mocha (0.9.12)
19
- rake (0.9.2)
20
- rcov (0.9.10)
21
- sequel (3.26.0)
22
- sqlite3 (1.3.3)
11
+ metaclass (0.0.1)
12
+ method_source (0.6.7)
13
+ ruby_parser (>= 2.3.1)
14
+ mocha (0.10.0)
15
+ metaclass (~> 0.0.1)
16
+ mysql2 (0.3.10)
17
+ pry (0.9.7.4)
18
+ coderay (~> 0.9.8)
19
+ method_source (~> 0.6.7)
20
+ ruby_parser (>= 2.3.1)
21
+ slop (~> 2.1.0)
22
+ rake (0.9.2.2)
23
+ rcov (0.9.11)
24
+ rdiscount (1.6.8)
25
+ ruby_parser (2.3.1)
26
+ sexp_processor (~> 3.0)
27
+ sequel (3.29.0)
28
+ sexp_processor (3.0.8)
29
+ slop (2.1.0)
30
+ sqlite3 (1.3.4)
23
31
  test-unit (2.3.2)
24
- thor (0.14.6)
25
32
  versionomy (0.4.1)
26
33
  blockenspiel (>= 0.4.1)
27
- yard (0.7.2)
34
+ yard (0.7.3)
28
35
 
29
36
  PLATFORMS
30
37
  ruby
31
38
 
32
39
  DEPENDENCIES
33
40
  bundler (~> 1.0.0)
34
- guard-test
35
- guard-yard
36
41
  jeweler (~> 1.6.4)
37
42
  mocha
43
+ mysql2
44
+ pry
38
45
  rake
39
46
  rcov
47
+ rdiscount
40
48
  sequel
41
49
  sqlite3
42
50
  test-unit (= 2.3.2)
data/Guardfile CHANGED
@@ -1,7 +1,6 @@
1
1
  guard 'test' do
2
- watch(%r{^lib/linkage/runner/([^/]+)\.rb$}) { |m| "test/unit/test_#{m[1]}_runner.rb" }
3
- watch(%r{^lib/linkage/([^/]+)\.rb$}) { |m| "test/unit/test_#{m[1]}.rb" }
4
- watch(%r{^test/unit/test_.+\.rb$})
2
+ watch(%r{^lib/linkage/([^/]+/)*([^/]+)\.rb$}) { |m| "test/unit/#{m[1]}test_#{m[2]}.rb" }
3
+ watch(%r{^test/unit/([^/]+/)*test_.+\.rb$})
5
4
  watch(%r{^test/integration/test_.+\.rb$})
6
5
  watch('lib/linkage/configuration.rb') { "test/unit/test_dataset.rb" }
7
6
  watch('test/helper.rb') { "test" }
data/Rakefile CHANGED
@@ -42,7 +42,7 @@ end
42
42
 
43
43
  task :default => :test
44
44
 
45
- require 'rake/rdoctask'
45
+ require 'rdoc/task'
46
46
  Rake::RDocTask.new do |rdoc|
47
47
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
48
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
@@ -14,9 +14,9 @@ module Linkage
14
14
  class Configuration
15
15
  # @private
16
16
  class ExpectationWrapper
17
- def initialize(type, field, config)
17
+ def initialize(type, data, config)
18
18
  @type = type
19
- @field = field
19
+ @data = data
20
20
  @config = config
21
21
  @side = nil
22
22
  @forced_kind = nil
@@ -25,15 +25,15 @@ module Linkage
25
25
  Linkage::Expectation::VALID_OPERATORS.each do |op|
26
26
  define_method(op) do |other|
27
27
  case other
28
- when FieldWrapper
29
- @other = other.field
30
- if other.side == @field.side
28
+ when DataWrapper
29
+ @other = other.data
30
+ if @other.static? || other.side == @data.side
31
31
  @forced_kind = :filter
32
- @side = @field.side
32
+ @side = @data.side
33
33
  end
34
34
  else
35
35
  @other = other
36
- @side = @field.side
36
+ @side = @data.side
37
37
  end
38
38
  add_expectation(op)
39
39
  end
@@ -43,19 +43,14 @@ module Linkage
43
43
 
44
44
  def add_expectation(operator)
45
45
  klass = Expectation.get(@type)
46
- exp = klass.new(operator, @field.field, @other, @forced_kind)
46
+ exp = klass.new(operator, @data.data, @other, @forced_kind)
47
47
  @config.add_expectation(exp, @side)
48
48
  end
49
49
  end
50
50
 
51
51
  # @private
52
- class FieldWrapper
53
- attr_reader :field, :side
54
- def initialize(field, side, config)
55
- @field = field
56
- @side = side
57
- @config = config
58
- end
52
+ class DataWrapper
53
+ attr_reader :data, :side
59
54
 
60
55
  def must
61
56
  ExpectationWrapper.new(:must, self, @config)
@@ -64,6 +59,41 @@ module Linkage
64
59
  def must_not
65
60
  ExpectationWrapper.new(:must_not, self, @config)
66
61
  end
62
+
63
+ def static?
64
+ false
65
+ end
66
+ end
67
+
68
+ # @private
69
+ class FunctionWrapper < DataWrapper
70
+ def initialize(klass, args, config)
71
+ @klass = klass
72
+ @args = args
73
+ @config = config
74
+
75
+ @side = args.inject(nil) do |side, arg|
76
+ if arg.kind_of?(DataWrapper)
77
+ raise "conflicting sides" if side && side != arg.side
78
+ arg.side
79
+ else
80
+ side
81
+ end
82
+ end
83
+ end
84
+
85
+ def data
86
+ @klass.new(*@args.collect { |arg| arg.kind_of?(DataWrapper) ? arg.data : arg })
87
+ end
88
+ end
89
+
90
+ # @private
91
+ class FieldWrapper < DataWrapper
92
+ def initialize(field, side, config)
93
+ @data = field
94
+ @side = side
95
+ @config = config
96
+ end
67
97
  end
68
98
 
69
99
  # @private
@@ -174,5 +204,15 @@ module Linkage
174
204
  def inspect
175
205
  to_s
176
206
  end
207
+
208
+ # For handling functions
209
+ def method_missing(name, *args, &block)
210
+ klass = Function[name.to_s]
211
+ if klass
212
+ FunctionWrapper.new(klass, args, self)
213
+ else
214
+ super
215
+ end
216
+ end
177
217
  end
178
218
  end
@@ -0,0 +1,171 @@
1
+ module Linkage
2
+ # Superclass to {Field} and {Function}.
3
+ #
4
+ # @abstract
5
+ class Data
6
+ # A "tree" used to find compatible types.
7
+ TYPE_CONVERSION_TREE = {
8
+ TrueClass => [Integer],
9
+ Integer => [Bignum, Float],
10
+ Bignum => [BigDecimal],
11
+ Float => [BigDecimal],
12
+ BigDecimal => [String],
13
+ String => nil,
14
+ DateTime => nil,
15
+ Date => nil,
16
+ Time => nil,
17
+ File => nil
18
+ }
19
+
20
+ # @return [Symbol] This object's name
21
+ attr_reader :name
22
+
23
+ def initialize(name)
24
+ @name = name
25
+ end
26
+
27
+ def ruby_type
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def dataset
32
+ raise NotImplementedError
33
+ end
34
+
35
+ def to_expr
36
+ raise NotImplementedError
37
+ end
38
+
39
+ # Create a data object that can hold data from two other fields. If the fields
40
+ # have different types, the resulting type is determined via a
41
+ # type-conversion tree.
42
+ #
43
+ # @param [Linkage::Data] other
44
+ # @return [Linkage::Field]
45
+ def merge(other, new_name = nil)
46
+ schema_1 = self.ruby_type
47
+ schema_2 = other.ruby_type
48
+ if schema_1 == schema_2
49
+ result = schema_1
50
+ else
51
+ type_1 = schema_1[:type]
52
+ opts_1 = schema_1[:opts] || {}
53
+ type_2 = schema_2[:type]
54
+ opts_2 = schema_2[:opts] || {}
55
+ result_type = type_1
56
+ result_opts = schema_1[:opts] ? schema_1[:opts].dup : {}
57
+
58
+ # type
59
+ if type_1 != type_2
60
+ result_type = first_common_type(type_1, type_2)
61
+ end
62
+
63
+ # text
64
+ if opts_1[:text] != opts_2[:text]
65
+ # This can only be of type String.
66
+ result_opts[:text] = true
67
+ result_opts.delete(:size)
68
+ end
69
+
70
+ # size
71
+ if !result_opts[:text] && opts_1[:size] != opts_2[:size]
72
+ types = [type_1, type_2].uniq
73
+ if types.length == 1 && types[0] == BigDecimal
74
+ # Two decimals
75
+ if opts_1.has_key?(:size) && opts_2.has_key?(:size)
76
+ s_1 = opts_1[:size]
77
+ s_2 = opts_2[:size]
78
+ result_opts[:size] = [ s_1[0] > s_2[0] ? s_1[0] : s_2[0] ]
79
+
80
+ if s_1[1] && s_2[1]
81
+ result_opts[:size][1] = s_1[1] > s_2[1] ? s_1[1] : s_2[1]
82
+ else
83
+ result_opts[:size][1] = s_1[1] ? s_1[1] : s_2[1]
84
+ end
85
+ else
86
+ result_opts[:size] = opts_1.has_key?(:size) ? opts_1[:size] : opts_2[:size]
87
+ end
88
+ elsif types.include?(String) && types.include?(BigDecimal)
89
+ # Add one to the precision of the BigDecimal (for the dot)
90
+ if opts_1.has_key?(:size) && opts_2.has_key?(:size)
91
+ s_1 = opts_1[:size].is_a?(Array) ? opts_1[:size][0] + 1 : opts_1[:size]
92
+ s_2 = opts_2[:size].is_a?(Array) ? opts_2[:size][0] + 1 : opts_2[:size]
93
+ result_opts[:size] = s_1 > s_2 ? s_1 : s_2
94
+ elsif opts_1.has_key?(:size)
95
+ result_opts[:size] = opts_1[:size].is_a?(Array) ? opts_1[:size][0] + 1 : opts_1[:size]
96
+ elsif opts_2.has_key?(:size)
97
+ result_opts[:size] = opts_2[:size].is_a?(Array) ? opts_2[:size][0] + 1 : opts_2[:size]
98
+ end
99
+ else
100
+ # Treat as two strings
101
+ if opts_1.has_key?(:size) && opts_2.has_key?(:size)
102
+ result_opts[:size] = opts_1[:size] > opts_2[:size] ? opts_1[:size] : opts_2[:size]
103
+ elsif opts_1.has_key?(:size)
104
+ result_opts[:size] = opts_1[:size]
105
+ else
106
+ result_opts[:size] = opts_2[:size]
107
+ end
108
+ end
109
+ end
110
+
111
+ # fixed
112
+ if opts_1[:fixed] != opts_2[:fixed]
113
+ # This can only be of type String.
114
+ result_opts[:fixed] = true
115
+ end
116
+
117
+ result = {:type => result_type}
118
+ result[:opts] = result_opts unless result_opts.empty?
119
+ end
120
+
121
+ if new_name
122
+ name = new_name.to_sym
123
+ else
124
+ name = self.name == other.name ? self.name : :"#{self.name}_#{other.name}"
125
+ end
126
+ Field.new(name, nil, result)
127
+ end
128
+
129
+ # Returns true if this data's name and dataset match the other's name
130
+ # and dataset (using {Dataset#==})
131
+ def ==(other)
132
+ if !other.is_a?(self.class)
133
+ super
134
+ elsif equal?(other)
135
+ true
136
+ else
137
+ self.name == other.name && self.dataset == other.dataset
138
+ end
139
+ end
140
+
141
+ # Returns true if this data source's dataset is equal to the given dataset
142
+ # (using Dataset#id).
143
+ #
144
+ # @param [Linkage::Dataset]
145
+ def belongs_to?(dataset)
146
+ self.dataset.id == dataset.id
147
+ end
148
+
149
+ private
150
+
151
+ def first_common_type(type_1, type_2)
152
+ types_1 = [type_1] + get_types(type_1)
153
+ types_2 = [type_2] + get_types(type_2)
154
+ (types_1 & types_2).first
155
+ end
156
+
157
+ # Get all types that the specified type can be converted to. Order
158
+ # matters.
159
+ def get_types(type)
160
+ result = []
161
+ types = TYPE_CONVERSION_TREE[type]
162
+ if types
163
+ result += types
164
+ types.each do |t|
165
+ result |= get_types(t)
166
+ end
167
+ end
168
+ result
169
+ end
170
+ end
171
+ end
@@ -89,25 +89,25 @@ module Linkage
89
89
  })
90
90
  end
91
91
 
92
- # Add a field to use for ordering the dataset.
92
+ # Add a data source to use for ordering the dataset.
93
93
  #
94
- # @param [Linkage::Field] field
94
+ # @param [Linkage::Data] data
95
95
  # @param [nil, Symbol] desc nil or :desc (for descending order)
96
- def add_order(field, desc = nil)
97
- expr = desc == :desc ? field.name.desc : field.name
96
+ def add_order(data, desc = nil)
97
+ expr = desc == :desc ? data.to_expr.desc : data.to_expr
98
98
  unless @order.include?(expr)
99
99
  @order << expr
100
100
  end
101
101
  end
102
102
 
103
- # Add a field to be selected on the dataset. If you don't add any
104
- # selects, all fields will be selected. The primary key is always
105
- # selected in either case.
103
+ # Add a data source to be selected on the dataset. If you don't add any
104
+ # selects, all fields will be selected. The primary key is always selected
105
+ # in either case.
106
106
  #
107
- # @param [Linkage::Field] field
108
- # @param [Symbol] as Optional field alias
109
- def add_select(field, as = nil)
110
- expr = as ? field.name.as(as) : field.name
107
+ # @param [Linkage::Data] data
108
+ # @param [Symbol] as Optional field/function alias
109
+ def add_select(data, as = nil)
110
+ expr = as ? data.to_expr.as(as) : data.to_expr
111
111
  unless @select.include?(expr)
112
112
  @select << expr
113
113
  end
@@ -115,12 +115,12 @@ module Linkage
115
115
 
116
116
  # Add a filter (SQL WHERE) condition to the dataset.
117
117
  #
118
- # @param [Linkage::Field] field
118
+ # @param [Linkage::Data] data
119
119
  # @param [Symbol] operator
120
- # @param [Linkage::Field, Object] other
121
- def add_filter(field, operator, other)
122
- arg1 = field.name
123
- arg2 = other.is_a?(Field) ? other.name : other
120
+ # @param [Linkage::Data, Object] other
121
+ def add_filter(data, operator, other)
122
+ arg1 = data.to_expr
123
+ arg2 = other.is_a?(Data) ? other.to_expr : other
124
124
  expr =
125
125
  case operator
126
126
  when :==
@@ -9,13 +9,13 @@ module Linkage
9
9
  attr_reader :operator, :field_1, :field_2
10
10
 
11
11
  # @param [Symbol] operator Currently, only :==
12
- # @param [Linkage::Field, Object] field_1
13
- # @param [Linkage::Field, Object] field_2
12
+ # @param [Linkage::Field, Linkage::Function, Object] field_1
13
+ # @param [Linkage::Field, Linkage::Function, Object] field_2
14
14
  # @param [Symbol] force_kind Manually set type of expectation (useful for
15
15
  # a filter between two fields)
16
16
  def initialize(operator, field_1, field_2, force_kind = nil)
17
- if !(field_1.is_a?(Field) || field_2.is_a?(Field))
18
- raise ArgumentError, "You must have at least one Linkage::Field"
17
+ if !((field_1.kind_of?(Data) && !field_1.static?) || (field_2.kind_of?(Data) && !field_2.static?))
18
+ raise ArgumentError, "You must have at least one data source (Linkage::Field or Linkage::Function)"
19
19
  end
20
20
 
21
21
  if !VALID_OPERATORS.include?(operator)
@@ -52,7 +52,7 @@ module Linkage
52
52
  # @return [Symbol] :self, :dual, :cross, or :filter
53
53
  def kind
54
54
  @kind ||=
55
- if !(@field_1.is_a?(Field) && @field_2.is_a?(Field))
55
+ if !(@field_1.is_a?(Data) && !@field_1.static? && @field_2.is_a?(Data) && !@field_2.static?)
56
56
  :filter
57
57
  elsif @field_1 == @field_2
58
58
  :self
@@ -95,7 +95,7 @@ module Linkage
95
95
  else
96
96
  as =
97
97
  if kind == :self
98
- nil
98
+ @field_1.is_a?(Function) ? @field_1.name : nil
99
99
  else
100
100
  name != @field_1.name ? name : nil
101
101
  end
data/lib/linkage/field.rb CHANGED
@@ -1,24 +1,7 @@
1
1
  module Linkage
2
2
  # This class is for holding information about a particular field in a
3
3
  # dataset.
4
- class Field
5
- # A "tree" used to find compatible types.
6
- TYPE_CONVERSION_TREE = {
7
- TrueClass => [Integer],
8
- Integer => [Bignum, Float],
9
- Bignum => [BigDecimal],
10
- Float => [BigDecimal],
11
- BigDecimal => [String],
12
- String => nil,
13
- DateTime => nil,
14
- Date => nil,
15
- Time => nil,
16
- File => nil
17
- }
18
-
19
- # @return [Symbol] This field's name
20
- attr_reader :name
21
-
4
+ class Field < Data
22
5
  # @return [Symbol] This field's schema information
23
6
  attr_reader :schema
24
7
 
@@ -90,138 +73,16 @@ module Linkage
90
73
  @ruby_type
91
74
  end
92
75
 
93
- # Create a field that can hold data from two other fields. If the fields
94
- # have different types, the resulting type is determined via a
95
- # type-conversion tree.
96
- #
97
- # @param [Linkage::Field] other
98
- # @return [Linkage::Field]
99
- def merge(other, new_name = nil)
100
- schema_1 = self.ruby_type
101
- schema_2 = other.ruby_type
102
- if schema_1 == schema_2
103
- result = schema_1
104
- else
105
- type_1 = schema_1[:type]
106
- opts_1 = schema_1[:opts] || {}
107
- type_2 = schema_2[:type]
108
- opts_2 = schema_2[:opts] || {}
109
- result_type = type_1
110
- result_opts = schema_1[:opts] ? schema_1[:opts].dup : {}
111
-
112
- # type
113
- if type_1 != type_2
114
- result_type = first_common_type(type_1, type_2)
115
- end
116
-
117
- # text
118
- if opts_1[:text] != opts_2[:text]
119
- # This can only be of type String.
120
- result_opts[:text] = true
121
- result_opts.delete(:size)
122
- end
123
-
124
- # size
125
- if !result_opts[:text] && opts_1[:size] != opts_2[:size]
126
- types = [type_1, type_2].uniq
127
- if types.length == 1 && types[0] == BigDecimal
128
- # Two decimals
129
- if opts_1.has_key?(:size) && opts_2.has_key?(:size)
130
- s_1 = opts_1[:size]
131
- s_2 = opts_2[:size]
132
- result_opts[:size] = [ s_1[0] > s_2[0] ? s_1[0] : s_2[0] ]
133
-
134
- if s_1[1] && s_2[1]
135
- result_opts[:size][1] = s_1[1] > s_2[1] ? s_1[1] : s_2[1]
136
- else
137
- result_opts[:size][1] = s_1[1] ? s_1[1] : s_2[1]
138
- end
139
- else
140
- result_opts[:size] = opts_1.has_key?(:size) ? opts_1[:size] : opts_2[:size]
141
- end
142
- elsif types.include?(String) && types.include?(BigDecimal)
143
- # Add one to the precision of the BigDecimal (for the dot)
144
- if opts_1.has_key?(:size) && opts_2.has_key?(:size)
145
- s_1 = opts_1[:size].is_a?(Array) ? opts_1[:size][0] + 1 : opts_1[:size]
146
- s_2 = opts_2[:size].is_a?(Array) ? opts_2[:size][0] + 1 : opts_2[:size]
147
- result_opts[:size] = s_1 > s_2 ? s_1 : s_2
148
- elsif opts_1.has_key?(:size)
149
- result_opts[:size] = opts_1[:size].is_a?(Array) ? opts_1[:size][0] + 1 : opts_1[:size]
150
- elsif opts_2.has_key?(:size)
151
- result_opts[:size] = opts_2[:size].is_a?(Array) ? opts_2[:size][0] + 1 : opts_2[:size]
152
- end
153
- else
154
- # Treat as two strings
155
- if opts_1.has_key?(:size) && opts_2.has_key?(:size)
156
- result_opts[:size] = opts_1[:size] > opts_2[:size] ? opts_1[:size] : opts_2[:size]
157
- elsif opts_1.has_key?(:size)
158
- result_opts[:size] = opts_1[:size]
159
- else
160
- result_opts[:size] = opts_2[:size]
161
- end
162
- end
163
- end
164
-
165
- # fixed
166
- if opts_1[:fixed] != opts_2[:fixed]
167
- # This can only be of type String.
168
- result_opts[:fixed] = true
169
- end
170
-
171
- result = {:type => result_type}
172
- result[:opts] = result_opts unless result_opts.empty?
173
- end
174
-
175
- if new_name
176
- name = new_name.to_sym
177
- else
178
- name = self.name == other.name ? self.name : :"#{self.name}_#{other.name}"
179
- end
180
- Field.new(name, nil, result)
181
- end
182
-
183
- # Returns true if this field's name and dataset match the other's name
184
- # and dataset (using {Dataset#==})
185
- def ==(other)
186
- if !other.is_a?(Field)
187
- super
188
- else
189
- self.name == other.name && self.dataset == other.dataset
190
- end
76
+ def to_expr
77
+ @name
191
78
  end
192
79
 
193
- # Returns true if this field's dataset is equal to the given dataset
194
- # (using Dataset#id).
195
- #
196
- # @param [Linkage::Dataset]
197
- def belongs_to?(dataset)
198
- self.dataset.id == dataset.id
80
+ def static?
81
+ false
199
82
  end
200
83
 
201
84
  def primary_key?
202
85
  schema && schema[:primary_key]
203
86
  end
204
-
205
- private
206
-
207
- def first_common_type(type_1, type_2)
208
- types_1 = [type_1] + get_types(type_1)
209
- types_2 = [type_2] + get_types(type_2)
210
- (types_1 & types_2).first
211
- end
212
-
213
- # Get all types that the specified type can be converted to. Order
214
- # matters.
215
- def get_types(type)
216
- result = []
217
- types = TYPE_CONVERSION_TREE[type]
218
- if types
219
- result += types
220
- types.each do |t|
221
- result |= get_types(t)
222
- end
223
- end
224
- result
225
- end
226
87
  end
227
88
  end