stratagem 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/Manifest +16 -4
  2. data/Rakefile +2 -2
  3. data/lib/bootstrap.rb +1 -0
  4. data/lib/stratagem/auto_mock/aquifer.rb +15 -7
  5. data/lib/stratagem/auto_mock/factory.rb +12 -2
  6. data/lib/stratagem/auto_mock/value_generator.rb +1 -1
  7. data/lib/stratagem/commands.rb +0 -1
  8. data/lib/stratagem/crawler/authentication.rb +116 -54
  9. data/lib/stratagem/crawler/form.rb +12 -0
  10. data/lib/stratagem/crawler/html_utils.rb +19 -7
  11. data/lib/stratagem/crawler/session.rb +156 -68
  12. data/lib/stratagem/crawler/site_model.rb +21 -7
  13. data/lib/stratagem/crawler/trace_utils.rb +3 -1
  14. data/lib/stratagem/extensions/trace_compression.rb +52 -0
  15. data/lib/stratagem/extensions.rb +1 -0
  16. data/lib/stratagem/framework_extensions/models/adapters/active_model/metadata.rb +3 -8
  17. data/lib/stratagem/framework_extensions/models/adapters/active_model/tracing.rb +21 -2
  18. data/lib/stratagem/framework_extensions/models/adapters/common/detect.rb +7 -0
  19. data/lib/stratagem/framework_extensions/models/adapters/common/extensions.rb +0 -0
  20. data/lib/stratagem/framework_extensions/models/adapters/common/metadata.rb +36 -0
  21. data/lib/stratagem/framework_extensions/models/adapters/common/tracing.rb +4 -0
  22. data/lib/stratagem/framework_extensions/models/adapters/{common → util}/authentication_metadata.rb +0 -0
  23. data/lib/stratagem/framework_extensions/models/annotations.rb +23 -1
  24. data/lib/stratagem/framework_extensions/models/metadata.rb +3 -3
  25. data/lib/stratagem/framework_extensions/models/tracing.rb +32 -10
  26. data/lib/stratagem/framework_extensions/models.rb +2 -2
  27. data/lib/stratagem/model/application.rb +8 -4
  28. data/lib/stratagem/model/components/base.rb +3 -0
  29. data/lib/stratagem/model/components/controller.rb +22 -23
  30. data/lib/stratagem/model/components/model.rb +3 -2
  31. data/lib/stratagem/model/components/reference.rb +24 -13
  32. data/lib/stratagem/model/components/route.rb +0 -3
  33. data/lib/stratagem/model/components/view.rb +1 -0
  34. data/lib/stratagem/model_builder.rb +9 -11
  35. data/lib/stratagem/site_crawler.rb +14 -19
  36. data/lib/stratagem.rb +1 -1
  37. data/spec/model/component_spec.rb +43 -0
  38. data/spec/model/components/view_spec.rb +43 -0
  39. data/spec/model/test_spec.rb +10 -0
  40. data/spec/samples/404.html.erb +30 -0
  41. data/spec/samples/_form.html.erb +8 -0
  42. data/spec/samples/index.html.erb +77 -0
  43. data/spec/samples/sample_model.rb +5 -0
  44. data/spec/samples/signup.html.erb +14 -0
  45. data/spec/scan/checks/email_address_spec.rb +24 -0
  46. data/spec/scan/checks/error_pages_spec.rb +22 -0
  47. data/stratagem.gemspec +7 -4
  48. metadata +50 -21
  49. data/lib/stratagem/commands/devel_crawl.rb +0 -27
  50. data/lib/stratagem/scan/checks/ssl/secure_login_page.rb +0 -19
  51. data/lib/stratagem/scan/checks/ssl/secure_login_submit.rb +0 -18
@@ -24,8 +24,6 @@ class CrawlError < StratagemError; end
24
24
  # end
25
25
  # end
26
26
 
27
- PHASES = [:unauthenticated,:authenticated]
28
-
29
27
  module Stratagem::Crawler::Session
30
28
  include ActionController::Integration::Runner
31
29
  include Stratagem::Crawler::HtmlUtils
@@ -36,38 +34,42 @@ module Stratagem::Crawler::Session
36
34
  Stratagem.logger.debug msg
37
35
  end
38
36
 
37
+ def parameter_types
38
+ @parameter_types ||= {}
39
+ end
40
+
41
+ def redirect_proc
42
+ @redirect_proc ||= Proc.new {|redirect_url| handle_redirect(redirect_url) }
43
+ end
44
+
39
45
  def crawler_session(application_model=nil)
40
- @model = application_model
41
- @model ||= Stratagem::ModelBuilder.new.run
42
- @redirect_proc = Proc.new {|redirect_url| handle_redirect(redirect_url) }
43
- @parameter_types = {} # routecontainer -> {:route_segment => Model, :route_segment => Model}
46
+ @application_model = application_model if application_model
44
47
  open_session do |session|
45
48
  @session = session
46
- phase(:unauthenticated)
47
49
  yield
48
50
  end
49
51
  end
50
52
 
51
53
  def application_model
52
- @model
54
+ @application_model ||= Stratagem::ModelBuilder.new.run
55
+ end
56
+
57
+ def aquifer(initial_capacity=6)
58
+ @aquifer ||= Stratagem::AutoMock::Aquifer.init(application_model).fill(initial_capacity)
53
59
  end
54
60
 
55
61
  def site_models
56
- @site_models ||= {} # builds models of the site for various phases of analysis, see PHASES
62
+ @site_models ||= []
57
63
  end
58
64
 
59
65
  def site_model
60
- raise Stratagem::Crawler::CrawlError.new("Phase not specified") unless @current_model
61
- @current_model
62
- end
63
-
64
- def phase(new_phase)
65
- @current_phase = new_phase
66
- @current_model = site_models[@current_phase] ||= Stratagem::Crawler::SiteModel.new
66
+ raise Stratagem::Crawler::CrawlError.new("Not within page set") unless site_models.last
67
+ site_models.last
67
68
  end
68
69
 
69
- def phases
70
- site_models
70
+ def page_set(name, &block)
71
+ site_models << Stratagem::Crawler::SiteModel.new(name)
72
+ yield site_model
71
73
  end
72
74
 
73
75
  def display
@@ -83,23 +85,28 @@ module Stratagem::Crawler::Session
83
85
  end
84
86
  end
85
87
 
86
- def crawl
88
+ def crawl(verbs=[:any,:get])
89
+ verbs = [verbs] unless verbs.kind_of?(Array)
90
+
87
91
  # grab all pages independently
88
- @model.routes.invalid.each {|route_container|
89
- puts "skipping invalid route #{route_container.route.to_s}"
90
- }
91
92
 
92
93
  authentication_controller = nil
93
- if (@current_phase == :authenticated)
94
- route = @model.routes.recognize(authentication.login_page)
95
- authentication_controller = route.controller
94
+ if (site_model.authentication)
95
+ route = application_model.routes.recognize(authentication.login_page)
96
+ authentication_controller = route.controller if route
96
97
  end
97
98
 
98
- @model.routes.each {|route_container|
99
+ application_model.routes.each {|route_container|
99
100
  if authentication_controller && route_container.controller && (route_container.controller.klass == authentication_controller.klass)
100
101
  log "Skipping authentication routes #{route_container.route.to_s}"
101
102
  else
102
- visit(route_container) unless @model.routes.invalid.include?(route_container)
103
+ if (application_model.routes.invalid.include?(route_container))
104
+ log "Skipping invalid route #{route_container.route.to_s}"
105
+ elsif (verbs.include?(route_container.route.conditions[:method]))
106
+ visit(route_container)
107
+ else
108
+ log "Skipping route with verb #{route_container.route.conditions[:method]} - #{route_container.route.to_s}"
109
+ end
103
110
  end
104
111
  }
105
112
 
@@ -114,40 +121,133 @@ module Stratagem::Crawler::Session
114
121
  private
115
122
 
116
123
  def visit(route_container)
117
- puts "visiting #{route_container.route}"
124
+ puts "Visiting #{route_container.route}"
118
125
  build_urls(route_container).each do |route_info|
119
126
  call_route(route_container, route_info)
120
127
  end
121
128
  end
122
129
 
123
- def call_route(route, route_info)
130
+ def call_route(route, route_info, track_invocations=true)
131
+ return if route_info.nil?
132
+
124
133
  puts 'CALLING: .'+route_info[:verb].downcase+". - "+route_info[:path]
125
134
  verb = route_info[:verb].downcase
126
135
  verb = 'get' if verb == '' || verb == 'any'
127
- case verb
128
- when 'get'
129
- do_get(route, route_info[:path])
130
- puts response.code if response
131
- when 'post'
132
- when 'put'
133
- when 'delete'
136
+
137
+ begin
138
+ invocations = model_invocations_for_request do
139
+ case verb
140
+ when 'get'
141
+ do_get(route, route_info[:path])
142
+ puts "\tresponse code: #{response.code}" if response
143
+ when 'post'
144
+ when 'put'
145
+ do_put(route, route_info[:path])
146
+ when 'delete'
147
+ else
148
+ raise "Unsupported verb: #{route[:verb]}"
149
+ end
150
+ end
151
+
152
+ if (response)
153
+ changes = detect_attribute_changes_in_models(invocations)
154
+ puts "\tfound #{invocations.size} invocations"
155
+ puts "\tchanges: #{changes.values.inspect}" if changes.size > 0
156
+ site_model.add(route, response, invocations, changes) {|redirect_url| redirect_proc.call(redirect_url) }
134
157
  else
135
- raise "Unsupported verb: #{route[:verb]}"
158
+ puts "ERROR: did not call #{route_info.inspect}"
159
+ end
160
+ rescue
161
+ puts $!.message
162
+ puts $!.backtrace
136
163
  end
137
164
  end
138
165
 
166
+ def do_get(route,path)
167
+ get path
168
+ end
169
+
170
+ def do_put(route,path)
171
+
172
+ # note: this should fail to generate anything meaningful, as we have not yet set up the parameters
173
+ put path
174
+
175
+ # let's find out what the method is looking for in the params object
176
+ params = map_models_to_attributes(infer_models_for_param_reads(route,controller.params.hash_reads))
177
+
178
+ # run again with the params
179
+ puts "PUTTING: #{path} with #{params.inspect}"
180
+
181
+ invocation_delta = model_invocations_for_request(:write) do
182
+ put path, params
183
+ end
184
+ end
185
+
186
+ def map_models_to_attributes(models)
187
+ result = {}
188
+ models.each {|param_read,model|
189
+ result[param_read] = aquifer.mock_attributes(model.klass)
190
+ }
191
+ result
192
+ end
193
+
194
+ def infer_models_for_param_reads(route,param_reads)
195
+ param_reads = param_reads.select {|read| read.to_s !~ /_id$/ }
196
+ param_reads -= [:action,'action',:controller,'controller',:id,'id',:format,'format']
197
+
198
+ result = {}
199
+
200
+ # resolve param reads to model types
201
+ # only support simple expected mappings at the moment
202
+ param_reads.each do |param_read|
203
+ class_name = param_read.to_s.camelize
204
+ if (class_name.singularize == class_name)
205
+ # this looks promising
206
+ model = application_model.models.find {|model| model.klass.name == class_name }
207
+ if (model)
208
+ puts "\t\tresolved param #{param_read} to #{model.klass.name}"
209
+ result[param_read] = model
210
+ end
211
+ end
212
+ end
213
+
214
+ result
215
+ end
216
+
217
+ def detect_attribute_changes_in_models(invocations)
218
+ changes = {}
219
+ invocations.select {|invocation| invocation.type == :write }.each do |invocation|
220
+ if (invocation.model_instance)
221
+ model = application_model.models.find {|m| m.klass == invocation.model_instance.class }
222
+
223
+ puts "\t\t#{invocation.model_class}.#{invocation.method} - #{invocation.model_instance}"
224
+ prior = aquifer.instances_of(invocation.model_instance.class).find {|m| m.id == invocation.model_instance.id }
225
+ post = invocation.model_instance
226
+ attribute_names = (prior.stratagem.attribute_names + prior.stratagem.foreign_keys)
227
+ changes[model] = attribute_names.select {|an|
228
+ begin
229
+ prior.send(an) != post.send(an)
230
+ rescue
231
+ puts "\t\t\t#{an} cannot be determined - #{$!.message}"
232
+ end
233
+ }
234
+ end
235
+ end
236
+ changes
237
+ end
238
+
139
239
  # Builds a list of string URLs for a given route. This is done
140
240
  # by replacing :xyz_id segments in the route with known values
141
241
  # from the well
142
242
  def build_urls(route_container)
143
243
  urls = []
144
244
  route = route_container.route
145
- parameter_types = (@parameter_types[route_container] ||= resolve_parameter_types(route_container))
146
- route_infos, params = build_url(route_container, parameter_types)
147
- puts "route: #{route_container.route.to_s} permutations:"
148
- route_infos.each do |info|
149
- puts "\t#{info[:path]}"
150
- end
245
+ param_types = (self.parameter_types[route_container] ||= resolve_parameter_types(route_container))
246
+ route_infos, params = build_url(route_container, param_types)
247
+ # puts "route: #{route_container.route.to_s} - #{parameter_types.inspect} - permutations:"
248
+ # route_infos.each do |info|
249
+ # puts "\t#{info[:path]}"
250
+ # end
151
251
  route_infos
152
252
  end
153
253
 
@@ -182,13 +282,14 @@ module Stratagem::Crawler::Session
182
282
  model = parameter_types[s]
183
283
  value = nil
184
284
  if (model)
185
- value = (Stratagem::AutoMock::Aquifer.instance.instances_of model).map {|inst|
285
+ if (aquifer.instances_of(model).size == 0)
286
+ aquifer.print
287
+ end
288
+ value = (aquifer.instances_of model).map {|inst|
186
289
  attr_name = s.gsub(/^:/, '').to_sym
187
290
  if inst.methods_include?(attr_name)
188
- puts "#{attr_name} is a method on the object"
189
291
  inst.send(attr_name)
190
292
  else
191
- puts "#{attr_name} is being mapped to the id on the object"
192
293
  inst.id
193
294
  end
194
295
  }
@@ -208,6 +309,7 @@ module Stratagem::Crawler::Session
208
309
  reqs = route.requirements.empty? ? "" : route.requirements.inspect
209
310
  url_permutations(segs) do |segments|
210
311
  path = segments.join('').gsub('(.:format)', '').gsub(/\/$/, '')
312
+ puts "\t\tbuilt url: #{path}"
211
313
  permutation = {:name => name, :verb => verb, :segs => segs, :reqs => reqs, :path => path}
212
314
  routes << permutation
213
315
  end
@@ -231,48 +333,34 @@ module Stratagem::Crawler::Session
231
333
  end
232
334
  end
233
335
 
234
- def do_get(route,path)
235
- begin
236
- get path
237
- site_model.add(route, response) {|redirect_url| @redirect_proc.call(redirect_url) }
238
- rescue
239
- puts $!.message
240
- puts path
241
- #puts $!.backtrace
242
- end
243
- end
244
-
245
336
  def resolve_parameter_types(route_container)
246
- puts "resolving parameter types for #{route_container.route}"
337
+ puts "\tresolving parameter types"
247
338
  resolved_parameters = {}
248
339
  route_infos, params = build_url(route_container, resolved_parameters)
249
340
  route_info = route_infos.first
250
341
  unknown_params = params.keys
251
- puts "unknown params: #{unknown_params}"
342
+ log "\tunknown params: #{unknown_params.inspect} - #{unknown_params.size}"
252
343
  progress = nil
253
344
  while ((unknown_params.size > 0) && (progress.nil? || (progress > 0)))
254
345
  progress = 0
255
346
 
256
- # p route_infos
257
- # puts "---"
347
+ puts "\tloading model invocations for request"
258
348
  delta = model_invocations_for_request do
259
- call_route(route_container, route_info)
349
+ call_route(route_container, route_info, false)
260
350
  end
261
351
 
262
- puts "delta - #{delta.size}"
263
- #p delta
352
+ puts "\tcalled route, found #{delta.size} invocations"
264
353
 
265
354
  unknown_params.clone.each do |key|
266
355
  value = params[key]
267
356
  value_s = params[key].map {|v| v.to_s }
268
357
  delta.each do |invocation|
269
- puts "#{route_info[:path]} - #{invocation.model_class.name} - #{invocation.method} - #{invocation.args.inspect} - #{value_s}"
270
-
358
+ # puts "\t#{route_info[:path]} - #{invocation.model_class.name} - #{invocation.method} - #{invocation.args.inspect} - #{value_s}"
271
359
  # TODO inspect is a hack, refactor
272
360
  if (invocation.args.include?(value.first)) || (invocation.args.inspect.include?('"'+value.first.to_s+'"'))
273
361
  # found match
274
362
 
275
- puts "\tresolved #{key} to #{invocation.model_class}"
363
+ puts "\t\tresolved #{key} to #{invocation.model_class}"
276
364
  unknown_params.delete(key)
277
365
  resolved_parameters[key] = invocation.model_class
278
366
  progress += 1
@@ -285,6 +373,7 @@ module Stratagem::Crawler::Session
285
373
  route_infos, params = build_url(route_container, resolved_parameters)
286
374
  route_info = route_infos.first
287
375
  end
376
+
288
377
  if (resolved_parameters.size > 0)
289
378
  resolved_parameters
290
379
  else
@@ -292,5 +381,4 @@ module Stratagem::Crawler::Session
292
381
  end
293
382
  end
294
383
 
295
-
296
384
  end
@@ -2,17 +2,26 @@ module Stratagem::Crawler
2
2
  class SiteModel
3
3
  include Stratagem::Crawler::HtmlUtils
4
4
 
5
- attr_reader :pages, :edges
5
+ attr_reader :pages, :edges, :name
6
+ attr_accessor :authentication
6
7
 
7
- def initialize
8
+ def initialize(name)
9
+ @name = name
8
10
  @pages = []
9
11
  @edges = []
10
12
  end
11
13
 
12
14
  def export
13
15
  {
16
+ :name => name,
14
17
  :pages => @pages.map {|page| page.export },
15
- :edges => @edges.map {|edge| edge.export }
18
+ :edges => @edges.map {|edge| edge.export },
19
+ :authentication => authentication.nil? ? nil : {
20
+ :success => authentication.success,
21
+ :login_page_external_id => authentication.login_page.object_id,
22
+ :response_page_external_id => authentication.response_page.object_id,
23
+ :ssl => authentication.ssl
24
+ },
16
25
  }
17
26
  end
18
27
 
@@ -20,8 +29,8 @@ module Stratagem::Crawler
20
29
  self.edges << Edge.new(from,to,type)
21
30
  end
22
31
 
23
- def add(route, response, &block)
24
- page = Page.new(self, response, &block)
32
+ def add(route, response, invocations=[], model_changes={}, &block)
33
+ page = Page.new(self, response, invocations, model_changes, &block)
25
34
  self.pages << page
26
35
  page
27
36
  end
@@ -66,8 +75,10 @@ module Stratagem::Crawler
66
75
  attr_accessor :redirected_to
67
76
  attr_accessor :document
68
77
 
69
- def initialize(site_model, response, &block)
78
+ def initialize(site_model, response, invocations, model_changes, &block)
70
79
  @site_model = site_model
80
+ @invocations = invocations
81
+ @model_changes = model_changes
71
82
  init(response, &block)
72
83
  end
73
84
 
@@ -82,7 +93,10 @@ module Stratagem::Crawler
82
93
  :path => path,
83
94
  :method => method,
84
95
  :redirected_to_page_external_id => redirected_to ? redirected_to.object_id : nil,
85
- :route_external_id => route.object_id
96
+ :route_external_id => route.object_id,
97
+ :references => @invocations.map {|i| i.to_reference.export },
98
+ :model_changes => Hash[@model_changes.map {|model,changes| [model.object_id, changes] }].to_json,
99
+ :parameters => response.request.parameters.to_json
86
100
  }
87
101
  end
88
102
 
@@ -1,10 +1,12 @@
1
1
  module Stratagem::Crawler
2
2
  module TraceUtils
3
- def model_invocations_for_request()
3
+ def model_invocations_for_request(type=nil)
4
4
  prior_invocations = ActiveRecord::Base.stratagem.invocations_audit.clone
5
5
  yield
6
6
  post_invocations = ActiveRecord::Base.stratagem.invocations_audit.clone
7
7
  delta = post_invocations - prior_invocations
8
+ delta = delta.select {|i| i.type == type } if (type)
9
+ delta
8
10
  end
9
11
  end
10
12
  end
@@ -0,0 +1,52 @@
1
+ # it's not elegant, but it compresses stack traces in a json friendly manner
2
+ class TraceDeflator
3
+ class << self
4
+ def create_word_map(compressed)
5
+ word_counts = {}
6
+ compressed.each do |column|
7
+ column.each do |row|
8
+ word_counts[row[0]] ||= 0
9
+ word_counts[row[0]] += 1
10
+ end
11
+ end
12
+
13
+ map = word_counts.keys.select {|key| (key.length) > 2 && (word_counts[key] > 1) }.sort {|a,b| word_counts[b] <=> word_counts[a]}
14
+ end
15
+
16
+ def sub_in_word_map(compressed,word_map)
17
+ compressed.map! do |column|
18
+ column.map! do |row|
19
+ sub_index = word_map.index(row[0])
20
+ sub_index ? [sub_index,row[1]] : row
21
+ end
22
+ end
23
+ end
24
+
25
+ def deflate(trace)
26
+ tokenized = trace.map {|s| s.split('/') }
27
+ longest = tokenized.inject(0) {|memo,a| a.size > memo ? a.size : memo }
28
+ tokenized.each {|a|
29
+ a << "" until a.size >= longest
30
+ }
31
+ compressed = tokenized.transpose.map do |column|
32
+ compressed_column = column.inject([]) {|new_col,col|
33
+ new_col << [col,0] if (new_col.size == 0)
34
+ prev = new_col.last
35
+ prev[0] == col ? prev[1] += 1 : new_col << [col,1]
36
+ new_col
37
+ }
38
+ end
39
+
40
+ word_map = create_word_map(compressed)
41
+ sub_in_word_map(compressed, word_map)
42
+
43
+ compressed = compressed.map {|column|
44
+ column.map {|row|
45
+ "#{row[1]}*#{row[0]}"
46
+ }.join("\n")
47
+ }
48
+
49
+ {:a => word_map, :b => compressed}
50
+ end
51
+ end
52
+ end
@@ -4,3 +4,4 @@ require 'stratagem/extensions/string'
4
4
  require 'stratagem/extensions/hash'
5
5
  require 'stratagem/extensions/object'
6
6
  require 'stratagem/extensions/module'
7
+ require 'stratagem/extensions/trace_compression'
@@ -13,7 +13,7 @@ module Stratagem::ApplicationExtensions::Models::Adapters::ActiveModel
13
13
  def relations(relation_type=nil) # :belongs_to, :has_many
14
14
  @relations ||= {}
15
15
  @relations[relation_type || :all] ||= model.reflect_on_all_associations(relation_type).map {|a|
16
- Stratagem::ApplicationExtensions::Models::Metadata::StratagemAssociation.new(a.name.to_sym, a.association_foreign_key.to_sym, a.klass, a.macro)
16
+ Stratagem::ApplicationExtensions::Models::Metadata::StratagemAssociation.new(a.name.to_sym, a.association_foreign_key.to_sym, a.klass, a.macro, a.options)
17
17
  }
18
18
  end
19
19
 
@@ -53,12 +53,7 @@ module Stratagem::ApplicationExtensions::Models::Adapters::ActiveModel
53
53
  end
54
54
 
55
55
  def attribute_names
56
- instance.attribute_names.map {|a| a.to_sym} - ignore_attributes
57
- end
58
-
59
- # junk attributes
60
- def ignore_attributes
61
- ["!".to_sym, :[]]
56
+ instance.attribute_names.map {|a| a.to_sym} - model.stratagem.ignore_attributes
62
57
  end
63
58
 
64
59
  # Attributes generally used by the persistence mechanism that should not be human writable
@@ -83,7 +78,7 @@ module Stratagem::ApplicationExtensions::Models::Adapters::ActiveModel
83
78
  column.type
84
79
  end
85
80
  else
86
- if (name =~ /password/)
81
+ if (name.to_s =~ /password/)
87
82
  :string
88
83
  else
89
84
  types = [:string, :boolean, :integer]
@@ -39,10 +39,29 @@ module Stratagem::ApplicationExtensions::Models::Adapters::ActiveModel
39
39
  # add logging of save methods
40
40
 
41
41
  def create_or_update(*args)
42
- path,action,line = stratagem.controller_trace(/active_record\/base\.rb/)
43
- stratagem.write_invocation(self, action.to_sym, args)
42
+ alternate_model = nil
43
+ path,action,line,trace,index = stratagem.controller_trace(/active_record\/base\.rb/)
44
+ if (index)
45
+ model_path,model_action,model_line = find_model_path(trace,index)
46
+ if (model_path)
47
+ puts "WRITE INVOCATION - USING: #{model_path},#{model_action},#{model_line}"
48
+ alternate_model = Stratagem::Model::Application.instance.models.find {|m| m.path == model_path }.klass
49
+ action = model_action
50
+ puts alternate_model
51
+ end
52
+ end
53
+
54
+ stratagem.write_invocation(self, alternate_model || self.class, action.to_sym, args)
44
55
  old_create_or_update(*args)
45
56
  end
57
+
58
+ def find_model_path(trace,controller_index)
59
+ 0.upto(controller_index) do |i|
60
+ line = trace[i]
61
+ return stratagem.parse_trace_line(line) if (line =~ /\/app\/models\//)
62
+ end
63
+ []
64
+ end
46
65
  end
47
66
  end
48
67
 
@@ -0,0 +1,7 @@
1
+ module Stratagem::ApplicationExtensions::Models::Adapters::Common
2
+ class Detect < Stratagem::ApplicationExtensions::Models::Detect
3
+ def self.supports?(model)
4
+ true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ module Stratagem::ApplicationExtensions::Models::Adapters::Common
2
+
3
+ # prefix method names with to avoid collision
4
+ class Metadata
5
+
6
+ attr_reader :model, :instance
7
+
8
+ def initialize(model)
9
+ @model = model
10
+ @instance = @model.new unless (@model == ActiveRecord::Base)
11
+ end
12
+
13
+ def attribute_names
14
+ @attribute_names ||= begin
15
+ names = model.new.methods.select {|m|
16
+ s = m.to_s
17
+ if (s.to_s =~ /=$/)
18
+ name = s.gsub(/=$/, '')
19
+ name.singularize == name
20
+ else
21
+ false
22
+ end
23
+ }.map {|m|
24
+ m.to_s.gsub(/=$/, '').to_sym
25
+ }
26
+ names - (model.stratagem.internal_attributes + model.stratagem.ignore_attributes + model.stratagem.relation_names)
27
+ end
28
+ end
29
+
30
+ # junk attributes
31
+ def ignore_attributes
32
+ @ignore_attributes ||= ["!", "[]", "===", "==", "=", "taguri"].map {|a| a.to_sym }
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,4 @@
1
+ module Stratagem::ApplicationExtensions::Models::Adapters::Common
2
+ module Tracing
3
+ end
4
+ end
@@ -1,8 +1,30 @@
1
1
  # Defines the stratagem namespace attached to the model
2
2
  module Stratagem::ApplicationExtensions::Models
3
- MethodInvocation = Struct.new(:method, :controller_path, :controller_action, :line_number, :model_instance, :model_class, :stack_trace, :args)
3
+ MethodInvocation = Struct.new(:method, :controller_path, :controller_action, :line_number, :model_instance, :model_class, :stack_trace, :args, :type)
4
4
  ValidatorDefinition = Struct.new(:validation, :field, :args, :model_class)
5
5
 
6
+ # Ability to convert a MethodInvocation object to a Reference object. Sort of a hack.
7
+ module MethodInvocationToReference
8
+ def to_reference
9
+ app = Stratagem::Model::Application.instance
10
+ model = model_class ? app.models.find {|model| model.klass == model_class } : nil
11
+ controller = controller_path ? app.controllers.find {|controller| controller.path == controller_path } : nil
12
+ Stratagem::Model::Component::Reference.new(
13
+ :from_component => controller,
14
+ :to_component => model,
15
+ :function => controller_action,
16
+ :method => method,
17
+ :line_number => line_number,
18
+ :reference_type => type,
19
+ :stack_trace => stack_trace
20
+ )
21
+ end
22
+ end
23
+ MethodInvocation.class_eval do
24
+ include MethodInvocationToReference
25
+ end
26
+
27
+
6
28
  class InstanceAnnotations
7
29
  include Mocking
8
30
 
@@ -1,7 +1,7 @@
1
1
 
2
2
  module Stratagem::ApplicationExtensions::Models
3
3
  module Metadata
4
- StratagemAssociation = Struct.new(:name, :foreign_key, :klass, :macro)
4
+ StratagemAssociation = Struct.new(:name, :foreign_key, :klass, :macro, :options)
5
5
 
6
6
  INSTANCE_ENUMERATION_METHODS = [:relations, :attribute_names, :ignore_attributes, :internal_attributes, :unaccessible_attributes, :invalid_columns, :exclude_attributes_for_mocking]
7
7
  INSTANCE_ENTITY_METHODS = [:attribute_type, :column_from_error, :authenticates?, :whitelists_attributes?, :blacklists_attributes?]
@@ -74,11 +74,11 @@ module Stratagem::ApplicationExtensions::Models
74
74
  memory << callback.send(method, *args) if callback.methods_include?(method) || callback.methods_include?(method.to_s)
75
75
  memory
76
76
  rescue
77
- puts $!.message
77
+ puts "error running callbacks: #{$!.message}"
78
78
  puts $!.backtrace
79
79
  end
80
80
  }
81
- (results || []).flatten.compact
81
+ (results || []).flatten.compact.uniq
82
82
  end
83
83
 
84
84
  end