schofield 0.2.2 → 0.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.
- 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
|