flounder 0.18.3 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 000e2bacbb00518bad55beff484a34fddf2bad90
4
- data.tar.gz: cef0c32ba9086ebf32fae131b99747c19a58839a
3
+ metadata.gz: 147eda5bed1bdaba30729f559e90632dcc127004
4
+ data.tar.gz: 658f4e9765bf292d36ff6966c56679a4d4e8d20f
5
5
  SHA512:
6
- metadata.gz: 7f2cf21c18f09391606b9c00110eab5534ccc6a9ec18b3a507a68cf567b9f6e909dd658408568391bf748474fce45502debb6ac579cde1f32b884f850481a9f6
7
- data.tar.gz: ab0b21e881cadd1caeec275b75f91f7386fa1e673eab24ac1495562a7d7f4161f52f6fdd02258ac9d146af39852a8937f7c69584e17e1222b8f68c043a2c2fe0
6
+ metadata.gz: 56487164cc91a3a6614f70a5a5142ad98309f2b05aef756fd192bb9c641cd32348f79be04a85ad98fa99b3f2d02ab923fdd3332390cba43235f57eb64310cc33
7
+ data.tar.gz: e630f8f88801e8abee286a1ac3654f41ec7b9a5564e8168815ebd5ac9c74be54f74151005f75caf3036f92167002092d45df514b2d8aeda3ea8e2e3650739208
data/Gemfile CHANGED
@@ -3,6 +3,12 @@ source "https://rubygems.org"
3
3
  group :test do
4
4
  gem 'qed'
5
5
  gem 'ae'
6
+ gem 'flexmock'
7
+ gem 'rspec'
8
+ end
9
+
10
+ group :benchmark do
11
+ gem 'ruby-prof'
6
12
  end
7
13
 
8
14
  gemspec
data/Gemfile.lock CHANGED
@@ -1,15 +1,12 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flounder (0.18.1)
4
+ flounder (0.18.3)
5
5
  aggregate (~> 0.2, >= 0.2.2)
6
6
  arel (~> 5, > 5.0.1)
7
- blankslate
8
7
  connection_pool (~> 2)
9
- hashie (~> 3, >= 3.2)
10
8
  pg (~> 0.17)
11
9
  pg-hstore (~> 1.2, >= 1.2.0)
12
- virtus
13
10
 
14
11
  GEM
15
12
  remote: https://rubygems.org/
@@ -19,24 +16,11 @@ GEM
19
16
  aggregate (0.2.2)
20
17
  ansi (1.4.3)
21
18
  arel (5.0.1.20140414130214)
22
- axiom-types (0.0.5)
23
- descendants_tracker (~> 0.0.1)
24
- ice_nine (~> 0.9)
25
- backports (3.6.3)
26
- blankslate (3.1.3)
27
19
  brass (1.2.1)
28
- coercible (0.2.0)
29
- backports (~> 3.0, >= 3.1.0)
30
- descendants_tracker (~> 0.0.1)
31
20
  connection_pool (2.0.0)
32
- descendants_tracker (0.0.4)
33
- thread_safe (~> 0.3, >= 0.3.1)
34
21
  diff-lcs (1.2.5)
35
- equalizer (0.0.9)
36
22
  facets (2.9.3)
37
23
  flexmock (1.3.3)
38
- hashie (3.3.1)
39
- ice_nine (0.11.0)
40
24
  pg (0.17.1)
41
25
  pg-hstore (1.2.0)
42
26
  qed (2.9.1)
@@ -56,12 +40,6 @@ GEM
56
40
  rspec-support (~> 3.1.0)
57
41
  rspec-support (3.1.2)
58
42
  ruby-prof (0.15.2)
59
- thread_safe (0.3.4)
60
- virtus (1.0.0)
61
- axiom-types (~> 0.0.5)
62
- coercible (~> 0.2)
63
- descendants_tracker (~> 0.0.1)
64
- equalizer (~> 0.0.7)
65
43
 
66
44
  PLATFORMS
67
45
  ruby
data/HISTORY CHANGED
@@ -1,3 +1,15 @@
1
+ # 1.0
2
+
3
+ * Return type from all queries is now not a Hashie::Mash, but a custom row
4
+ type. The interface it supports is a lot more restricted. This has two
5
+ benefits:
6
+
7
+ a) Speed increase for queries that return many rows
8
+ b) Maintenance is easier
9
+
10
+ In fact, Hashie::Mash is very tolerant with regards to access to keys that
11
+ do not exist. The new code is not, so expect breakage.
12
+
1
13
  # 0.18
2
14
  + Allows usage of relation names in where clauses.
3
15
  + Remaps entity oids automatically if a mismatch exists.
data/README CHANGED
@@ -19,8 +19,8 @@ SYNOPSIS
19
19
 
20
20
  STATUS
21
21
 
22
- Heavily volatile alpha, but feature stable for most of what we already have.
23
- We welcome feedback! (flounder at technologyastronauts dot ch)
22
+ Somewhat cooled off beta phase - Most features are stable now and the API
23
+ doesn't change radically anymore.
24
24
 
25
25
  LICENSE
26
26
 
data/benchmark/001.rb ADDED
@@ -0,0 +1,12 @@
1
+
2
+ # First benchmark just tests loading of data into array format.
3
+ # Run with
4
+ # ruby -r profile benchmark/001.rb
5
+
6
+ require_relative 'lib/connection'
7
+
8
+ domain = Benchmark.domain
9
+ perf01 = domain[:perf01]
10
+
11
+ a = perf01.all.to_a
12
+
data/benchmark/002.rb ADDED
@@ -0,0 +1,18 @@
1
+
2
+ # First benchmark just tests loading of data into array format.
3
+
4
+ require_relative 'lib/connection'
5
+ require 'ruby-prof'
6
+
7
+ domain = Benchmark.domain
8
+ perf01 = domain[:perf01]
9
+
10
+ r = perf01.all.to_a
11
+ RubyProf.start
12
+ a = r.map(&:foo).inject(0) { |sum, el| sum + el }
13
+ result = RubyProf.stop
14
+
15
+ # p a
16
+
17
+ printer = RubyProf::DotPrinter.new(result)
18
+ printer.print(STDOUT, min_percent: 2)
@@ -0,0 +1,19 @@
1
+
2
+ # To get this to work, please run the qed tests first, since those load the
3
+ # database structure.
4
+
5
+ require 'flounder'
6
+
7
+ module Benchmark
8
+
9
+ module_function
10
+ def domain
11
+ @domain ||= begin
12
+ connection = Flounder.connect(dbname: 'flounder')
13
+ Flounder.domain(connection) do |dom|
14
+ dom.entity(:perf01, :perf01, 'perf01')
15
+ end
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,15 @@
1
+
2
+ module Benchmark
3
+
4
+ module_function
5
+ def rand_string min_len, max_len
6
+ len = min_len + rand(max_len-min_len)
7
+
8
+ s = ''
9
+ len.times do
10
+ s << (?a.ord + rand(27)).chr
11
+ end
12
+
13
+ s
14
+ end
15
+ end
data/benchmark/load.rb ADDED
@@ -0,0 +1,23 @@
1
+
2
+ require_relative 'lib/connection'
3
+ require_relative 'lib/random'
4
+
5
+ domain = Benchmark.domain
6
+ perf01 = domain[:perf01]
7
+
8
+ n = 1000
9
+ puts "Loading perf01 with #{n} records..."
10
+ n.times do |n|
11
+ perf01.insert(
12
+ foo: n,
13
+ bar: rand(100000),
14
+ baz: rand(1000),
15
+ when: Time.now,
16
+ count: n,
17
+ iif: rand() < 0.5,
18
+ type: Benchmark.rand_string(5, 25),
19
+ bemerkung: Benchmark.rand_string(5, 200)).kick
20
+ end
21
+
22
+
23
+ p perf01.all.to_a
data/flounder.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "flounder"
5
- s.version = '0.18.3'
5
+ s.version = '1.0.0'
6
6
  s.summary = "Flounder is a way to write SQL simply in Ruby. It deals with everything BUT object relational mapping. "
7
7
  s.email = "kaspar.schiess@technologyastronauts.ch"
8
8
  s.homepage = "https://bitbucket.org/technologyastronauts/oss_flounder"
@@ -16,7 +16,6 @@ Gem::Specification.new do |s|
16
16
 
17
17
  s.add_runtime_dependency 'arel', '~> 5', '> 5.0.1'
18
18
  s.add_runtime_dependency 'pg', '~> 0.17'
19
- s.add_runtime_dependency 'hashie', '~> 3', '>= 3.2'
20
19
  s.add_runtime_dependency 'connection_pool', '~> 2'
21
20
  s.add_runtime_dependency 'pg-hstore', '~> 1.2', '>= 1.2.0'
22
21
  s.add_runtime_dependency 'aggregate', '~> 0.2', '>= 0.2.2'
data/lib/flounder.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require 'pg'
2
2
  require 'arel'
3
- require 'hashie/mash'
4
3
 
5
4
  require 'flounder/symbol_extensions'
6
5
 
@@ -23,6 +22,9 @@ require 'flounder/exceptions'
23
22
 
24
23
  require 'flounder/expression'
25
24
 
25
+ require 'flounder/result/descriptor'
26
+ require 'flounder/result/row'
27
+
26
28
  module Flounder
27
29
  module_function
28
30
  def connect opts={}
@@ -35,7 +35,7 @@ module Flounder
35
35
  name = result.fname(idx)
36
36
  type_oid = result.ftype(idx)
37
37
  mod = result.fmod(idx)
38
- typesym = type_oid_to_sym(type_oid)
38
+ typesym = type_oid_to_sym(self, type_oid)
39
39
 
40
40
  unless typesym
41
41
  type_string = type_name(type_oid, mod)
@@ -16,14 +16,16 @@ module Flounder
16
16
  OID_TIME = 1083
17
17
  OID_TIMESTAMPTZ = 1184
18
18
 
19
- def typecast type_oid, value
19
+ def typecast connection, type_oid, value
20
+ raise ArgumentError, "AF: type_oid must not be nil" unless type_oid
21
+
20
22
  return nil unless value
21
23
 
22
24
  # hstore extension
23
- if oid_hstore && type_oid == oid_hstore
25
+ if type_oid == oid_hstore(connection)
24
26
  return PgHstore.load(value)
25
27
  end
26
-
28
+
27
29
  # assert: value is not nil
28
30
  case type_oid
29
31
  when OID_TIMESTAMPTZ
@@ -47,12 +49,12 @@ module Flounder
47
49
  end
48
50
  end
49
51
 
50
- def oid_hstore
52
+ def oid_hstore connection
51
53
  unless @oid_hstore_initialized
52
54
  @oid_hstore_initialized = true
53
55
 
54
56
  @oid_hstore = begin
55
- result = exec("SELECT oid FROM pg_type WHERE typname='hstore'")
57
+ result = connection.exec("SELECT oid FROM pg_type WHERE typname='hstore'")
56
58
  row = result.first
57
59
 
58
60
  row && row.values.first.to_i
@@ -62,8 +64,10 @@ module Flounder
62
64
  @oid_hstore
63
65
  end
64
66
 
65
- def type_oid_to_sym oid
66
- return :hash if oid_hstore && oid==oid_hstore
67
+ def type_oid_to_sym connection, oid
68
+ raise ArgumentError, "AF: oid must not be nil" unless oid
69
+
70
+ return :hash if oid==oid_hstore(connection)
67
71
 
68
72
  case oid
69
73
  when OID_TIMESTAMP, OID_TIMESTAMPTZ
@@ -85,22 +89,35 @@ module Flounder
85
89
  end
86
90
  end
87
91
 
88
- def each_field entity, result, row_idx
92
+ # Yields header information for each column in the given result in turn.
93
+ #
94
+ # @yieldparam idx [Integer] column index
95
+ # @yieldparam entity [Flounder::Entity] entity as resolved by table OID
96
+ # @yieldparam field_name [String] field name as present in the SQL result
97
+ # @yieldparam type [Integer] type OID
98
+ # @yieldparam binary [Boolean] is this a binary field?
99
+ #
100
+ def each_field entity, result
89
101
  domain = entity.domain
90
102
 
91
103
  result.nfields.times do |field_idx|
92
104
  table_oid = result.ftable(field_idx)
93
105
  entity = domain.by_oid(table_oid)
94
106
 
95
- yield entity,
107
+ yield(
108
+ field_idx,
109
+ entity,
96
110
  result.fname(field_idx),
97
- result.getvalue(row_idx, field_idx),
98
111
  result.ftype(field_idx),
99
- result.fformat(field_idx) == 1,
100
- field_idx
112
+ result.fformat(field_idx) == 1)
101
113
  end
102
114
  end
103
115
 
116
+ def access_value connection, result, type_oid, row_idx, col_idx
117
+ value = result.getvalue(row_idx, col_idx)
118
+ typecast(connection, type_oid, value)
119
+ end
120
+
104
121
  # Helper function for debugging
105
122
  def type_name ftype, fmod
106
123
  pg.
@@ -52,31 +52,38 @@ module Flounder::Query
52
52
  manager.to_sql.tap { |sql|
53
53
  domain.log_sql(sql) }
54
54
  end
55
-
55
+
56
56
  # Returns all rows of the query result as an array. Individual rows are
57
57
  # mapped to objects using the row mapper.
58
58
  #
59
59
  def kick connection=nil
60
60
  all = nil
61
+ connection ||= engine
61
62
 
62
63
  measure do
63
- (connection || engine).exec(self.to_sql, bind_values) do |result|
64
- all = Array.new(result.ntuples, nil)
65
- result.ntuples.times do |row_idx|
66
- all[row_idx] = engine.connection.
67
- objectify_result_row(entity, result, row_idx) do |name|
68
- column_name_to_entity(name)
69
- end
70
- end
64
+ result = connection.exec(self.to_sql, bind_values)
65
+
66
+ descriptor = ::Flounder::Result::Descriptor.new(
67
+ connection, entity, result, &method(:column_name_to_entity))
68
+
69
+ all = Array.new(result.ntuples, nil)
70
+ result.ntuples.times do |row_idx|
71
+ all[row_idx] = descriptor.row(row_idx)
71
72
  end
72
73
  end
73
74
 
74
75
  all
75
76
  end
77
+
78
+ # Implement this if your column names in the query allow inferring
79
+ # the entity and the column name. Return them as a tuple <entity, name>.
80
+ #
76
81
  def column_name_to_entity name
77
- # Implement this if your column names in the query allow inferring
78
- # the entity and the column name. Return them as a tuple <entity, name>.
79
82
  end
83
+
84
+ # Measures the block given to it and logs time spent in the block to the
85
+ # domain.
86
+ #
80
87
  def measure
81
88
  measure = Benchmark.measure {
82
89
  yield
@@ -233,8 +240,8 @@ module Flounder::Query
233
240
  end
234
241
  end
235
242
 
236
- # Called on each key/value pair of an update clause, this returns a
237
- # hash that can be passed to Arel #update.
243
+ # Called on each key/value pair of an update clause, this returns a hash
244
+ # that can be passed to Arel #update.
238
245
  #
239
246
  def transform_tuple_for_set field, value
240
247
  if value.kind_of? Hash
@@ -161,7 +161,7 @@ module Flounder::Query
161
161
  #
162
162
  def size
163
163
  manager.projections = []
164
- project 'count(*) as count'
164
+ project 'count(*)::int as count'
165
165
 
166
166
  all.first.count
167
167
  end
@@ -0,0 +1,18 @@
1
+
2
+ module Flounder::Result::Accessor
3
+ class Field
4
+ attr_reader :descriptor
5
+ attr_reader :col_idx
6
+ attr_reader :type_oid
7
+
8
+ def initialize descriptor, col_idx, type_oid
9
+ @descriptor = descriptor
10
+ @col_idx = col_idx
11
+ @type_oid = type_oid
12
+ end
13
+
14
+ def produce_value(row_idx)
15
+ descriptor.value(type_oid, row_idx, col_idx)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,36 @@
1
+
2
+ require_relative 'field'
3
+
4
+ module Flounder::Result::Accessor
5
+ class Node
6
+ attr_reader :children_by_name
7
+
8
+ def initialize
9
+ @children_by_name = Hash.new do |hash, name|
10
+ hash[name.to_sym] = Node.new
11
+ end
12
+ end
13
+
14
+ def [] name
15
+ children_by_name[name.to_sym]
16
+ end
17
+ def has_obj? name
18
+ children_by_name.has_key? name.to_sym
19
+ end
20
+ def names
21
+ children_by_name.keys
22
+ end
23
+
24
+ def add_field name, *a
25
+ children_by_name[name.to_sym] = Field.new(*a)
26
+ end
27
+
28
+ def size
29
+ children_by_name.size
30
+ end
31
+
32
+ def produce_value *_
33
+ yield self
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,70 @@
1
+
2
+ module Flounder::Result
3
+ class Descriptor
4
+ include ::Flounder::PostgresUtils
5
+
6
+ # Result obtained from the connection
7
+ attr_reader :pg_result
8
+
9
+ attr_reader :connection
10
+
11
+ # Entity to use for field resolution
12
+ attr_reader :entity
13
+
14
+ # Root of the accessor tree.
15
+ # @api private
16
+ attr_reader :accessor_root
17
+
18
+ # @api private
19
+ attr_reader :name_resolver
20
+
21
+ def initialize connection, entity, pg_result, &name_resolver
22
+ @entity = entity
23
+ @pg_result = pg_result
24
+ @connection = connection
25
+ @name_resolver = name_resolver || -> (name) {}
26
+
27
+ build_accessors
28
+ end
29
+
30
+ def row idx
31
+ Row.new(accessor_root, idx)
32
+ end
33
+
34
+ def value type, row_idx, col_idx
35
+ access_value(connection, pg_result, type, row_idx, col_idx)
36
+ end
37
+
38
+ # Parses and builds accessor structure for the result stored here.
39
+ #
40
+ # @api private
41
+ #
42
+ def build_accessors
43
+ @accessor_root = Accessor::Node.new
44
+ each_field(entity, pg_result) do |idx, entity, field, type, binary|
45
+ processed_entity, processed_name = name_resolver.call(field)
46
+ entity = processed_entity if processed_entity
47
+ field = processed_name if processed_name
48
+
49
+ # JOIN tables are available from the result using their singular names.
50
+ if entity
51
+ accessor_root[entity.singular].add_field(field, self, idx, type)
52
+ end
53
+
54
+ # The main entity and custom fields (AS something) are available on the
55
+ # top-level of the result.
56
+ if field && (!entity || entity == self.entity)
57
+ raise ::Flounder::DuplicateField,
58
+ "#{field.inspect} already defined in result set, aliasing occurs." \
59
+ if accessor_root.has_obj? field
60
+
61
+ accessor_root.add_field(field, self, idx, type)
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ require 'flounder/result/accessor/node'
70
+
@@ -0,0 +1,97 @@
1
+ module Flounder::Result
2
+ class Row
3
+ def initialize node, row_idx
4
+ @root = node
5
+ @row_idx = row_idx
6
+ @attributes = {}
7
+ end
8
+
9
+ # Primary resolution mechanism: Lazy lookup of fields.
10
+
11
+ def method_missing sym, *args, &block
12
+ if @root.has_obj?(sym)
13
+ return cache_attribute(sym)
14
+ end
15
+
16
+ if sym.to_s.end_with?(??)
17
+ stripped = sym.to_s[0..-2]
18
+ return @root.has_obj?(stripped) && !value_for(stripped).nil?
19
+ end
20
+
21
+ super
22
+ end
23
+ def respond_to? sym, include_all=false
24
+ @attributes.has_key?(sym) ||
25
+ @root.has_obj?(sym) ||
26
+ super
27
+ end
28
+ def methods regular=true
29
+ (__columns__ + super).uniq
30
+ end
31
+
32
+ def inspect
33
+ "flounder/Row(#{__columns__.inspect})"
34
+ end
35
+
36
+ def == other
37
+ if other.kind_of?(Row)
38
+ other_root = other.instance_variable_get('@root')
39
+
40
+ __columns__ == other.__columns__ &&
41
+ __columns__.all? { |name|
42
+ my_value = self[name]
43
+ other_value = other[name]
44
+ my_value == other_value }
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ def [] name
51
+ if @root.has_obj?(name)
52
+ value_for(name)
53
+ end
54
+ end
55
+
56
+ # Returns values of given keys as an array.
57
+ #
58
+ def values_at *keys
59
+ keys.map { |key| self[key] }
60
+ end
61
+
62
+ # Returns all column names.
63
+ #
64
+ def __columns__
65
+ @root.names
66
+ end
67
+
68
+ # Turns this row into a hash, performing deep conversion of all field names.
69
+ # Use this to go into the Hash world and never come back.
70
+ #
71
+ def to_h
72
+ __columns__.map { |name| [name, value_for(name)] }.to_h
73
+ end
74
+
75
+ private
76
+
77
+ def value_for name
78
+ if @attributes.has_key?(name)
79
+ return @attributes[name]
80
+ end
81
+
82
+ node = @root[name]
83
+ @attributes[name] = node && node.produce_value(@row_idx) { |node|
84
+ Row.new(node, @row_idx)
85
+ }
86
+ end
87
+
88
+ def cache_attribute name
89
+ value = value_for(name)
90
+
91
+ # Produce a local accessor via Virtus
92
+ self.define_singleton_method(name) { value }
93
+
94
+ value
95
+ end
96
+ end
97
+ end
data/qed/flounder.sql CHANGED
@@ -41,6 +41,24 @@ BEGIN;
41
41
  INSERT INTO "comments" (post_id, text, author_id) VALUES (1, 'A silly comment.', 1);
42
42
  COMMIT;
43
43
 
44
+ BEGIN;
45
+ -- A table with structure to do performance measurements.
46
+ DROP TABLE IF EXISTS "perf01" CASCADE;
47
+ CREATE TABLE "perf01" (
48
+ "id" serial not null primary key,
49
+ "foo" int4,
50
+ "bar" int4,
51
+ "baz" int2 not null,
52
+ "when" time(6),
53
+ "count" int4,
54
+ "iif" bool DEFAULT true,
55
+ "type" varchar(255),
56
+ "created_at" timestamp(6) not NULL default(now()),
57
+ "updated_at" timestamp(6) not NULL default(now()),
58
+ "bemerkung" text
59
+ );
60
+ COMMIT;
61
+
44
62
  -- import using
45
63
  --
46
64
  -- cat qed/flounder.sql | psql flounder
data/qed/projection.md CHANGED
@@ -18,7 +18,11 @@ When projecting, all result data will be toplevel and only the projected data is
18
18
  result = posts.join(users).on(:user_id => :id).
19
19
  project(users[:name]).first
20
20
 
21
- result.id.assert == nil # Not loaded.
21
+ result.refute.respond_to?(:id) # Not loaded.
22
+
23
+ result.refute.id? # Synonymous to obj.respond_to?(:id) && !obj.id.nil?
24
+ result.user.assert.name?
25
+
22
26
  result.user.name.assert == 'John Snow'
23
27
  ~~~
24
28
 
@@ -0,0 +1,27 @@
1
+
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Flounder::EntityAlias do
6
+ class Table
7
+ def alias *a
8
+ self
9
+ end
10
+
11
+ def [] name
12
+ "field #{name}"
13
+ end
14
+ end
15
+
16
+ let(:table) { Table.new }
17
+ let(:entity) { flexmock('entity',
18
+ table: table, name: 'entity', table_name: 'table') }
19
+
20
+ let(:ea) { Flounder::EntityAlias.new(entity, :aliases, :alias) }
21
+
22
+ describe '#fields' do
23
+ it 'returns a list of fields' do
24
+ ea.fields(:a, :b).assert == [ea[:a], ea[:b]]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,70 @@
1
+
2
+ # Run tests using
3
+ # ruby -Ilib:test TEST_FILE
4
+
5
+ require 'spec_helper'
6
+
7
+ describe Flounder::Result::Row do
8
+ class DescriptorStub
9
+ def initialize hash
10
+ @hash = hash
11
+ end
12
+
13
+ def has_obj? name
14
+ @hash.has_key?(name)
15
+ end
16
+
17
+ def [] name
18
+ val = @hash.fetch(name)
19
+
20
+ if val.kind_of? Hash
21
+ else
22
+ ValueStub.new(val)
23
+ end
24
+ end
25
+
26
+ def names
27
+ @hash.keys
28
+ end
29
+ end
30
+ class ValueStub
31
+ def initialize value
32
+ @value = value
33
+ end
34
+ def produce_value *a
35
+ @value
36
+ end
37
+ end
38
+
39
+ let(:descriptor) { DescriptorStub.new(:foo => :bar, :bar => :baz) }
40
+ let(:row) { Flounder::Result::Row.new(descriptor, 1) }
41
+
42
+ it 'allows field access through methods' do
43
+ row.foo.assert == :bar
44
+ end
45
+ it 'raises when accessing fields that don\'t exist' do
46
+ NoMethodError.raised? do
47
+ row.bar
48
+ end
49
+ end
50
+ it 'has a useful inspect' do
51
+ row.inspect.assert == "flounder/Row([:foo, :bar])"
52
+ end
53
+
54
+ describe 'hash-like features' do
55
+ it 'has [] accessor' do
56
+ row[:foo].assert == :bar
57
+ end
58
+ it 'has __columns__ list' do
59
+ row.__columns__.assert == [:foo, :bar]
60
+ end
61
+
62
+ it 'converts to a hash (#to_h)' do
63
+ row.to_h.assert == {:foo => :bar, :bar => :baz}
64
+ end
65
+
66
+ it 'has #values_at accessor' do
67
+ row.values_at(:foo, :bar).assert == [:bar, :baz]
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,11 @@
1
+
2
+
3
+ require 'rspec'
4
+ require 'flexmock'
5
+ require 'ae/adapters/rspec'
6
+
7
+ RSpec.configure do |config|
8
+ config.mock_with :flexmock
9
+ end
10
+
11
+ require 'flounder'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flounder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.3
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaspar Schiess
@@ -9,114 +9,94 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-12-02 00:00:00.000000000 Z
12
+ date: 2014-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: arel
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ~>
18
+ - - "~>"
19
19
  - !ruby/object:Gem::Version
20
20
  version: '5'
21
- - - '>'
21
+ - - ">"
22
22
  - !ruby/object:Gem::Version
23
23
  version: 5.0.1
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
- - - ~>
28
+ - - "~>"
29
29
  - !ruby/object:Gem::Version
30
30
  version: '5'
31
- - - '>'
31
+ - - ">"
32
32
  - !ruby/object:Gem::Version
33
33
  version: 5.0.1
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: pg
36
36
  requirement: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.17'
41
41
  type: :runtime
42
42
  prerelease: false
43
43
  version_requirements: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0.17'
48
- - !ruby/object:Gem::Dependency
49
- name: hashie
50
- requirement: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ~>
53
- - !ruby/object:Gem::Version
54
- version: '3'
55
- - - '>='
56
- - !ruby/object:Gem::Version
57
- version: '3.2'
58
- type: :runtime
59
- prerelease: false
60
- version_requirements: !ruby/object:Gem::Requirement
61
- requirements:
62
- - - ~>
63
- - !ruby/object:Gem::Version
64
- version: '3'
65
- - - '>='
66
- - !ruby/object:Gem::Version
67
- version: '3.2'
68
48
  - !ruby/object:Gem::Dependency
69
49
  name: connection_pool
70
50
  requirement: !ruby/object:Gem::Requirement
71
51
  requirements:
72
- - - ~>
52
+ - - "~>"
73
53
  - !ruby/object:Gem::Version
74
54
  version: '2'
75
55
  type: :runtime
76
56
  prerelease: false
77
57
  version_requirements: !ruby/object:Gem::Requirement
78
58
  requirements:
79
- - - ~>
59
+ - - "~>"
80
60
  - !ruby/object:Gem::Version
81
61
  version: '2'
82
62
  - !ruby/object:Gem::Dependency
83
63
  name: pg-hstore
84
64
  requirement: !ruby/object:Gem::Requirement
85
65
  requirements:
86
- - - ~>
66
+ - - "~>"
87
67
  - !ruby/object:Gem::Version
88
68
  version: '1.2'
89
- - - '>='
69
+ - - ">="
90
70
  - !ruby/object:Gem::Version
91
71
  version: 1.2.0
92
72
  type: :runtime
93
73
  prerelease: false
94
74
  version_requirements: !ruby/object:Gem::Requirement
95
75
  requirements:
96
- - - ~>
76
+ - - "~>"
97
77
  - !ruby/object:Gem::Version
98
78
  version: '1.2'
99
- - - '>='
79
+ - - ">="
100
80
  - !ruby/object:Gem::Version
101
81
  version: 1.2.0
102
82
  - !ruby/object:Gem::Dependency
103
83
  name: aggregate
104
84
  requirement: !ruby/object:Gem::Requirement
105
85
  requirements:
106
- - - ~>
86
+ - - "~>"
107
87
  - !ruby/object:Gem::Version
108
88
  version: '0.2'
109
- - - '>='
89
+ - - ">="
110
90
  - !ruby/object:Gem::Version
111
91
  version: 0.2.2
112
92
  type: :runtime
113
93
  prerelease: false
114
94
  version_requirements: !ruby/object:Gem::Requirement
115
95
  requirements:
116
- - - ~>
96
+ - - "~>"
117
97
  - !ruby/object:Gem::Version
118
98
  version: '0.2'
119
- - - '>='
99
+ - - ">="
120
100
  - !ruby/object:Gem::Version
121
101
  version: 0.2.2
122
102
  description: " Flounder is the missing piece between the database and your Ruby
@@ -127,11 +107,19 @@ executables: []
127
107
  extensions: []
128
108
  extra_rdoc_files: []
129
109
  files:
130
- - flounder.gemspec
131
110
  - Gemfile
132
111
  - Gemfile.lock
133
112
  - HACKING
134
113
  - HISTORY
114
+ - LICENSE
115
+ - README
116
+ - benchmark/001.rb
117
+ - benchmark/002.rb
118
+ - benchmark/lib/connection.rb
119
+ - benchmark/lib/random.rb
120
+ - benchmark/load.rb
121
+ - flounder.gemspec
122
+ - lib/flounder.rb
135
123
  - lib/flounder/connection.rb
136
124
  - lib/flounder/connection_pool.rb
137
125
  - lib/flounder/domain.rb
@@ -151,9 +139,11 @@ files:
151
139
  - lib/flounder/query/select.rb
152
140
  - lib/flounder/query/update.rb
153
141
  - lib/flounder/relation.rb
142
+ - lib/flounder/result/accessor/field.rb
143
+ - lib/flounder/result/accessor/node.rb
144
+ - lib/flounder/result/descriptor.rb
145
+ - lib/flounder/result/row.rb
154
146
  - lib/flounder/symbol_extensions.rb
155
- - lib/flounder.rb
156
- - LICENSE
157
147
  - qed/applique/ae.rb
158
148
  - qed/applique/flounder.rb
159
149
  - qed/applique/setup_domain.rb
@@ -172,7 +162,9 @@ files:
172
162
  - qed/projection.md
173
163
  - qed/selects.md
174
164
  - qed/updates.md
175
- - README
165
+ - spec/lib/entity_alias_spec.rb
166
+ - spec/lib/result/row_spec.rb
167
+ - spec/spec_helper.rb
176
168
  homepage: https://bitbucket.org/technologyastronauts/oss_flounder
177
169
  licenses:
178
170
  - MIT
@@ -183,17 +175,17 @@ require_paths:
183
175
  - lib
184
176
  required_ruby_version: !ruby/object:Gem::Requirement
185
177
  requirements:
186
- - - '>='
178
+ - - ">="
187
179
  - !ruby/object:Gem::Version
188
180
  version: '0'
189
181
  required_rubygems_version: !ruby/object:Gem::Requirement
190
182
  requirements:
191
- - - '>='
183
+ - - ">="
192
184
  - !ruby/object:Gem::Version
193
185
  version: '0'
194
186
  requirements: []
195
187
  rubyforge_project:
196
- rubygems_version: 2.0.14
188
+ rubygems_version: 2.2.2
197
189
  signing_key:
198
190
  specification_version: 4
199
191
  summary: Flounder is a way to write SQL simply in Ruby. It deals with everything BUT
@@ -217,3 +209,4 @@ test_files:
217
209
  - qed/projection.md
218
210
  - qed/selects.md
219
211
  - qed/updates.md
212
+ has_rdoc: