Module: ADSL::Extract::Rails::CallbackChainSimulator

Includes:
Parser
Included in:
RailsExtractor
Defined in:
lib/adsl/extract/rails/callback_chain_simulator.rb

Instance Method Summary collapse

Instance Method Details

#halting_status_of(ast_node, is_action_body = false) ⇒ Object

returns true or false if the node will render or raise, or will not render or raise returns nil if the node may or may not render or raise



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/adsl/extract/rails/callback_chain_simulator.rb', line 12

def halting_status_of(ast_node, is_action_body = false)
  if ast_node.is_a?(ASTBlock)
    sub_statuses = ast_node.statements.map do |stmt|
      sub_rs = halting_status_of stmt, is_action_body
      return true if sub_rs
      sub_rs
    end
    return nil if sub_statuses.include? nil
    return false
  elsif ast_node.is_a?(ASTEither)
    sub_statuses = ast_node.blocks.map{ |block| halting_status_of block, is_action_body }
    return false if sub_statuses.uniq == [false]
    return true if sub_statuses.uniq == [true]
    return nil
  elsif ast_node.is_a?(ASTDummyStmt) and [:render, :raise].include?(ast_node.type)
    return false if ast_node.type == :render and is_action_body
    return true
  else
    return false
  end
end

#interrupt_callback_chain_on_render(root_block, action_name) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/adsl/extract/rails/callback_chain_simulator.rb', line 99

def interrupt_callback_chain_on_render(root_block, action_name)
  callbacks = split_into_callbacks root_block

  index = action_index = callbacks.index{ |callback_name, block| callback_name == action_name } 
  return if index.nil?
   
  # skip the action and proceed to the most prior before block
  until index < 0
    block = callbacks[index][1]
    render_halts = index != action_index

    case halting_status_of block, !render_halts
    when true
      # will halt execution after this callback is done
      callbacks = callbacks.first(index + 1)
    when nil
      # may render
      paths = split_into_paths_that_will_or_will_not_halt block, !render_halts
      what_happens_unless_renders = ASTBlock.new(:statements => callbacks[index+1..-1].map{ |c| c[1] })
      callbacks = callbacks.first(index + 1)
      callbacks.last[1] = ASTEither.new(:blocks => [
        paths[:will_halt],
        ASTBlock.new(:statements => [paths[:will_not_halt], *what_happens_unless_renders])
      ])
    else
      # doesn't render, all good!
    end
    index -= 1
  end

  root_block.statements = callbacks.map{ |name, block| block }
end

#split_into_callbacks(root_block) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/adsl/extract/rails/callback_chain_simulator.rb', line 81

def split_into_callbacks(root_block)
  pairs = []
  root_block.statements.reverse_each do |stmt|
    if stmt.is_a? ADSL::Parser::ASTDummyStmt
      pairs << [stmt.type, []]
    else
      pairs.last[1] << stmt
    end
  end
  pairs.reverse!
  pairs.length.times do |index|
    stmts = pairs[index][1]
    pairs[index][1] = (stmts.length == 1 ? stmts.first : ASTBlock.new(:statements => stmts.reverse))
  end
  
  pairs
end

#split_into_paths_that_will_or_will_not_halt(block, is_action_body = false) ⇒ Object

returns a hash of => block, :will_not_halt => block



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/adsl/extract/rails/callback_chain_simulator.rb', line 35

def split_into_paths_that_will_or_will_not_halt(block, is_action_body = false)
  case halting_status_of block, is_action_body
  when true;  return { :will_halt => block, :will_not_halt => nil }
  when false; return { :will_halt => nil,   :will_not_halt => block }
  end
 
  paths = { :will_halt => [], :will_not_halt => [] }
  block.statements.each do |stmt|
    if stmt.is_a?(ASTBlock) && halting_status_of(stmt, is_action_body).nil?
      possibilities = split_into_paths_that_will_or_will_not_halt stmt, is_action_body

      paths[:will_halt]     << possibilities[:will_halt]
      paths[:will_not_halt] << possibilities[:will_not_halt]
    elsif stmt.is_a?(ASTEither) && halting_status_of(stmt, is_action_body).nil?
      rendering_paths     = []
      not_rendering_paths = []
      
      stmt.blocks.each do |subblock|
        possibilities = split_into_paths_that_will_or_will_not_halt subblock, is_action_body
        rendering_paths     << possibilities[:will_halt]     unless possibilities[:will_halt].nil?
        not_rendering_paths << possibilities[:will_not_halt] unless possibilities[:will_not_halt].nil?
      end

      if rendering_paths.length == 1
        paths[:will_halt] << rendering_paths.first
      else
        paths[:will_halt] << ASTEither.new(:blocks => rendering_paths)
      end

      if not_rendering_paths.length == 1
        paths[:will_not_halt] << not_rendering_paths.first
      else
        paths[:will_not_halt] << ASTEither.new(:blocks => not_rendering_paths)
      end
    else
      paths[:will_halt]     << stmt
      paths[:will_not_halt] << stmt
    end
  end

  paths[:will_halt]     = ASTBlock.new(:statements => paths[:will_halt])
  paths[:will_not_halt] = ASTBlock.new(:statements => paths[:will_not_halt])

  paths
end