dm-is-reflective 1.2.0 → 1.3.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: 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