schofield 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +61 -0
- data/Gemfile.lock +4 -4
- data/README.rdoc +1 -1
- data/VERSION +1 -1
- data/lib/generators/schofield/association.rb +24 -24
- data/lib/generators/schofield/attribute.rb +49 -32
- data/lib/generators/schofield/attributes.rb +129 -128
- data/lib/generators/schofield/level.rb +86 -86
- data/lib/generators/schofield/levels.rb +21 -16
- data/lib/generators/schofield/navigation.rb +17 -0
- data/lib/generators/schofield/responses.rb +13 -13
- data/lib/generators/schofield/routes.rb +19 -14
- data/lib/generators/schofield/schofield_generator.rb +22 -18
- data/lib/generators/templates/controller.erb +7 -7
- data/lib/generators/templates/form.erb +11 -7
- data/lib/generators/templates/index.erb +2 -2
- data/lib/generators/templates/join_controller.erb +1 -1
- data/lib/generators/templates/model.erb +37 -39
- data/lib/generators/templates/show.erb +1 -1
- data/schofield.gemspec +10 -9
- data/spec/spec_helper.rb +1 -1
- metadata +20 -20
@@ -1,27 +1,27 @@
|
|
1
1
|
module Schofield
|
2
|
-
|
2
|
+
|
3
3
|
module Generators
|
4
4
|
|
5
5
|
class Level
|
6
|
-
|
6
|
+
|
7
7
|
COLUMNS_TO_IGNORE = %w( ^created_at$ ^updated_at$ _content_type$ _file_size$ _updated_at$ _type$ ^type$ ^crypted_password$ ^password_salt$ ^persistence_token$ ^perishable_token$ ^login_count$ ^failed_login_count$ ^last_request_at$ ^current_login_at$ ^last_login_at$ ^current_login_ip$ ^last_login_ip$ )
|
8
|
-
|
8
|
+
|
9
9
|
class << self; attr_reader :columns_to_ignore end
|
10
|
-
|
10
|
+
|
11
11
|
def self.tables_to_ignore= tables
|
12
12
|
@columns_to_ignore = ( COLUMNS_TO_IGNORE + tables.map{ |m| "^#{m.singularize}_id$" } )
|
13
13
|
end
|
14
|
-
|
15
|
-
|
16
|
-
attr_reader :name, :parent_associations, :attributes, :superclass
|
14
|
+
|
15
|
+
|
16
|
+
attr_reader :model, :name, :parent_associations, :attributes, :superclass
|
17
17
|
attr_accessor :child_associations, :subclasses
|
18
18
|
|
19
19
|
delegate :validations,
|
20
20
|
:validations?,
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
21
|
+
:attr_accessors,
|
22
|
+
:attr_accessors?,
|
23
|
+
:attr_accessibles,
|
24
|
+
:attr_accessibles?,
|
25
25
|
:attachments?,
|
26
26
|
:to_s_string,
|
27
27
|
:attached_files,
|
@@ -41,135 +41,135 @@ module Schofield
|
|
41
41
|
add_parent_associations
|
42
42
|
add_attributes
|
43
43
|
end
|
44
|
-
|
45
|
-
|
44
|
+
|
45
|
+
|
46
46
|
# Inheritence
|
47
|
-
|
47
|
+
|
48
48
|
def subclass?
|
49
49
|
@superclass.present?
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
def superclass?
|
53
53
|
@subclasses.any?
|
54
54
|
end
|
55
|
-
|
56
|
-
|
55
|
+
|
56
|
+
|
57
57
|
# Join table
|
58
|
-
|
58
|
+
|
59
59
|
def join?
|
60
60
|
@join ||= @model.columns.select { |c| c.name.match(/_id$/) }.length == 2 && @model.columns.select { |c| !%w( id created_at updated_at position ).include?(c.name) }.length == 2
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
def other_parent_name parent_name
|
64
64
|
@parent_associations.find{ |a| a.parent_name != parent_name }.parent_name
|
65
65
|
end
|
66
|
-
|
67
|
-
|
66
|
+
|
67
|
+
|
68
68
|
# Polymorphism
|
69
|
-
|
69
|
+
|
70
70
|
def polymorphic?
|
71
71
|
@polymorphic ||= polymorphic_name.present?
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
def polymorphic_name
|
75
75
|
match_data = nil
|
76
76
|
@polymorphic_name ||= @model.columns.find { |c| match_data = c.name.match(/^(.+)_type$/) } ? match_data[1] : nil
|
77
77
|
end
|
78
|
-
|
79
|
-
|
78
|
+
|
79
|
+
|
80
80
|
# Nesting
|
81
|
-
|
81
|
+
|
82
82
|
def nested?
|
83
|
-
@parent_associations.
|
83
|
+
@parent_associations.any?(&:nest?)
|
84
84
|
end
|
85
|
-
|
85
|
+
|
86
86
|
def nests?
|
87
|
-
@child_associations.
|
87
|
+
@child_associations.any?(&:nest?)
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
def nested_associations
|
91
91
|
if superclass? then []
|
92
92
|
else
|
93
93
|
associations = @child_associations + ( subclass? ? @superclass.child_associations : [] )
|
94
94
|
end
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
def nested_levels
|
98
98
|
nested_associations.select(&:nest?).map(&:child)
|
99
99
|
end
|
100
|
-
|
101
|
-
|
100
|
+
|
101
|
+
|
102
102
|
# Associations and cardinality
|
103
|
-
|
103
|
+
|
104
104
|
def belongs_to?
|
105
105
|
@parent_associations.any?
|
106
106
|
end
|
107
|
-
|
107
|
+
|
108
108
|
def belongs_to_one_names
|
109
109
|
@parent_associations.select(&:one_to_one?).map(&:parent_name)
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
def belongs_to_one?
|
113
|
-
@parent_associations.
|
113
|
+
@parent_associations.any?(&:one_to_one?)
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
def has_ones?
|
117
|
-
@child_associations.
|
117
|
+
@child_associations.any?(&:one_to_one?)
|
118
118
|
end
|
119
|
-
|
119
|
+
|
120
120
|
def has_manies?
|
121
|
-
@child_associations.
|
121
|
+
@child_associations.any?(&:one_to_many?)
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
def has_ones
|
125
125
|
@child_associations.select(&:one_to_one?).map(&:child)
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
def has_manies
|
129
129
|
@child_associations.select(&:one_to_many?).map(&:child)
|
130
130
|
end
|
131
|
-
|
132
|
-
|
131
|
+
|
132
|
+
|
133
133
|
# Combos
|
134
|
-
|
134
|
+
|
135
135
|
def routes?
|
136
136
|
!nested? && !superclass? && !belongs_to_one? && !join?
|
137
137
|
end
|
138
|
-
|
138
|
+
|
139
139
|
def controllers?
|
140
140
|
!superclass? && name != 'user' && !belongs_to_one?
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
def views?
|
144
144
|
controllers? && !join?
|
145
145
|
end
|
146
|
-
|
146
|
+
|
147
147
|
def models?
|
148
148
|
name != 'user'
|
149
149
|
end
|
150
|
-
|
150
|
+
|
151
151
|
def tables?
|
152
152
|
!belongs_to_one? && !superclass?
|
153
153
|
end
|
154
|
-
|
155
|
-
|
156
|
-
|
154
|
+
|
155
|
+
|
156
|
+
|
157
157
|
# Sortable
|
158
|
-
|
158
|
+
|
159
159
|
def sortable?
|
160
160
|
@sortable
|
161
161
|
end
|
162
|
-
|
163
|
-
|
162
|
+
|
163
|
+
|
164
164
|
# Form
|
165
|
-
|
165
|
+
|
166
166
|
def multipart?
|
167
|
-
attachments? || has_ones.
|
167
|
+
attachments? || has_ones.any?(&:attachments?)
|
168
168
|
end
|
169
|
-
|
170
|
-
|
169
|
+
|
170
|
+
|
171
171
|
# Association ancestry
|
172
|
-
|
172
|
+
|
173
173
|
def ancestry
|
174
174
|
level = self
|
175
175
|
nested_ins = []
|
@@ -189,99 +189,99 @@ module Schofield
|
|
189
189
|
nested_ins.reverse
|
190
190
|
end
|
191
191
|
|
192
|
-
|
192
|
+
|
193
193
|
# Form fields
|
194
|
-
|
194
|
+
|
195
195
|
def attribute_of_nesting_parent? attribute
|
196
196
|
attribute.model_name && parent_associations.select(&:nest?).map(&:parent_name).include?(attribute.model_name)
|
197
197
|
end
|
198
|
-
|
198
|
+
|
199
199
|
def polymorphic_attribute? attribute
|
200
200
|
polymorphic_name && attribute.model_name == polymorphic_name
|
201
201
|
end
|
202
|
-
|
202
|
+
|
203
203
|
def form_field? attribute
|
204
|
-
attribute.name
|
204
|
+
!%w( position slug ).include?(attribute.name) && attribute.name !~ /_fingerprint$/ && !polymorphic_attribute?(attribute) && !attribute_of_nesting_parent?(attribute)
|
205
205
|
end
|
206
|
-
|
207
|
-
|
208
|
-
|
206
|
+
|
207
|
+
|
208
|
+
|
209
209
|
private
|
210
|
-
|
210
|
+
|
211
211
|
def add_attributes
|
212
212
|
@attributes = Attributes.new
|
213
213
|
@model.columns.each do |column|
|
214
214
|
add_attribute(column) unless ignore?(column) || (polymorphic? && column.name =~ /^#{polymorphic_name}_id$/)
|
215
215
|
end
|
216
216
|
end
|
217
|
-
|
217
|
+
|
218
218
|
def add_attribute column
|
219
219
|
attribute = @attributes.new_attribute(column, belongs_to_one_names.include?(column.name.gsub(/_id$/, '')))
|
220
220
|
set_sortable if attribute.name == 'position'
|
221
221
|
end
|
222
|
-
|
222
|
+
|
223
223
|
def set_sortable
|
224
224
|
unless superclass?
|
225
225
|
@sortable = true
|
226
226
|
Levels.sortable = @name
|
227
227
|
end
|
228
228
|
end
|
229
|
-
|
229
|
+
|
230
230
|
# Nesting refers to nested routes, embedding refers to nested_attributes_for
|
231
231
|
# Assuming that a polymorphic model has no parents other than it's polymorphically associated parents
|
232
232
|
# If model has a one-to-one relationship with a parent, no nesting will occur as child will be embedded in parent
|
233
233
|
# Only allowing child to be nested under one parent or if polymorphic, the polymorphically associated parents
|
234
234
|
def add_parent_associations
|
235
|
-
|
235
|
+
|
236
236
|
if polymorphic?
|
237
|
-
|
237
|
+
|
238
238
|
answer = Responses.get("Which models are #{polymorphic_name}?")
|
239
239
|
answer.split(/[^\w]+/).map(&:underscore).each do |parent_name|
|
240
240
|
@parent_associations << Association.new(self, parent_name, one_to_one?(polymorphic_name), polymorphic_name)
|
241
241
|
end
|
242
|
-
|
242
|
+
|
243
243
|
else
|
244
|
-
|
244
|
+
|
245
245
|
any_one_to_ones = false
|
246
246
|
@model.columns.each do |column|
|
247
|
-
if (match_data = column.name.match(/^(.+)_id$/)).present?
|
247
|
+
if (match_data = column.name.match(/^(.+)_id$/)).present? && Levels.exists?(match_data[1])
|
248
248
|
parent_name = match_data[1]
|
249
249
|
is_one_to_one = one_to_one?(parent_name)
|
250
250
|
any_one_to_ones = true if is_one_to_one
|
251
251
|
@parent_associations << Association.new(self, parent_name, is_one_to_one)
|
252
252
|
end
|
253
253
|
end
|
254
|
-
|
254
|
+
|
255
255
|
if @parent_associations.any? && !any_one_to_ones
|
256
256
|
@parent_associations.length == 1 ? ask_if_nested : ask_where_to_nest
|
257
257
|
end
|
258
258
|
end
|
259
259
|
end
|
260
|
-
|
260
|
+
|
261
261
|
def one_to_one? parent_name
|
262
262
|
@one_to_one ||= %w( y yes ).include?(Responses.get("#{parent_name.gsub('_', ' ')} HAS ONE #{@human_name}? [N]").downcase)
|
263
263
|
end
|
264
|
-
|
264
|
+
|
265
265
|
def ask_if_nested
|
266
266
|
question = "Do you wish to nest #{@human_name} in #{@parent_associations.first.parent_name}? [yes]"
|
267
267
|
@parent_associations.first.nest = true unless %w( n no ).include?(Responses.get(question).downcase.strip)
|
268
|
-
end
|
269
|
-
|
268
|
+
end
|
269
|
+
|
270
270
|
def ask_where_to_nest
|
271
271
|
question = "Where do you wish to nest #{@human_name}? nowhere(n), "
|
272
272
|
question += @parent_associations.enum_with_index.map{ |n,i| "#{n.parent_name.gsub('_', ' ')}(#{i})" }.join(', ') + ' [n]'
|
273
273
|
answer = Responses.get(question)
|
274
274
|
@parent_associations[answer.to_i].nest = true unless answer == 'n' || answer.blank?
|
275
275
|
end
|
276
|
-
|
276
|
+
|
277
277
|
def ignore? column
|
278
278
|
self.class.columns_to_ignore.each do |string|
|
279
279
|
return true if column.name.match(/#{string}/)
|
280
280
|
end
|
281
281
|
column.primary
|
282
282
|
end
|
283
|
-
|
283
|
+
|
284
284
|
end
|
285
|
-
|
285
|
+
|
286
286
|
end
|
287
287
|
end
|
@@ -1,18 +1,19 @@
|
|
1
1
|
module Schofield
|
2
|
-
|
2
|
+
|
3
3
|
module Generators
|
4
4
|
|
5
5
|
class Levels
|
6
|
-
|
6
|
+
|
7
7
|
class << self; attr_reader :all, :hierarchy, :sortables end
|
8
|
-
|
8
|
+
|
9
9
|
@all = []
|
10
10
|
@names = {}
|
11
11
|
@sortables = {}
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
|
13
|
+
|
14
|
+
|
15
15
|
def self.models= models
|
16
|
+
@model_names = models.map{ |m| m.name.underscore }
|
16
17
|
models.each do |model|
|
17
18
|
if polymorphic_model?(model)
|
18
19
|
superclass = add_level(model)
|
@@ -25,28 +26,32 @@ module Schofield
|
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
28
|
-
|
29
|
+
|
29
30
|
def self.sortables
|
30
31
|
@sortables.keys
|
31
32
|
end
|
32
|
-
|
33
|
+
|
33
34
|
def self.sortable=sortable
|
34
35
|
@sortables[sortable] = true
|
35
36
|
end
|
36
|
-
|
37
|
+
|
37
38
|
def self.find name
|
38
39
|
@all[@names[name]]
|
39
40
|
end
|
40
41
|
|
41
|
-
|
42
|
-
|
42
|
+
def self.exists? name
|
43
|
+
@model_names.include?(name.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
|
43
48
|
private
|
44
|
-
|
45
|
-
|
49
|
+
|
50
|
+
|
46
51
|
def self.polymorphic_model? model
|
47
52
|
model.columns.find { |c| c.name == 'type' }.present?
|
48
53
|
end
|
49
|
-
|
54
|
+
|
50
55
|
def self.subclasses model
|
51
56
|
begin
|
52
57
|
answer = Responses.get "Which models extend #{model.name}?"
|
@@ -61,14 +66,14 @@ module Schofield
|
|
61
66
|
end
|
62
67
|
subclasses
|
63
68
|
end
|
64
|
-
|
69
|
+
|
65
70
|
def self.add_level model, superclass=nil
|
66
71
|
level = Level.new(model, superclass)
|
67
72
|
@all << level
|
68
73
|
@names[model.name.underscore] = @all.length - 1
|
69
74
|
level
|
70
75
|
end
|
71
|
-
|
76
|
+
|
72
77
|
end
|
73
78
|
end
|
74
79
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Schofield
|
2
|
+
|
3
|
+
module Generators
|
4
|
+
|
5
|
+
class Navigation
|
6
|
+
|
7
|
+
def self.generate
|
8
|
+
Levels.all.select(&:routes?).inject("\n %ul\n %li.toplevel") do |memo, level|
|
9
|
+
memo += "\n\n - if permitted_to?(:read, :#{level.name.pluralize})"
|
10
|
+
memo += "\n %li{ :class => nav_classes('#{level.model.name}') }= link_to '#{level.model.name.titleize.pluralize}', admin_#{level.name.pluralize}_path"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
module Schofield
|
2
|
-
|
2
|
+
|
3
3
|
module Generators
|
4
|
-
|
4
|
+
|
5
5
|
class Responses
|
6
|
-
|
6
|
+
|
7
7
|
class << self; attr_accessor :generator, :re_ask end
|
8
|
-
|
8
|
+
|
9
9
|
@file = File.join(Rails.root, 'tmp', 'answers.txt')
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
|
12
12
|
def self.get question
|
13
13
|
@question = question
|
14
14
|
if re_ask || (answer = past_answer).nil?
|
@@ -16,31 +16,31 @@ module Schofield
|
|
16
16
|
end
|
17
17
|
answer || ''
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def self.ask
|
21
21
|
re_ask = true
|
22
22
|
answer = generator.ask(@question)
|
23
23
|
@answers ||= {}
|
24
24
|
@answers[@question] = answer || ''
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def self.past_answer
|
28
28
|
self.past_answers[@question]
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
def self.past_answers
|
32
32
|
@answers ||= File.exists?(@file) ? File.open(@file, 'rb') { |f| Marshal.load(f) } : {}
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
def self.save
|
36
36
|
File.open(@file, 'wb') { |io| Marshal.dump(@answers, io) }
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
def self.say string
|
40
40
|
generator.say(string)
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
45
|
end
|
46
46
|
end
|