linkage 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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