Class: Mochigome::Relation

Inherits:
Object
  • Object
show all
Defined in:
lib/relation.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(layers) ⇒ Relation

Returns a new instance of Relation.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/relation.rb', line 7

def initialize(layers)
  @model_graph = ModelGraph.new
  @spine_layers = layers
  @models = Set.new
  @model_join_stack = []
  @spine = []
  @join_path_descriptions = []

  @spine_layers.map(&:to_real_model).uniq.each do |m|
    join_to_model(m)
    @spine << m
  end
  @spine_layers.each{|m| select_model_id(m)}
end

Instance Attribute Details

#join_path_descriptionsObject (readonly)

Returns the value of attribute join_path_descriptions.



5
6
7
# File 'lib/relation.rb', line 5

def join_path_descriptions
  @join_path_descriptions
end

#spine_layersObject (readonly)

Returns the value of attribute spine_layers.



5
6
7
# File 'lib/relation.rb', line 5

def spine_layers
  @spine_layers
end

Instance Method Details

#apply_access_filter_func(func) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/relation.rb', line 125

def apply_access_filter_func(func)
  @models.each do |m|
    begin
      h = func.call(m)
      h.delete(:join_paths).try :each do |path|
        # FIXME: Eventually we need to support joins that
        # double back, if only for CanCan stuff, so get rid of this
        # uniq junk.
        join_on_path_thru path.uniq, "Access filter for #{m.name}"
      end
      if h[:condition]
        apply_condition h.delete(:condition)
      end
      unless h.empty?
        raise QueryError.new("Unknown assoc filter keys #{h.keys.inspect}")
      end
    rescue QueryError => e
      raise QueryError.new("Error checking access to #{m.name}: #{e}")
    end
  end
end

#apply_condition(cond) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/relation.rb', line 108

def apply_condition(cond)
  return unless cond
  if cond.is_a?(ActiveRecord::Base)
    cond = [cond]
  end
  if cond.is_a?(Array)
    # TODO: Should group by type and use IN expressions
    cond = cond.inject(nil) do |expr, obj|
      subexpr = obj.class.arel_primary_key.eq(obj.id)
      expr ? expr.or(subexpr) : subexpr
    end
  end

  join_to_expr_models(cond)
  @rel = @rel.where(cond)
end

#cloneObject



30
31
32
33
34
35
# File 'lib/relation.rb', line 30

def clone
  c = super
  c.instance_variable_set :@models, @models.clone
  c.instance_variable_set :@rel, @rel.clone if @rel
  c
end

#join_on_path(path, descrip = "Generic") ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/relation.rb', line 78

def join_on_path(path, descrip = "Generic")
  begin
    path = path.map(&:to_real_model).uniq
    join_to_model path.first
    join_descrips = [path.first.name]
    (0..(path.size-2)).map{|i| [path[i], path[i+1]]}.each do |src, tgt|
      if @models.include?(tgt)
        apply_condition(@model_graph.edge_condition(src, tgt))
        join_descrips << "*#{tgt.name}"
      else
        add_join_link(src, tgt)
        join_descrips << tgt.name
      end
    end
    @join_path_descriptions << "#{join_descrips.join("->")} (#{descrip})"
  rescue QueryError => e
    raise QueryError.new("Error pathing #{path.map(&:name).inspect}: #{e}")
  end
end

#join_on_path_thru(path, descrip = nil) ⇒ Object



69
70
71
72
73
74
75
76
# File 'lib/relation.rb', line 69

def join_on_path_thru(path, descrip = nil)
  full_path = @model_graph.path_thru(path)
  if full_path
    join_on_path(full_path, descrip || "Generic path thru #{path.map(&:name).inspect}")
  else
    raise QueryError.new("Cannot route thru #{path.map(&:name).inspect}")
  end
end

#join_to_model(model) ⇒ Object

Raises:



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
# File 'lib/relation.rb', line 37

def join_to_model(model)
  return if @models.include?(model)
  unless @rel
    @rel = @model_graph.relation_init(model)
    @models.add model
    return
  end

  # Route to it in as few steps as possible, closer to spine end if tie.
  best_path = nil
  (@spine.reverse + (@models.to_a - @spine).sort{|a,b| a.name <=> b.name}).each do |link_model|
    path = @model_graph.path_thru([link_model, model])
    if path && (best_path.nil? || path.size < best_path.size)
      best_path = path
    end
  end

  raise QueryError.new("No path to #{model} from #{@models.map(&:name).inspect}") unless best_path
  join_on_path best_path, "Best path to model #{model}"

  # Also use the conditions of any other unique path
  # TODO: Write a test that requires the below code to work
  @models.reject{|n| best_path.include?(n)}.each do |n|
    extra_path = @model_graph.path_thru([n, model])
    if extra_path
      unless best_path.all?{|m| extra_path.include?(m)}
        join_on_path extra_path, "Additional path to model #{model}"
      end
    end
  end
end

#select_expr(e) ⇒ Object



103
104
105
106
# File 'lib/relation.rb', line 103

def select_expr(e)
  join_to_expr_models(e)
  @rel = @rel.project(e)
end

#select_model_id(m) ⇒ Object



98
99
100
101
# File 'lib/relation.rb', line 98

def select_model_id(m)
  join_to_model(m)
  @rel = @rel.project(m.arel_primary_key.as("#{m.name}_id"))
end

#to_arelObject



22
23
24
# File 'lib/relation.rb', line 22

def to_arel
  @rel.try(:clone)
end

#to_sqlObject



26
27
28
# File 'lib/relation.rb', line 26

def to_sql
  @rel.try(:to_sql)
end