mochigome 0.0.2

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 (39) hide show
  1. data/.autotest +14 -0
  2. data/Gemfile +16 -0
  3. data/Gemfile.lock +71 -0
  4. data/LICENSE +674 -0
  5. data/README.rdoc +11 -0
  6. data/Rakefile +74 -0
  7. data/TODO +6 -0
  8. data/lib/data_node.rb +106 -0
  9. data/lib/exceptions.rb +6 -0
  10. data/lib/mochigome.rb +7 -0
  11. data/lib/mochigome_ver.rb +3 -0
  12. data/lib/model_extensions.rb +211 -0
  13. data/lib/query.rb +199 -0
  14. data/test/app_root/app/controllers/application_controller.rb +2 -0
  15. data/test/app_root/app/controllers/owners_controller.rb +2 -0
  16. data/test/app_root/app/models/boring_datum.rb +3 -0
  17. data/test/app_root/app/models/category.rb +7 -0
  18. data/test/app_root/app/models/owner.rb +17 -0
  19. data/test/app_root/app/models/product.rb +21 -0
  20. data/test/app_root/app/models/sale.rb +9 -0
  21. data/test/app_root/app/models/store.rb +13 -0
  22. data/test/app_root/app/models/store_product.rb +11 -0
  23. data/test/app_root/config/boot.rb +130 -0
  24. data/test/app_root/config/database-pg.yml +8 -0
  25. data/test/app_root/config/database.yml +6 -0
  26. data/test/app_root/config/environment.rb +14 -0
  27. data/test/app_root/config/environments/test.rb +20 -0
  28. data/test/app_root/config/offroad.yml +6 -0
  29. data/test/app_root/config/preinitializer.rb +20 -0
  30. data/test/app_root/config/routes.rb +4 -0
  31. data/test/app_root/db/migrate/20110817163830_create_tables.rb +66 -0
  32. data/test/app_root/vendor/plugins/mochigome/init.rb +2 -0
  33. data/test/factories.rb +39 -0
  34. data/test/test.watchr +6 -0
  35. data/test/test_helper.rb +66 -0
  36. data/test/unit/data_node_test.rb +144 -0
  37. data/test/unit/model_extensions_test.rb +367 -0
  38. data/test/unit/query_test.rb +202 -0
  39. metadata +143 -0
@@ -0,0 +1,9 @@
1
+ class Sale < ActiveRecord::Base
2
+ has_mochigome_aggregations [:count]
3
+
4
+ belongs_to :store_product
5
+ has_one :store, :through => :store_product
6
+ has_one :product, :through => :store_product
7
+
8
+ validates_presence_of :store_product
9
+ end
@@ -0,0 +1,13 @@
1
+ class Store < ActiveRecord::Base
2
+ acts_as_mochigome_focus do |f|
3
+ f.type_name "Storefront"
4
+ end
5
+
6
+ belongs_to :owner
7
+ has_many :store_products
8
+ has_many :products, :through => :store_products
9
+ has_many :sales, :through => :store_products
10
+
11
+ validates_presence_of :name
12
+ validates_presence_of :owner
13
+ end
@@ -0,0 +1,11 @@
1
+ class StoreProduct < ActiveRecord::Base
2
+ # A many-to-many-join model
3
+ # Does not act as report focus
4
+
5
+ belongs_to :store
6
+ belongs_to :product
7
+ has_many :sales
8
+
9
+ validates_presence_of :store
10
+ validates_presence_of :product
11
+ end
@@ -0,0 +1,130 @@
1
+ # Don't change this file!
2
+ # Configure your app in config/environment.rb and config/environments/*.rb
3
+
4
+ RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
5
+
6
+ module Rails
7
+ class << self
8
+ def boot!
9
+ require 'rubygems'
10
+ Deprecate::skip = true
11
+ unless booted?
12
+ preinitialize
13
+ pick_boot.run
14
+ end
15
+ end
16
+
17
+ def booted?
18
+ defined? Rails::Initializer
19
+ end
20
+
21
+ def pick_boot
22
+ (vendor_rails? ? VendorBoot : GemBoot).new
23
+ end
24
+
25
+ def vendor_rails?
26
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
27
+ end
28
+
29
+ def preinitialize
30
+ load(preinitializer_path) if File.exist?(preinitializer_path)
31
+ end
32
+
33
+ def preinitializer_path
34
+ "#{RAILS_ROOT}/config/preinitializer.rb"
35
+ end
36
+ end
37
+
38
+ class Boot
39
+ def run
40
+ load_initializer
41
+ Rails::Initializer.run(:set_load_path)
42
+ end
43
+ end
44
+
45
+ class VendorBoot < Boot
46
+ def load_initializer
47
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
48
+ Rails::Initializer.run(:install_gem_spec_stubs)
49
+ Rails::GemDependency.add_frozen_gem_path
50
+ end
51
+ end
52
+
53
+ class GemBoot < Boot
54
+ def load_initializer
55
+ self.class.load_rubygems
56
+ load_rails_gem
57
+ require 'initializer'
58
+ end
59
+
60
+ def load_rails_gem
61
+ if version = self.class.gem_version
62
+ gem 'rails', version
63
+ else
64
+ gem 'rails'
65
+ end
66
+ rescue Gem::LoadError => load_error
67
+ if load_error.message =~ /Could not find RubyGem rails/
68
+ STDERR.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
69
+ exit 1
70
+ else
71
+ raise
72
+ end
73
+ end
74
+
75
+ class << self
76
+ def rubygems_version
77
+ Gem::RubyGemsVersion rescue nil
78
+ end
79
+
80
+ def gem_version
81
+ if defined? RAILS_GEM_VERSION
82
+ RAILS_GEM_VERSION
83
+ elsif ENV.include?('RAILS_GEM_VERSION')
84
+ ENV['RAILS_GEM_VERSION']
85
+ else
86
+ parse_gem_version(read_environment_rb)
87
+ end
88
+ end
89
+
90
+ def load_rubygems
91
+ min_version = '1.3.2'
92
+ require 'rubygems'
93
+ unless rubygems_version >= min_version
94
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
95
+ exit 1
96
+ end
97
+
98
+ rescue LoadError
99
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
100
+ exit 1
101
+ end
102
+
103
+ def parse_gem_version(text)
104
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
105
+ end
106
+
107
+ private
108
+ def read_environment_rb
109
+ File.read("#{RAILS_ROOT}/config/environment.rb")
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ class Rails::Boot
116
+ def run
117
+ load_initializer
118
+
119
+ Rails::Initializer.class_eval do
120
+ def load_gems
121
+ @bundler_loaded ||= Bundler.require :default, Rails.env
122
+ end
123
+ end
124
+
125
+ Rails::Initializer.run(:set_load_path)
126
+ end
127
+ end
128
+
129
+ # All that for this:
130
+ Rails.boot!
@@ -0,0 +1,8 @@
1
+ test:
2
+ adapter: postgresql
3
+ encoding: unicode
4
+ database: mochigome_test
5
+ pool: 5
6
+ username: david
7
+ password: foobar
8
+ min_messages: WARNING
@@ -0,0 +1,6 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+ verbosity: quiet
5
+ pool: 5
6
+ timeout: 5000
@@ -0,0 +1,14 @@
1
+ require File.join(File.dirname(__FILE__), 'boot')
2
+
3
+ Rails::Initializer.run do |config|
4
+ config.cache_classes = false
5
+ config.whiny_nils = true
6
+ config.action_controller.session = {:key => 'rails_session', :secret => 'd229e4d22437432705ab3985d4d246'}
7
+
8
+ if ENV['PSQL_TEST_MODE']
9
+ puts "Using postgresql for a test database"
10
+ config.database_configuration_file = "#{RAILS_ROOT}/config/database-pg.yml"
11
+ else
12
+ puts "Using sqlite for a test database"
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ config.cache_classes = true
2
+
3
+ # Log error messages when you accidentally call methods on nil.
4
+ config.whiny_nils = true
5
+
6
+ # Show full error reports and disable caching
7
+ config.action_controller.consider_all_requests_local = true
8
+ config.action_controller.perform_caching = false
9
+ config.action_view.cache_template_loading = true
10
+
11
+ # Disable request forgery protection in test environment
12
+ config.action_controller.allow_forgery_protection = false
13
+
14
+ # Tell Action Mailer not to deliver emails to the real world.
15
+ # The :test delivery method accumulates sent emails in the
16
+ # ActionMailer::Base.deliveries array.
17
+ config.action_mailer.delivery_method = :test
18
+
19
+ # Avoid excessively large log files
20
+ config.log_level = :error
@@ -0,0 +1,6 @@
1
+ # Configuration for Offroad
2
+ # :online_url: The URL for the regular online version of the app
3
+ # :app_name: The name of the application, to be used in helpful messages in the generated mirror data files.
4
+
5
+ :online_url: "http://example.com"
6
+ :app_name: Example Application
@@ -0,0 +1,20 @@
1
+ begin
2
+ require "rubygems"
3
+ require "bundler"
4
+ rescue LoadError
5
+ raise "Could not load the bundler gem. Install it with `gem install bundler`."
6
+ end
7
+
8
+ if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24")
9
+ raise RuntimeError, "Your bundler version is too old for Rails 2.3." +
10
+ "Run `gem install bundler` to upgrade."
11
+ end
12
+
13
+ begin
14
+ # Set up load paths for all bundled gems
15
+ ENV["BUNDLE_GEMFILE"] = File.expand_path("../../../../Gemfile", __FILE__)
16
+ Bundler.setup
17
+ rescue Bundler::GemNotFound
18
+ raise RuntimeError, "Bundler couldn't find some gems." +
19
+ "Did you run `bundle install`?"
20
+ end
@@ -0,0 +1,4 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.connect ':controller/:action/:id'
3
+ map.connect ':controller/:action/:id.:format'
4
+ end
@@ -0,0 +1,66 @@
1
+ class CreateTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :products do |t|
4
+ t.string :name
5
+ t.decimal :price
6
+ t.integer :category_id
7
+ t.boolean :categorized
8
+ t.timestamps
9
+ end
10
+
11
+ create_table :categories do |t|
12
+ t.string :name
13
+ t.timestamps
14
+ end
15
+
16
+ create_table :stores do |t|
17
+ t.string :name
18
+ t.integer :owner_id
19
+ t.timestamps
20
+ end
21
+
22
+ create_table :owners do |t|
23
+ t.string :first_name
24
+ t.string :last_name
25
+ t.date :birth_date
26
+ t.string :phone_number
27
+ t.string :email_address
28
+ t.timestamps
29
+ end
30
+
31
+ create_table :store_products do |t|
32
+ t.integer :store_id
33
+ t.integer :product_id
34
+ end
35
+
36
+ create_table :sales do |t|
37
+ t.integer :store_product_id
38
+ t.timestamps
39
+ end
40
+
41
+ # Used by ModelExtensionTest to create temporary models
42
+ create_table :fake do |t|
43
+ t.timestamps
44
+ t.string :a
45
+ t.string :b
46
+ t.integer :product_id
47
+ t.integer :x
48
+ end
49
+
50
+ # A model that has nothing to do with reports
51
+ create_table :boring_data do |t|
52
+ t.string :foo
53
+ t.string :bar
54
+ end
55
+ end
56
+
57
+ def self.down
58
+ drop_table :products
59
+ drop_table :categories
60
+ drop_table :stores
61
+ drop_table :owners
62
+ drop_table :store_products
63
+ drop_table :sales
64
+ drop_table :fake
65
+ end
66
+ end
@@ -0,0 +1,2 @@
1
+ # Pretend the mochigome gem is a plugin for the test environment
2
+ require "#{File.dirname(__FILE__)}/../../../../../lib/mochigome.rb"
data/test/factories.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'factory_girl'
2
+
3
+ FactoryGirl.define do
4
+ factory :product do
5
+ name 'LG Optimus V'
6
+ price 19.95
7
+ categorized true
8
+ end
9
+
10
+ factory :category do
11
+ name 'Gadgets'
12
+ end
13
+
14
+ factory :store do
15
+ name 'Best Buy'
16
+ owner
17
+ end
18
+
19
+ factory :owner do
20
+ first_name 'Thomas'
21
+ last_name 'Edison'
22
+ birth_date { 30.years.ago }
23
+ phone_number "800-555-1212"
24
+ email_address { "#{first_name}.#{last_name}@example.com".downcase }
25
+ end
26
+
27
+ factory :store_product do
28
+ store
29
+ product
30
+ end
31
+
32
+ factory :sale do
33
+ store_product
34
+ end
35
+
36
+ factory :boring_datum do
37
+ foo 'Bar'
38
+ end
39
+ end
data/test/test.watchr ADDED
@@ -0,0 +1,6 @@
1
+ require 'autowatchr'
2
+
3
+ Autowatchr.new(self) do |config|
4
+ config.test_re = '^%s.+/.+\_test.rb$' % config.test_dir
5
+ config.test_file = "unit/%s_test.rb"
6
+ end
@@ -0,0 +1,66 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+
3
+ prev_dir = Dir.getwd
4
+ begin
5
+ Dir.chdir("#{File.dirname(__FILE__)}/..")
6
+
7
+ begin
8
+ # Used when running test files directly
9
+ $LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"
10
+ require "#{File.dirname(__FILE__)}/app_root/config/environment"
11
+ rescue LoadError
12
+ # This is needed for root-level rake task 'test'
13
+ require "app_root/config/environment"
14
+ end
15
+ ensure
16
+ Dir.chdir(prev_dir)
17
+ end
18
+
19
+ require 'rubygems'
20
+ require 'minitest/autorun'
21
+ require 'redgreen'
22
+
23
+ module MiniTest
24
+ def self.filter_backtrace(backtrace)
25
+ backtrace = backtrace.select do |e|
26
+ if ENV['FULL_BACKTRACE']
27
+ true
28
+ else
29
+ !(e.include?("/ruby/") || e.include?("/gems/"))
30
+ end
31
+ end
32
+
33
+ common_prefix = nil
34
+ backtrace.each do |elem|
35
+ next if elem.start_with? "./"
36
+ if common_prefix
37
+ until elem.start_with? common_prefix
38
+ common_prefix.chop!
39
+ end
40
+ else
41
+ common_prefix = String.new(elem)
42
+ end
43
+ end
44
+
45
+ return backtrace.map do |element|
46
+ if element.start_with? common_prefix && common_prefix.size < element.size
47
+ element[common_prefix.size, element.size]
48
+ elsif element.start_with? "./"
49
+ element[2, element.size]
50
+ elsif element.start_with?(Dir.getwd)
51
+ element[Dir.getwd.size+1, element.size]
52
+ else
53
+ element
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+
60
+ require 'factories'
61
+ MiniTest::Unit::TestCase.send(:include, Factory::Syntax::Methods)
62
+
63
+ MiniTest::Unit::TestCase.add_setup_hook do
64
+ ActiveRecord::Migration.verbose = false
65
+ ActiveRecord::Migrator.migrate("#{Rails.root}/db/migrate") # Migrations in the test app
66
+ end
@@ -0,0 +1,144 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
2
+
3
+ describe Mochigome::DataNode do
4
+ it "is a Hash" do
5
+ assert Mochigome::DataNode.new(:foo, :bar).is_a?(Hash)
6
+ end
7
+
8
+ it "converts keys to symbols on creation" do
9
+ datanode = Mochigome::DataNode.new(:foo, :bar, [{"a" => 1}, {"b" => 2}, {:c => 3}])
10
+ assert_equal({:a => 1, :b => 2, :c => 3}, datanode)
11
+ end
12
+
13
+ it "converts its type name and name to strings on creation" do
14
+ datanode = Mochigome::DataNode.new(:foo, :bar)
15
+ assert_equal "foo", datanode.type_name
16
+ assert_equal "bar", datanode.name
17
+ end
18
+
19
+ describe "when created empty" do
20
+ before do
21
+ @datanode = Mochigome::DataNode.new(:data, :john_doe)
22
+ end
23
+
24
+ it "has no comment" do
25
+ assert_equal nil, @datanode.comment
26
+ end
27
+
28
+ it "can get a comment" do
29
+ @datanode.comment = "We are the Knights of Ni!"
30
+ assert_equal "We are the Knights of Ni!", @datanode.comment
31
+ end
32
+
33
+ it "can merge content from an array of single-item hashes" do
34
+ @datanode.merge! [{:foo => 42}, {"bar" => 84}]
35
+ assert_equal 42, @datanode[:foo]
36
+ assert_equal 84, @datanode[:bar]
37
+ end
38
+
39
+ it "can have child nodes added to the top layer" do
40
+ @datanode << Mochigome::DataNode.new(:subdata, :alice, {:a => 1, :b => 2})
41
+ @datanode << Mochigome::DataNode.new(:subdata, :bob, {:a => 3, :b => 4})
42
+ assert_equal 2, @datanode.children.size
43
+ assert_equal({:a => 1, :b => 2}, @datanode.children.first)
44
+ end
45
+
46
+ it "can accept an array of children" do
47
+ @datanode << [
48
+ Mochigome::DataNode.new(:subdata, :alice, {:a => 1, :b => 2}),
49
+ Mochigome::DataNode.new(:subdata, :bob, {:a => 3, :b => 4})
50
+ ]
51
+ assert_equal 2, @datanode.children.size
52
+ assert_equal({:a => 1, :b => 2}, @datanode.children.first)
53
+ end
54
+
55
+ it "can have items added at multiple layers" do
56
+ @datanode << Mochigome::DataNode.new(:subdata, :alice, {:a => 1, :b => 2})
57
+ @datanode.children.first << Mochigome::DataNode.new(:subsubdata, :spot, {:x => 10, :y => 20})
58
+ @datanode.children.first << Mochigome::DataNode.new(:subsubdata, :fluffy, {:x => 100, :y => 200})
59
+ assert_equal 1, @datanode.children.size
60
+ assert_equal 2, @datanode.children.first.size
61
+ assert_equal({:x => 10, :y => 20}, @datanode.children.first.children.first)
62
+ end
63
+
64
+ it "cannot accept children that are not DataNodes" do
65
+ assert_raises Mochigome::DataNodeError do
66
+ @datanode << {:x => 1, :y => 2}
67
+ end
68
+ end
69
+
70
+ it "returns the new child DataNode(s) from a concatenation" do
71
+ new_child = @datanode << Mochigome::DataNode.new(:subdata, :alce, {:a => 1})
72
+ assert_equal @datanode.children.first, new_child
73
+
74
+ new_children = @datanode << [
75
+ Mochigome::DataNode.new(:subdata, :bob, {:a => 1}),
76
+ Mochigome::DataNode.new(:subdata, :charlie, {:a => 2})
77
+ ]
78
+ assert_equal @datanode.children.drop(1), new_children
79
+ end
80
+ end
81
+
82
+ describe "when populated" do
83
+ before do
84
+ @datanode = Mochigome::DataNode.new(:corporation, :acme)
85
+ @datanode.comment = "Foo"
86
+ @datanode.merge! [{:id => 400}, {:apples => 1}, {:box_cutters => 2}, {:can_openers => 3}]
87
+ emp1 = @datanode << Mochigome::DataNode.new(:employee, :alice)
88
+ emp1.merge! [{:id => 500}, {:x => 9}, {:y => 8}, {:z => 7}, {:internal_type => "Cyborg"}]
89
+ emp2 = @datanode << Mochigome::DataNode.new(:employee, :bob)
90
+ emp2.merge! [{:id => 600}, {:x => 5}, {:y => 4}, {:z => 8734}, {:internal_type => "Human"}]
91
+ emp2 << Mochigome::DataNode.new(:pet, :lassie)
92
+
93
+ @titles = [
94
+ "corporation::name",
95
+ "corporation::id",
96
+ "corporation::apples",
97
+ "corporation::box_cutters",
98
+ "corporation::can_openers",
99
+ "employee::name",
100
+ "employee::id",
101
+ "employee::x",
102
+ "employee::y",
103
+ "employee::z",
104
+ "employee::internal_type",
105
+ "pet::name"
106
+ ]
107
+ end
108
+
109
+ it "can convert to an XML document with ids, names, types, and internal_types as attributes" do
110
+ # Why stringify and reparse it? So that we could switch to another XML generator.
111
+ doc = Nokogiri::XML(@datanode.to_xml.to_s)
112
+
113
+ comment = doc.xpath('/node[@type="Corporation"]/comment()').first
114
+ assert comment
115
+ assert comment.comment?
116
+ assert_equal "Foo", comment.content
117
+
118
+ assert_equal "400", doc.xpath('/node[@type="Corporation"]').first['id']
119
+ assert_equal "2", doc.xpath('/node/datum[@name="Box Cutters"]').first.content
120
+
121
+ emp_nodes = doc.xpath('/node/node[@type="Employee"]')
122
+ assert_equal "500", emp_nodes.first['id']
123
+ assert_equal "alice", emp_nodes.first['name']
124
+ assert_equal "bob", emp_nodes.last['name']
125
+ assert_equal "Cyborg", emp_nodes.first['internal_type']
126
+ assert_equal "4", emp_nodes.last.xpath('./datum[@name="Y"]').first.content
127
+ assert_equal "lassie", emp_nodes.last.xpath('node').first['name']
128
+ end
129
+
130
+ it "can convert to a flattened Ruport table" do
131
+ table = @datanode.to_flat_ruport_table
132
+ assert_equal @titles, table.column_names
133
+ assert_equal ['acme', 400, 1, 2, 3, 'alice', 500, 9, 8, 7, "Cyborg", nil], table.data[0].to_a
134
+ assert_equal ['acme', 400, 1, 2, 3, 'bob', 600, 5, 4, 8734, "Human", "lassie"], table.data[1].to_a
135
+ end
136
+
137
+ it "can convert to a flat array of arrays" do
138
+ a = @datanode.to_flat_arrays
139
+ assert_equal @titles, a[0]
140
+ assert_equal ['acme', 400, 1, 2, 3, 'alice', 500, 9, 8, 7, "Cyborg", nil], a[1]
141
+ assert_equal ['acme', 400, 1, 2, 3, 'bob', 600, 5, 4, 8734, "Human", "lassie"], a[2]
142
+ end
143
+ end
144
+ end