stratagem 0.1.7 → 0.1.8

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.
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