mochigome 0.1 → 0.1.1

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.
data/Rakefile CHANGED
@@ -58,8 +58,8 @@ gemspec = Gem::Specification.new do |s|
58
58
  s.authors = ["David Mike Simon"]
59
59
  s.email = "david.mike.simon@gmail.com"
60
60
  s.homepage = "http://github.com/DavidMikeSimon/mochigome"
61
- s.summary = "User-customizable report generator"
62
- s.description = "Report generator that uses your ActiveRecord associations"
61
+ s.summary = "The do-what-I-mean report generator"
62
+ s.description = "Report generator that graphs over ActiveRecord associations"
63
63
  s.files = `git ls-files .`.split("\n") - [".gitignore"]
64
64
  s.platform = Gem::Platform::RUBY
65
65
  s.require_path = 'lib'
@@ -69,6 +69,7 @@ gemspec = Gem::Specification.new do |s|
69
69
  s.add_dependency('ruport')
70
70
  s.add_dependency('nokogiri')
71
71
  s.add_dependency('rgl')
72
+ s.add_dependency('activerecord')
72
73
  end
73
74
 
74
75
  Gem::PackageTask.new(gemspec) do |pkg|
data/lib/mochigome_ver.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mochigome
2
- VERSION = "0.1"
2
+ VERSION = "0.1.1"
3
3
  end
data/lib/model_graph.rb CHANGED
@@ -4,28 +4,30 @@ require 'rgl/traversal'
4
4
  module Mochigome
5
5
  private
6
6
 
7
- module ModelGraph
8
- @@graphed_models = Set.new
9
- @@table_to_model = {}
10
- @@assoc_graph = RGL::DirectedAdjacencyGraph.new
11
- @@edge_relation_funcs = {}
12
- @@shortest_paths = {}
7
+ class ModelGraph
8
+ def initialize
9
+ @graphed_models = Set.new
10
+ @table_to_model = {}
11
+ @assoc_graph = RGL::DirectedAdjacencyGraph.new
12
+ @edge_relation_funcs = {}
13
+ @shortest_paths = {}
14
+ end
13
15
 
14
16
  # Take an expression and return a Set of all models it references
15
- def self.expr_models(e)
17
+ def expr_models(e)
16
18
  r = Set.new
17
19
  [:expr, :left, :right].each do |m|
18
20
  r += expr_models(e.send(m)) if e.respond_to?(m)
19
21
  end
20
22
  if e.respond_to?(:relation)
21
- model = @@table_to_model[e.relation.name]
23
+ model = @table_to_model[e.relation.name]
22
24
  raise ModelSetupError.new("Table->model lookup error") unless model
23
25
  r.add model
24
26
  end
25
27
  r
26
28
  end
27
29
 
28
- def self.relation_over_path(path, rel = nil)
30
+ def relation_over_path(path, rel = nil)
29
31
  real_path = path.map{|e| (e.real_model? ? e : e.model)}.uniq
30
32
  # Project ensures that we return a Rel, not a Table, even if path is empty
31
33
  rel ||= real_path.first.arel_table.project
@@ -35,12 +37,12 @@ module Mochigome
35
37
  rel
36
38
  end
37
39
 
38
- def self.relation_func(u, v)
39
- @@edge_relation_funcs[[u,v]] or
40
+ def relation_func(u, v)
41
+ @edge_relation_funcs[[u,v]] or
40
42
  raise QueryError.new "No assoc from #{u.name} to #{v.name}"
41
43
  end
42
44
 
43
- def self.path_thru(models)
45
+ def path_thru(models)
44
46
  update_assoc_graph(models)
45
47
  model_queue = models.dup
46
48
  path = [model_queue.shift]
@@ -51,7 +53,7 @@ module Mochigome
51
53
  real_src = src.real_model? ? src : src.model
52
54
  real_tgt = tgt.real_model? ? tgt : tgt.model
53
55
  unless real_src == real_tgt
54
- seg = @@shortest_paths[[real_src,real_tgt]]
56
+ seg = @shortest_paths[[real_src,real_tgt]]
55
57
  unless seg
56
58
  raise QueryError.new("No path: #{real_src.name} to #{real_tgt.name}")
57
59
  end
@@ -68,20 +70,42 @@ module Mochigome
68
70
  path
69
71
  end
70
72
 
71
- def self.update_assoc_graph(models)
73
+ private
74
+
75
+ def update_assoc_graph(models)
72
76
  model_queue = models.dup
73
77
  added_models = []
74
78
  until model_queue.empty?
75
79
  model = model_queue.shift
76
80
  next if model.is_a?(SubgroupModel)
77
- next if @@graphed_models.include? model
78
- @@graphed_models.add model
81
+ next if @graphed_models.include? model
82
+ @graphed_models.add model
79
83
  added_models << model
80
84
 
81
- if @@table_to_model.has_key?(model.table_name)
82
- raise ModelError.new("Table #{model.table_name} used twice")
85
+ if @table_to_model.has_key?(model.table_name)
86
+ # TODO Test this!
87
+ # Find the nearest common ancestor that derives from AR::Base
88
+ common = nil
89
+ [model, @table_to_model[model.table_name]].each do |tgt|
90
+ a = tgt.ancestors
91
+ a = a.select{|c| c.ancestors.include?(ActiveRecord::Base)}
92
+ if common.nil?
93
+ common = a
94
+ else
95
+ common = common & a
96
+ end
97
+ end
98
+
99
+ if common.empty? || common.first == ActiveRecord::Base
100
+ raise ModelSetupError.new(
101
+ "Unrelated models %s and %s both claim to use table %s" %
102
+ [model, @table_to_model[model.table_name], model.table_name]
103
+ )
104
+ end
105
+ @table_to_model[model.table_name] = common.first
106
+ else
107
+ @table_to_model[model.table_name] = model
83
108
  end
84
- @@table_to_model[model.table_name] = model
85
109
 
86
110
  model.reflections.
87
111
  reject{|name, assoc| assoc.through_reflection}.
@@ -91,25 +115,25 @@ module Mochigome
91
115
  next if assoc.options[:polymorphic] # TODO How to deal with these? Check for matching has_X assoc?
92
116
  foreign_model = assoc.klass
93
117
  edge = [model, foreign_model]
94
- next if @@assoc_graph.has_edge?(*edge) # Ignore duplicate assocs
95
- @@assoc_graph.add_edge(*edge)
96
- @@edge_relation_funcs[edge] = model.arelified_assoc(name)
97
- unless @@graphed_models.include?(foreign_model)
118
+ next if @assoc_graph.has_edge?(*edge) # Ignore duplicate assocs
119
+ @assoc_graph.add_edge(*edge)
120
+ @edge_relation_funcs[edge] = model.arelified_assoc(name)
121
+ unless @graphed_models.include?(foreign_model)
98
122
  model_queue.push(foreign_model)
99
123
  end
100
124
  end
101
125
  end
102
126
 
103
127
  added_models.each do |model|
104
- next unless @@assoc_graph.has_vertex?(model)
105
- path_tree = @@assoc_graph.bfs_search_tree_from(model).reverse
128
+ next unless @assoc_graph.has_vertex?(model)
129
+ path_tree = @assoc_graph.bfs_search_tree_from(model).reverse
106
130
  path_tree.depth_first_search do |tgt_model|
107
131
  next if tgt_model == model
108
132
  path = [tgt_model]
109
133
  while (parent = path_tree.adjacent_vertices(path.first).first)
110
134
  path.unshift parent
111
135
  end
112
- @@shortest_paths[[model,tgt_model]] = path
136
+ @shortest_paths[[model,tgt_model]] = path
113
137
  end
114
138
 
115
139
  # Use through reflections as a hint for preferred indirect paths
@@ -123,8 +147,8 @@ module Mochigome
123
147
  # FIXME Can't handle polymorphic through reflection
124
148
  end
125
149
  edge = [model,foreign_model]
126
- next if @@shortest_paths[edge].try(:size).try(:<, 3)
127
- @@shortest_paths[edge] = [model, join_model, foreign_model]
150
+ next if @shortest_paths[edge].try(:size).try(:<, 3)
151
+ @shortest_paths[edge] = [model, join_model, foreign_model]
128
152
  end
129
153
  end
130
154
  end
data/lib/query.rb CHANGED
@@ -3,7 +3,6 @@ module Mochigome
3
3
  def initialize(layer_types, options = {})
4
4
  # TODO: Validate layer types: not empty, AR, act_as_mochigome_focus
5
5
  @layer_types = layer_types
6
- @layers_path = ModelGraph.path_thru(@layer_types)
7
6
 
8
7
  @name = options.delete(:root_name).try(:to_s) || "report"
9
8
  @access_filter = options.delete(:access_filter) || lambda {|cls| {}}
@@ -28,10 +27,8 @@ module Mochigome
28
27
  agg_rel.join_on_path_thru([focus_model, data_model])
29
28
  agg_rel.apply_access_filter_func(@access_filter)
30
29
 
31
- focus_idx = @layers_path.index(focus_model)
32
- key_path = focus_idx ? @layers_path.take(focus_idx+1) : @layers_path
33
- key_path = key_path.select{|m| @layer_types.include?(m)}
34
- key_cols = key_path.map{|m| m.arel_primary_key}
30
+ key_models = @ids_rel.spine_layers_thru(focus_model)
31
+ key_cols = key_models.map{|m| m.arel_primary_key}
35
32
 
36
33
  agg_fields = data_model.mochigome_aggregation_settings.
37
34
  options[:fields].reject{|a| a[:in_ruby]}
@@ -86,7 +83,7 @@ module Mochigome
86
83
  Mochigome Version: #{Mochigome::VERSION}
87
84
  Report Generated: #{Time.now}
88
85
  Layers: #{@layer_types.map(&:name).join(" => ")}
89
- AR Path: #{@layers_path.map(&:name).join(" => ")}
86
+ AR Path: #{@ids_rel.full_spine_path.map(&:name).join(" => ")}
90
87
  eos
91
88
  root.comment.gsub!(/(\n|^) +/, "\\1")
92
89
 
@@ -191,11 +188,12 @@ module Mochigome
191
188
 
192
189
  class Relation
193
190
  def initialize(layers)
191
+ @model_graph = ModelGraph.new
194
192
  @spine_layers = layers
195
- @spine = ModelGraph.path_thru(layers) or
193
+ @spine = @model_graph.path_thru(layers) or
196
194
  raise QueryError.new("No valid path thru #{layers.inspect}") #TODO Test
197
195
  @models = Set.new @spine
198
- @rel = ModelGraph.relation_over_path(@spine)
196
+ @rel = @model_graph.relation_over_path(@spine)
199
197
 
200
198
  @spine_layers.each{|m| select_model_id(m)}
201
199
  end
@@ -208,6 +206,16 @@ module Mochigome
208
206
  @rel.to_sql
209
207
  end
210
208
 
209
+ def full_spine_path
210
+ @spine.dup
211
+ end
212
+
213
+ def spine_layers_thru(model)
214
+ r = @spine.take_while{|m| m != model}
215
+ r << model unless r.size == @spine.size
216
+ r.select{|m| @spine_layers.include? m}
217
+ end
218
+
211
219
  def clone
212
220
  c = super
213
221
  c.instance_variable_set :@spine, @spine.dup
@@ -222,7 +230,7 @@ module Mochigome
222
230
  # Route to it in as few steps as possible, closer to spine end if tie.
223
231
  best_path = nil
224
232
  (@spine.reverse + (@models.to_a - @spine)).each do |link_model|
225
- path = ModelGraph.path_thru([link_model, model])
233
+ path = @model_graph.path_thru([link_model, model])
226
234
  if path && (best_path.nil? || path.size < best_path.size)
227
235
  best_path = path
228
236
  end
@@ -233,7 +241,7 @@ module Mochigome
233
241
  end
234
242
 
235
243
  def join_on_path_thru(path)
236
- join_on_path(ModelGraph.path_thru(path).uniq)
244
+ join_on_path(@model_graph.path_thru(path).uniq)
237
245
  end
238
246
 
239
247
  def join_on_path(path)
@@ -248,7 +256,7 @@ module Mochigome
248
256
  end
249
257
 
250
258
  def select_expr(e)
251
- ModelGraph.expr_models(e).each{|m| join_to_model(m)}
259
+ @model_graph.expr_models(e).each{|m| join_to_model(m)}
252
260
  @rel = @rel.project(e)
253
261
  end
254
262
 
@@ -264,7 +272,7 @@ module Mochigome
264
272
  end
265
273
  end
266
274
 
267
- ModelGraph.expr_models(cond).each{|m| join_to_model(m)}
275
+ @model_graph.expr_models(cond).each{|m| join_to_model(m)}
268
276
  @rel = @rel.where(cond)
269
277
  end
270
278
 
@@ -289,7 +297,7 @@ module Mochigome
289
297
  raise QueryError.new("Can't join from #{src}, not available") unless
290
298
  @models.include?(src)
291
299
  return if @models.include?(tgt) # TODO Maybe still apply join conditions?
292
- @rel = ModelGraph.relation_func(src, tgt).call(@rel)
300
+ @rel = @model_graph.relation_func(src, tgt).call(@rel)
293
301
  @models.add tgt
294
302
  end
295
303
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mochigome
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- version: "0.1"
9
+ - 1
10
+ version: 0.1.1
10
11
  platform: ruby
11
12
  authors:
12
13
  - David Mike Simon
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2012-04-09 00:00:00 Z
18
+ date: 2012-04-10 00:00:00 Z
18
19
  dependencies:
19
20
  - !ruby/object:Gem::Dependency
20
21
  version_requirements: &id001 !ruby/object:Gem::Requirement
@@ -73,7 +74,21 @@ dependencies:
73
74
  prerelease: false
74
75
  type: :runtime
75
76
  name: rgl
76
- description: Report generator that uses your ActiveRecord associations
77
+ - !ruby/object:Gem::Dependency
78
+ version_requirements: &id005 !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ requirement: *id005
88
+ prerelease: false
89
+ type: :runtime
90
+ name: activerecord
91
+ description: Report generator that graphs over ActiveRecord associations
77
92
  email: david.mike.simon@gmail.com
78
93
  executables: []
79
94
 
@@ -159,6 +174,6 @@ rubyforge_project: "[none]"
159
174
  rubygems_version: 1.8.6
160
175
  signing_key:
161
176
  specification_version: 3
162
- summary: User-customizable report generator
177
+ summary: The do-what-I-mean report generator
163
178
  test_files: []
164
179