Class: Mochigome::Relation
- Inherits:
-
Object
- Object
- Mochigome::Relation
- Defined in:
- lib/relation.rb
Instance Attribute Summary collapse
-
#join_path_descriptions ⇒ Object
readonly
Returns the value of attribute join_path_descriptions.
-
#spine_layers ⇒ Object
readonly
Returns the value of attribute spine_layers.
Instance Method Summary collapse
- #apply_access_filter_func(func) ⇒ Object
- #apply_condition(cond) ⇒ Object
- #clone ⇒ Object
-
#initialize(layers) ⇒ Relation
constructor
A new instance of Relation.
- #join_on_path(path, descrip = "Generic") ⇒ Object
- #join_on_path_thru(path, descrip = nil) ⇒ Object
- #join_to_model(model) ⇒ Object
- #select_expr(e) ⇒ Object
- #select_model_id(m) ⇒ Object
- #to_arel ⇒ Object
- #to_sql ⇒ Object
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_descriptions ⇒ Object (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_layers ⇒ Object (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 |
#clone ⇒ Object
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
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_arel ⇒ Object
22 23 24 |
# File 'lib/relation.rb', line 22 def to_arel @rel.try(:clone) end |
#to_sql ⇒ Object
26 27 28 |
# File 'lib/relation.rb', line 26 def to_sql @rel.try(:to_sql) end |