flounder 0.18.3 → 1.0.0

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: 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: