dm-is-reflective 1.2.0 → 1.3.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: a4a92f58af6a1c63b6bb445e06fdc573239ef558
4
- data.tar.gz: 4577c3ed73a122c4ddd09e62c8c0e64802e49540
3
+ metadata.gz: 9e05d37577ac7449933ee4680893d496c6ca138f
4
+ data.tar.gz: ff50451f50e9b1094db71272cbda92bb7476015a
5
5
  SHA512:
6
- metadata.gz: f271c3864848cfb098e7f7b32887831700617668c001a56f25f0be1a0497d0e488db5f8970ad7e96ee9ab9aecb4fd333b4fa71c70de48afd435929c266751f5c
7
- data.tar.gz: a139f550d6b69b36ddb824eec2ca59b4ec157d1289d39cdcc31f268a5d50a7abb90abcb1b48a05f7cdb0784edfb31b28440078adaef7f3b721ccb82d5f7e1944
6
+ metadata.gz: eb614bb45b425c7d5cbdbeb9cbaa8b8acb34ff4492af861c277cdfb60c116ec5560504dd10402dbb492dfa7f5adbc441588f40e0bd3fd59fef29e7535c47d858
7
+ data.tar.gz: 9bc150a2c0da4b77af03cca23f2f7c8148d7196689e714c4e8544e6fb38d506c2257dc5f5b25fca74b620e20893c4192eadbc8d2430f1f40ab0f2f8ecccbf5bd
data/CHANGES.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGES
2
2
 
3
+ ## dm-is-reflective 1.3.0, 2013-05-20
4
+
5
+ * Warn instead of raising a TypeError if a datatype cannot be found.
6
+ We fallback to use String.
7
+ * Now it works for multiple composite keys.
8
+ * If there's no key defined, it would pick the first unique index as the key.
9
+ * If a field name is conflicted, it would try to resolve it by appending a
10
+ underscore to the field name.
11
+
3
12
  ## dm-is-reflective 1.2.0, 2013-05-14
4
13
 
5
14
  * We got a bunch of internal renaming.
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "dm-is-reflective"
5
- s.version = "1.2.0"
5
+ s.version = "1.3.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Lin Jen-Shin (godfat)"]
9
- s.date = "2013-05-14"
9
+ s.date = "2013-05-20"
10
10
  s.description = "DataMapper plugin that helps you manipulate an existing database.\nIt creates mappings between existing columns and model's properties."
11
11
  s.email = ["godfat (XD) godfat.org"]
12
12
  s.executables = ["dm-is-reflective"]
@@ -114,12 +114,12 @@ module DmIsReflective::DataObjectsAdapter
114
114
  model.storage_names[:default] = storage
115
115
  scope.const_set(Inflector.classify(storage), model)
116
116
  model.__send__(:reflect, /.*/)
117
- model.finalize if model.respond_to?(:finalize)
118
117
  model
119
118
  end
120
119
 
121
120
  def reflective_lookup_primitive primitive
122
- raise TypeError.new("#{primitive} not found for #{self.class}")
121
+ warn "#{primitive} not found for #{self.class}: #{caller.inspect}"
122
+ String # falling back to the universal interface
123
123
  end
124
124
 
125
125
  def reflective_auto_load_adapter_extension
@@ -9,18 +9,50 @@ module DmIsReflective::MysqlAdapter
9
9
  private
10
10
  # construct needed table metadata
11
11
  def reflective_query_storage storage
12
- sql = <<-SQL
13
- SELECT column_name, column_default, is_nullable, data_type,
14
- character_maximum_length, column_key, extra
15
- FROM `information_schema`.`columns`
16
- WHERE `table_schema` = ? AND `table_name` = ?
12
+ sql_indices = <<-SQL
13
+ SELECT column_name, index_name, non_unique
14
+ FROM `information_schema`.`statistics`
15
+ WHERE table_schema = ? AND table_name = ?
16
+ SQL
17
+
18
+ sql_columns = <<-SQL
19
+ SELECT column_name, column_key, column_default, is_nullable,
20
+ data_type, character_maximum_length, extra, table_name
21
+ FROM `information_schema`.`columns`
22
+ WHERE table_schema = ? AND table_name = ?
17
23
  SQL
18
24
 
19
25
  # TODO: can we fix this shit in dm-mysql-adapter?
20
- path = options[:path] || options['path'] ||
21
- options[:database] || options['database']
26
+ path = (options[:path] || options['path'] ||
27
+ options[:database] || options['database']).sub('/', '')
28
+
29
+ indices =
30
+ select(Ext::String.compress_lines(sql_indices), path, storage).
31
+ group_by(&:column_name)
22
32
 
23
- select(Ext::String.compress_lines(sql), path.sub('/', ''), storage)
33
+ select(Ext::String.compress_lines(sql_columns), path, storage).
34
+ map do |column|
35
+ if idx = indices[column.column_name]
36
+ idx_uni, idx_com = idx.partition{ |i| i.non_unique == 0 }.map{ |i|
37
+ if i.empty?
38
+ nil
39
+ elsif i.size == 1
40
+ i.first.index_name.to_sym
41
+ else
42
+ i.map{ |ii| ii.index_name.to_sym }
43
+ end
44
+ }
45
+ else
46
+ idx_uni, idx_com = nil
47
+ end
48
+
49
+ column.instance_eval <<-RUBY
50
+ def unique_index; #{idx_uni.inspect}; end
51
+ def index ; #{idx_com.inspect}; end
52
+ RUBY
53
+
54
+ column
55
+ end
24
56
  end
25
57
 
26
58
  def reflective_field_name field
@@ -32,17 +64,24 @@ module DmIsReflective::MysqlAdapter
32
64
  end
33
65
 
34
66
  def reflective_attributes field, attrs = {}
35
- attrs[:serial] = true if field.extra == 'auto_increment'
36
- attrs[:key] = true if field.column_key == 'PRI'
67
+ attrs[:serial] = true if field.extra == 'auto_increment'
68
+
69
+ if field.column_key == 'PRI'
70
+ attrs[:key] = true
71
+ attrs[:unique_index] = :"#{field.table_name}_pkey"
72
+ else
73
+ attrs[:unique_index] = field.unique_index if field.unique_index
74
+ attrs[ :index] = field. index if field. index
75
+ end
37
76
 
38
- attrs[:allow_nil] = field.is_nullable == 'YES'
39
- attrs[:default] = field.column_default if
40
- field.column_default
77
+ attrs[:allow_nil] = field.is_nullable == 'YES'
78
+ attrs[:default] = field.column_default if
79
+ field.column_default
41
80
 
42
- attrs[:length] = field.character_maximum_length if
43
- field.character_maximum_length
81
+ attrs[:length] = field.character_maximum_length if
82
+ field.character_maximum_length
44
83
 
45
- attrs
84
+ attrs
46
85
  end
47
86
 
48
87
  def reflective_lookup_primitive primitive
@@ -13,28 +13,53 @@ module DmIsReflective::PostgresAdapter
13
13
 
14
14
  private
15
15
  def reflective_query_storage storage
16
- sql = <<-SQL
17
- SELECT column_name FROM "information_schema"."key_column_usage"
18
- WHERE table_schema = current_schema() AND table_name = ?
16
+ sql_indices = <<-SQL
17
+ SELECT a.attname, i.relname, ix.indisprimary, ix.indisunique
18
+ FROM pg_class t, pg_class i, pg_index ix, pg_attribute a
19
+ WHERE t.oid = ix.indrelid
20
+ AND i.oid = ix.indexrelid
21
+ AND a.attrelid = t.oid
22
+ AND a.attnum = ANY(ix.indkey)
23
+ AND t.relkind = 'r'
24
+ AND t.relname = ?
19
25
  SQL
20
26
 
21
- keys = select(Ext::String.compress_lines(sql), storage).to_set
22
-
23
- sql = <<-SQL
27
+ sql_columns = <<-SQL
24
28
  SELECT column_name, column_default, is_nullable,
25
29
  character_maximum_length, udt_name
26
- FROM "information_schema"."columns"
27
- WHERE table_schema = current_schema() AND table_name = ?
30
+ FROM "information_schema"."columns"
31
+ WHERE table_schema = current_schema() AND table_name = ?
28
32
  SQL
29
33
 
30
- select(Ext::String.compress_lines(sql), storage).map{ |struct|
31
- struct.instance_eval <<-RUBY
32
- def key?
33
- #{keys.member?(struct.column_name)}
34
- end
34
+ indices =
35
+ select(Ext::String.compress_lines(sql_indices), storage).
36
+ group_by(&:attname)
37
+
38
+ select(Ext::String.compress_lines(sql_columns), storage).map do |column|
39
+ if idx = indices[column.column_name]
40
+ is_key = !!idx.find{ |i| i.indisprimary }
41
+ idx_uni, idx_com = idx.partition{ |i| i.indisunique }.map{ |i|
42
+ if i.empty?
43
+ nil
44
+ elsif i.size == 1
45
+ i.first.relname.to_sym
46
+ else
47
+ i.map{ |ii| ii.relname.to_sym }
48
+ end
49
+ }
50
+ else
51
+ is_key = false
52
+ idx_uni, idx_com = nil
53
+ end
54
+
55
+ column.instance_eval <<-RUBY
56
+ def key? ; #{is_key} ; end
57
+ def unique_index; #{idx_uni.inspect}; end
58
+ def index ; #{idx_com.inspect}; end
35
59
  RUBY
36
- struct
37
- }
60
+
61
+ column
62
+ end
38
63
  end
39
64
 
40
65
  def reflective_field_name field
@@ -47,11 +72,16 @@ module DmIsReflective::PostgresAdapter
47
72
 
48
73
  def reflective_attributes field, attrs = {}
49
74
  # strip data type
50
- field.column_default.gsub!(/(.*?)::[\w\s]*/, '\1') if
51
- field.column_default
75
+ if field.column_default
76
+ field.column_default.gsub!(/(.*?)::[\w\s]*/, '\1')
77
+ end
52
78
 
53
79
  attrs[:serial] = true if field.column_default =~ /nextval\('\w+'\)/
54
- attrs[:key] = true if field.key?
80
+ attrs[:key] = true if field.key?
81
+
82
+ attrs[:unique_index] = field.unique_index if field.unique_index
83
+ attrs[ :index] = field. index if field. index
84
+
55
85
  attrs[:allow_nil] = field.is_nullable == 'YES'
56
86
  # strip string quotation
57
87
  attrs[:default] = field.column_default.gsub(/^'(.*?)'$/, '\1') if
@@ -4,8 +4,7 @@ module DmIsReflective::SqliteAdapter
4
4
 
5
5
  def storages
6
6
  sql = <<-SQL
7
- SELECT name
8
- FROM sqlite_master
7
+ SELECT name FROM sqlite_master
9
8
  WHERE type = 'table' AND NOT name = 'sqlite_sequence'
10
9
  SQL
11
10
 
@@ -14,7 +13,48 @@ module DmIsReflective::SqliteAdapter
14
13
 
15
14
  private
16
15
  def reflective_query_storage storage
17
- select('PRAGMA table_info(?)', storage)
16
+ sql_indices = <<-SQL
17
+ SELECT name, sql FROM sqlite_master
18
+ WHERE type = 'index' AND tbl_name = ?
19
+ SQL
20
+
21
+ indices = select(sql_indices, storage).inject({}){ |r, field|
22
+ columns = field.sql[/\(.+\)/].scan(/\w+/)
23
+ uniqueness = !!field.sql[/CREATE UNIQUE INDEX/]
24
+
25
+ columns.each{ |c|
26
+ type = if uniqueness then :unique_index else :index end
27
+ r[c] ||= {:unique_index => [], :index => []}
28
+ r[c][type] << field.name
29
+ }
30
+
31
+ r
32
+ }
33
+
34
+ select('PRAGMA table_info(?)', storage).map{ |field|
35
+ if idx = indices[field.name]
36
+ idx_uni, idx_com = [:unique_index, :index].map{ |type|
37
+ i = idx[type]
38
+ if i.empty?
39
+ nil
40
+ elsif i.size == 1
41
+ i.first.to_sym
42
+ else
43
+ i.map(&:to_sym)
44
+ end
45
+ }
46
+ else
47
+ idx_uni, idx_com = nil
48
+ end
49
+
50
+ field.instance_eval <<-RUBY
51
+ def table_name ; '#{storage}' ; end
52
+ def index ; #{idx_com.inspect}; end
53
+ def unique_index; #{idx_uni.inspect}; end
54
+ RUBY
55
+
56
+ field
57
+ }
18
58
  end
19
59
 
20
60
  def reflective_field_name field
@@ -27,9 +67,14 @@ module DmIsReflective::SqliteAdapter
27
67
 
28
68
  def reflective_attributes field, attrs = {}
29
69
  if field.pk != 0
30
- attrs[:key] = true
31
- attrs[:serial] = true
70
+ attrs[:key] = true
71
+ attrs[:serial] = true
72
+ attrs[:unique_index] = :"#{field.table_name}_pkey"
32
73
  end
74
+
75
+ attrs[:unique_index] = field.unique_index if field.unique_index
76
+ attrs[ :index] = field. index if field. index
77
+
33
78
  attrs[:allow_nil] = field.notnull == 0
34
79
  attrs[:default] = field.dflt_value[1..-2] if field.dflt_value
35
80
 
@@ -55,27 +55,41 @@ module DmIsReflective
55
55
 
56
56
  reflected = targets.each{ |target|
57
57
  case target
58
- when Regexp;
59
- break name if name.to_s =~ target
58
+ when Regexp;
59
+ break name if name.to_s =~ target
60
60
 
61
- when Symbol, String;
62
- break name if name == target.to_sym
61
+ when Symbol, String;
62
+ break name if name == target.to_sym
63
63
 
64
- when Class;
65
- break name if type == target
64
+ when Class;
65
+ break name if type == target
66
66
 
67
- else
68
- raise ArgumentError.new("invalid argument: #{target.inspect}")
67
+ else
68
+ raise ArgumentError.new("invalid argument: #{target.inspect}")
69
69
  end
70
70
  }
71
71
 
72
- property(reflected, type, attrs) if reflected.kind_of?(Symbol)
72
+ reflect_property(reflected, type, attrs) if
73
+ reflected.kind_of?(Symbol)
73
74
  }.compact
74
75
 
76
+ if key.empty? && k = properties.find{ |p| p.unique_index }
77
+ property k.name, k.primitive, :key => true
78
+ end
79
+
75
80
  finalize if respond_to?(:finalize)
76
81
  result
77
82
  end
78
83
 
84
+ def reflect_property reflected, type, attrs
85
+ property reflected, type, attrs
86
+ rescue ArgumentError => e
87
+ if e.message =~ /cannot be used as a property name/
88
+ reflect_property "#{reflected}_", type,
89
+ {:field => reflected.to_s}.merge(attrs)
90
+ end
91
+ end
92
+
79
93
  def to_source scope=nil
80
94
  <<-RUBY
81
95
  class #{scope}::#{name} < #{superclass}
@@ -7,6 +7,32 @@ require 'dm-migrations'
7
7
  require 'dm-is-reflective'
8
8
 
9
9
  module Abstract
10
+ class Cat
11
+ include DataMapper::Resource
12
+ property :id, Serial
13
+
14
+ belongs_to :user
15
+ belongs_to :super_user
16
+
17
+ property :user_id , Integer,
18
+ :unique_index => [:usu, :u]
19
+ property :super_user_id, Integer,
20
+ :unique_index => [:usu],
21
+ :index => [:su]
22
+ end
23
+
24
+ class Comment
25
+ include DataMapper::Resource
26
+ belongs_to :user, :required => false
27
+
28
+ property :id, Serial
29
+ property :title, String, :length => 50, :default => 'default title',
30
+ :allow_nil => false
31
+ property :body, Text
32
+
33
+ is :reflective
34
+ end
35
+
10
36
  class User
11
37
  include DataMapper::Resource
12
38
  has n, :comments
@@ -27,19 +53,8 @@ module Abstract
27
53
  is :reflective
28
54
  end
29
55
 
30
- class Comment
31
- include DataMapper::Resource
32
- belongs_to :user, :required => false
33
-
34
- property :id, Serial
35
- property :title, String, :length => 50, :default => 'default title',
36
- :allow_nil => false
37
- property :body, Text
38
-
39
- is :reflective
40
- end
41
-
42
- Tables = ['abstract_comments', 'abstract_super_users', 'abstract_users']
56
+ Tables = %w[abstract_cats abstract_comments
57
+ abstract_super_users abstract_users]
43
58
 
44
59
  AttrCommon = {:allow_nil => true}
45
60
  AttrCommonPK = {:serial => true, :key => true, :allow_nil => false}
@@ -54,40 +69,58 @@ end
54
69
  include Abstract
55
70
 
56
71
  shared :reflective do
72
+ def cat_fields
73
+ @cat_fields ||=
74
+ [[:id, DataMapper::Property::Serial,
75
+ {:unique_index => :abstract_cats_pkey}.merge(AttrCommonPK)],
76
+ [:super_user_id, Integer,
77
+ {:unique_index => :unique_abstract_cats_usu,
78
+ :index => :index_abstract_cats_su }.merge(AttrCommon)],
79
+ [:user_id , Integer,
80
+ {:unique_index => [:unique_abstract_cats_usu,
81
+ :unique_abstract_cats_u]}.merge(AttrCommon)]]
82
+ end
83
+
84
+ def comment_fields
85
+ @comment_fields ||= begin
86
+ [[:body , DataMapper::Property::Text , AttrText],
87
+ [:id , DataMapper::Property::Serial,
88
+ {:unique_index => :abstract_comments_pkey}.merge(AttrCommonPK)],
89
+
90
+ [:title , String ,
91
+ {:length => 50, :default => 'default title', :allow_nil => false}],
92
+
93
+ [:user_id, Integer ,
94
+ {:index => :index_abstract_comments_user}.merge(AttrCommon)]]
95
+ end
96
+ end
97
+
57
98
  def user_fields
99
+ @user_fields ||=
58
100
  [[:created_at, DateTime, AttrCommon],
59
- [:id, DataMapper::Property::Serial, AttrCommonPK],
101
+ [:id, DataMapper::Property::Serial,
102
+ {:unique_index => :abstract_users_pkey}.merge(AttrCommonPK)],
60
103
  [:login, String, {:length => 70}.merge(AttrCommon)],
61
104
  [:sig, DataMapper::Property::Text, AttrText]]
62
105
  end
63
106
 
64
- def comment_fields
65
- [[:body, DataMapper::Property::Text, AttrText],
66
- [:id, DataMapper::Property::Serial, AttrCommonPK],
67
- [:title, String, {:length => 50, :default => 'default title',
68
- :allow_nil => false}],
69
- [:user_id, Integer, AttrCommon]]
70
- end
71
-
72
- # there's differences between adapters
73
107
  def super_user_fields
74
- mysql = defined?(DataMapper::Adapters::MysqlAdapter) &&
75
- DataMapper::Adapters::MysqlAdapter
76
- case DataMapper.repository.adapter
77
- when mysql
78
- # Mysql couldn't tell it's boolean or tinyint
79
- [[:bool, Integer, AttrCommon],
80
- [:id, DataMapper::Property::Serial, AttrCommonPK]]
81
-
82
- else
83
- [[:bool, DataMapper::Property::Boolean, AttrCommon],
84
- [:id, DataMapper::Property::Serial, AttrCommonPK]]
108
+ @super_user_fields ||= begin
109
+ type = case DataMapper.repository.adapter.class.name
110
+ when 'DataMapper::Adapters::MysqlAdapter'
111
+ Integer
112
+ else
113
+ DataMapper::Property::Boolean
114
+ end
115
+ [[:bool, type, AttrCommon],
116
+ [:id , DataMapper::Property::Serial,
117
+ {:unique_index => :abstract_super_users_pkey}.merge(AttrCommonPK)]]
85
118
  end
86
119
  end
87
120
 
88
121
  before do
89
122
  @dm = setup_data_mapper
90
- [User, Comment, SuperUser].each(&:auto_migrate!)
123
+ [Cat, Comment, User, SuperUser].each(&:auto_migrate!)
91
124
  end
92
125
 
93
126
  def sort_fields fields
@@ -167,8 +200,9 @@ shared :reflective do
167
200
  key, value = i
168
201
  r[key] = value.sort_by{ |v| v.first.to_s }
169
202
  r
170
- }.should.eq('abstract_users' => user_fields ,
171
- 'abstract_comments' => comment_fields ,
203
+ }.should.eq('abstract_cats' => cat_fields,
204
+ 'abstract_comments' => comment_fields,
205
+ 'abstract_users' => user_fields,
172
206
  'abstract_super_users' => super_user_fields)
173
207
  end
174
208
 
@@ -215,7 +249,8 @@ shared :reflective do
215
249
  should 'auto_genclasses' do
216
250
  scope = new_scope
217
251
  @dm.auto_genclass!(:scope => scope).map(&:to_s).sort.should.eq \
218
- ["#{scope}::AbstractComment",
252
+ ["#{scope}::AbstractCat" ,
253
+ "#{scope}::AbstractComment" ,
219
254
  "#{scope}::AbstractSuperUser",
220
255
  "#{scope}::AbstractUser"]
221
256
 
@@ -1,4 +1,4 @@
1
1
 
2
2
  module DmIsReflective
3
- VERSION = '1.2.0'
3
+ VERSION = '1.3.0'
4
4
  end
@@ -19,5 +19,6 @@ describe 'mysql' do
19
19
  :database => 'dm_is_reflective')
20
20
  end
21
21
  end
22
+
22
23
  behaves_like :reflective
23
24
  end if defined?(DataMapper::Adapters::MysqlAdapter)
@@ -19,5 +19,6 @@ describe 'postgres' do
19
19
  :database => 'dm_is_reflective')
20
20
  end
21
21
  end
22
+
22
23
  behaves_like :reflective
23
24
  end if defined?(DataMapper::Adapters::PostgresAdapter)
@@ -6,5 +6,6 @@ describe 'sqlite' do
6
6
  def setup_data_mapper
7
7
  DataMapper.setup(:default, :adapter => 'sqlite', :database => ':memory:')
8
8
  end
9
+
9
10
  behaves_like :reflective
10
11
  end if defined?(DataMapper::Adapters::SqliteAdapter)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-is-reflective
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lin Jen-Shin (godfat)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-14 00:00:00.000000000 Z
11
+ date: 2013-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dm-core