joinfix 0.1.0
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/MIT-LICENSE +21 -0
- data/README +86 -0
- data/TEST_README +44 -0
- data/lib/joinfix/fixture.rb +29 -0
- data/lib/joinfix/fixtures.rb +271 -0
- data/lib/joinfix/fixtures_class.rb +102 -0
- data/lib/joinfix.rb +208 -0
- data/rails/README +182 -0
- data/rails/Rakefile +10 -0
- data/rails/app/controllers/application.rb +7 -0
- data/rails/app/helpers/application_helper.rb +3 -0
- data/rails/app/models/group.rb +4 -0
- data/rails/app/models/inner_child.rb +4 -0
- data/rails/app/models/nested_child.rb +4 -0
- data/rails/app/models/nested_parent.rb +4 -0
- data/rails/app/models/user.rb +4 -0
- data/rails/app/models/user_group.rb +5 -0
- data/rails/config/boot.rb +45 -0
- data/rails/config/database.yml +36 -0
- data/rails/config/environment.rb +60 -0
- data/rails/config/environments/development.rb +21 -0
- data/rails/config/environments/production.rb +18 -0
- data/rails/config/environments/test.rb +19 -0
- data/rails/config/routes.rb +23 -0
- data/rails/db/migrate/001_create_nested_parents.rb +12 -0
- data/rails/db/migrate/002_create_nested_children.rb +12 -0
- data/rails/db/migrate/003_create_inner_children.rb +12 -0
- data/rails/db/migrate/004_create_users.rb +11 -0
- data/rails/db/migrate/005_create_groups.rb +11 -0
- data/rails/db/migrate/006_create_user_groups.rb +12 -0
- data/rails/db/schema.rb +35 -0
- data/rails/doc/README_FOR_APP +2 -0
- data/rails/public/404.html +30 -0
- data/rails/public/500.html +30 -0
- data/rails/public/dispatch.cgi +10 -0
- data/rails/public/dispatch.fcgi +24 -0
- data/rails/public/dispatch.rb +10 -0
- data/rails/public/favicon.ico +0 -0
- data/rails/public/images/rails.png +0 -0
- data/rails/public/index.html +277 -0
- data/rails/public/javascripts/application.js +2 -0
- data/rails/public/javascripts/controls.js +833 -0
- data/rails/public/javascripts/dragdrop.js +942 -0
- data/rails/public/javascripts/effects.js +1088 -0
- data/rails/public/javascripts/prototype.js +2515 -0
- data/rails/public/robots.txt +1 -0
- data/rails/script/about +3 -0
- data/rails/script/breakpointer +3 -0
- data/rails/script/console +3 -0
- data/rails/script/destroy +3 -0
- data/rails/script/generate +3 -0
- data/rails/script/performance/benchmarker +3 -0
- data/rails/script/performance/profiler +3 -0
- data/rails/script/plugin +3 -0
- data/rails/script/process/inspector +3 -0
- data/rails/script/process/reaper +3 -0
- data/rails/script/process/spawner +3 -0
- data/rails/script/runner +3 -0
- data/rails/script/server +3 -0
- data/rails/test/fixtures/groups.yml +3 -0
- data/rails/test/fixtures/inner_children.yml +2 -0
- data/rails/test/fixtures/nested_children.yml +2 -0
- data/rails/test/fixtures/nested_parents.yml +33 -0
- data/rails/test/fixtures/user_groups.yml +0 -0
- data/rails/test/fixtures/users.yml +20 -0
- data/rails/test/test_helper.rb +29 -0
- data/rails/test/unit/group_test.rb +10 -0
- data/rails/test/unit/inner_child_test.rb +10 -0
- data/rails/test/unit/nested_child_test.rb +10 -0
- data/rails/test/unit/nested_parent_test.rb +24 -0
- data/rails/test/unit/user_group_test.rb +10 -0
- data/rails/test/unit/user_test.rb +12 -0
- data/test/belongs_to_test.rb +77 -0
- data/test/config.yml +5 -0
- data/test/fixtures/as_children.yml +0 -0
- data/test/fixtures/bt_children.yml +0 -0
- data/test/fixtures/bt_parents.yml +30 -0
- data/test/fixtures/habtm_children.yml +0 -0
- data/test/fixtures/habtm_children_habtm_parents.yml +0 -0
- data/test/fixtures/habtm_joins.yml +0 -0
- data/test/fixtures/habtm_parents.yml +18 -0
- data/test/fixtures/hm_children.yml +0 -0
- data/test/fixtures/hm_joins.yml +0 -0
- data/test/fixtures/hm_parents.yml +34 -0
- data/test/fixtures/ho_children.yml +0 -0
- data/test/fixtures/ho_parents.yml +14 -0
- data/test/fixtures/inner_children.yml +2 -0
- data/test/fixtures/nested_children.yml +0 -0
- data/test/fixtures/nested_parents.yml +33 -0
- data/test/fixtures/no_join_fixes.yml +4 -0
- data/test/fixtures/omap_no_join_fixes.yml +7 -0
- data/test/fixtures/polymorphic_children.yml +0 -0
- data/test/has_and_belongs_to_many_test.rb +72 -0
- data/test/has_many_test.rb +97 -0
- data/test/has_one_test.rb +56 -0
- data/test/joinfix_test.rb +287 -0
- data/test/joinfix_test_helper.rb +54 -0
- data/test/joinfix_test_suite.rb +10 -0
- data/test/nested_test.rb +70 -0
- metadata +189 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2006-2007, Regents of the University of Colorado.
|
2
|
+
Developer:: Simon Chiang, Biomolecular Structure Program
|
3
|
+
Support:: UCHSC School of Medicine Deans Academic Enrichment Fund
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
6
|
+
software and associated documentation files (the "Software"), to deal in the Software
|
7
|
+
without restriction, including without limitation the rights to use, copy, modify, merge,
|
8
|
+
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
9
|
+
to whom the Software is furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or
|
12
|
+
substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
18
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
19
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
20
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
21
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# = JoinFix
|
2
|
+
# A reflection-based solution to the fixture join problem.
|
3
|
+
#
|
4
|
+
# == Info
|
5
|
+
#
|
6
|
+
# Copyright (c) 2006-2007, Regents of the University of Colorado.
|
7
|
+
# Developer:: Simon Chiang, Biomolecular Structure Program
|
8
|
+
# Support:: UCHSC School of Medicine Deans Academic Enrichment Fund
|
9
|
+
# Licence:: MIT-Style
|
10
|
+
#
|
11
|
+
# == Usage
|
12
|
+
# Consider the following data model:
|
13
|
+
#
|
14
|
+
# class User < ActiveRecord::Base
|
15
|
+
# has_many :user_groups,
|
16
|
+
# has_many :groups, :through => :user_groups
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# class Group < ActiveRecord::Base
|
20
|
+
# has_many :group_users, :class_name => 'UserGroup'
|
21
|
+
# has_many :users, :through => :group_users
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class UserGroup < ActiveRecord::Base
|
25
|
+
# belongs_to :user
|
26
|
+
# belongs_to :group
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# Write your fixtures using the naming scheme you lay out in your models.
|
30
|
+
# Entries can be spread across multiple fixture files, or not. Joins between
|
31
|
+
# entries are written inline, either by referening the name of another entry
|
32
|
+
# or by defining the joined entry:
|
33
|
+
#
|
34
|
+
# [users.yml]
|
35
|
+
# bob:
|
36
|
+
# login: bob
|
37
|
+
# groups: admin_group # => reference to the 'admin_group' entry
|
38
|
+
#
|
39
|
+
# jane:
|
40
|
+
# id: 3 # => you can specify ids if you want
|
41
|
+
# login: jane
|
42
|
+
# groups: # => an array of joins
|
43
|
+
# - admin_group
|
44
|
+
# - workers:
|
45
|
+
# name: worker bees
|
46
|
+
#
|
47
|
+
# samantha:
|
48
|
+
# login: sam
|
49
|
+
# groups: # => inline definition of joined entries
|
50
|
+
# movers:
|
51
|
+
# name: movers
|
52
|
+
# shakers:
|
53
|
+
# name: shakers
|
54
|
+
#
|
55
|
+
# [groups.yml]
|
56
|
+
# admin_group: # => references can span files
|
57
|
+
# name: administrators
|
58
|
+
#
|
59
|
+
# Join entries implied in your definition, as in a has_and_belongs_to_many association, will
|
60
|
+
# be created and named by joining together the names of the parent and child, ordered
|
61
|
+
# by the '<' operator. For example, the users.yml and groups.yml fixtures will also produce:
|
62
|
+
#
|
63
|
+
# admin_group_bob # => join for bob and admin_group
|
64
|
+
# jane_workers # => join for jane and workers
|
65
|
+
# ...
|
66
|
+
#
|
67
|
+
# In your tests, require joinfix and use the fixtures exactly as you would normally.
|
68
|
+
# One gotcha (which really isn't a gotcha) -- you must be sure to name all the tables
|
69
|
+
# for which your fixtures create entries. In fact this is no different than normal,
|
70
|
+
# but it's easy to forget if you lump joins into one file.
|
71
|
+
#
|
72
|
+
# require 'joinfix'
|
73
|
+
#
|
74
|
+
# class UserTest < Test::Unit::TestCase
|
75
|
+
# fixtures :users, :groups, :user_groups # => got to name them all!
|
76
|
+
#
|
77
|
+
# def test_joinfix
|
78
|
+
# assert_equal "administrators", users(:bob).groups.first.name
|
79
|
+
# assert_equal 2, User.find_by_login("jane").groups.count
|
80
|
+
# assert_equal 3, UserGroup.find(user_groups(:jane_workers).id).user.id
|
81
|
+
# assert_equal users(:samantha), User.find_by_login("sam").groups.find_by_name("movers").users.first
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
#
|
85
|
+
#
|
86
|
+
|
data/TEST_README
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# = JoinFix Tests
|
2
|
+
# JoinFix does not have an active test suite that can be run when installed via RubyGems,
|
3
|
+
# because as you may expect, running the tests for joinfix requires that ActiveRecord can
|
4
|
+
# connect to a test database. Additionally, the tests depend on the 'gemdev' gem.
|
5
|
+
#
|
6
|
+
# If you want to run the tests, you need to do the following:
|
7
|
+
# * install gemdev ('gem install gemdev')
|
8
|
+
# * create the database and grant permissions to a test user
|
9
|
+
# * modify config.yml to reflect the database and user
|
10
|
+
#
|
11
|
+
# Execute these commands when logged into 'mysql' (assuming you're using mysql on localhost)
|
12
|
+
#
|
13
|
+
# create database joinfix;
|
14
|
+
# grant all on joinfix.* to 'ruby'@'localhost' identified by 'rubypass'
|
15
|
+
#
|
16
|
+
# Now you can run the test from the command line using:
|
17
|
+
#
|
18
|
+
# % rake test
|
19
|
+
#
|
20
|
+
# = Rails Tests
|
21
|
+
# JoinFix is intended to be used in Ruby on Rails. I've set up a rails project for
|
22
|
+
# testing, complete with models, migrations, fixtures, and the appropriate tests.
|
23
|
+
#
|
24
|
+
# The rails tests require a '_development' and '_test' database to connect to.
|
25
|
+
#
|
26
|
+
# To setup the databases:
|
27
|
+
# * create the databases and grant permissions to a test user
|
28
|
+
# * modify rails/config/database.yml to reflect the databases and user
|
29
|
+
# * migrate the table schema into the development database
|
30
|
+
#
|
31
|
+
# Execute these commands when logged into 'mysql' (assuming you're using mysql on localhost)
|
32
|
+
#
|
33
|
+
# create database joinfix_development;
|
34
|
+
# grant all on joinfix_development.* to 'ruby'@'localhost' identified by 'rubypass';
|
35
|
+
#
|
36
|
+
# create database joinfix_test;
|
37
|
+
# grant all on joinfix_test.* to 'ruby'@'localhost' identified by 'rubypass';
|
38
|
+
#
|
39
|
+
# Subsequently you need to migrate the database specification into these tables
|
40
|
+
# and run the tests. From the command line, in the rails directory:
|
41
|
+
#
|
42
|
+
# % rake db:migrate
|
43
|
+
# % rake test
|
44
|
+
#
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Additional classes to make Fixture behave like a Hash. Required for resolving joins.
|
2
|
+
#
|
3
|
+
# Just off of trivial because the internal data structure for Fixture can be a Hash or
|
4
|
+
# a YAML::Omap
|
5
|
+
class Fixture
|
6
|
+
def has_key?(key)
|
7
|
+
# should work for Hash and Omap
|
8
|
+
@fixture.keys.include?(key)
|
9
|
+
end
|
10
|
+
|
11
|
+
def each_pair(&block)
|
12
|
+
if @fixture.respond_to?(:each_pair)
|
13
|
+
# for Hash
|
14
|
+
@fixture.each_pair(&block)
|
15
|
+
else
|
16
|
+
# for Omap
|
17
|
+
@fixture.map { |k, v| yield(k,v) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_if(&block)
|
22
|
+
# should work for Hash and Omap
|
23
|
+
@fixture.delete_if(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
@fixture[key] = value
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
class Fixtures
|
2
|
+
attr_reader :join_configs, :templates
|
3
|
+
|
4
|
+
alias join_fix_original_insert_fixtures insert_fixtures
|
5
|
+
def insert_fixtures
|
6
|
+
Fixtures.template_all_loaded_fixtures if templates.nil?
|
7
|
+
join_fix_original_insert_fixtures
|
8
|
+
end
|
9
|
+
|
10
|
+
@@entry_stack = []
|
11
|
+
|
12
|
+
def template_fixtures
|
13
|
+
return unless templates.nil?
|
14
|
+
|
15
|
+
begin
|
16
|
+
configure
|
17
|
+
rescue
|
18
|
+
raise "\nError configuring fixtures for: #{@class_name}\n#{$!.message}"
|
19
|
+
end
|
20
|
+
|
21
|
+
extract_templates
|
22
|
+
|
23
|
+
begin
|
24
|
+
templates.each_pair do |entry_name, entry|
|
25
|
+
make_entry(entry_name, entry, true)
|
26
|
+
end
|
27
|
+
rescue
|
28
|
+
entry_str = "Entry:\n" + @@entry_stack.last.to_yaml
|
29
|
+
raise "Error making entry for: '#{@class_name}'\n#{entry_str}#{$!.message}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def offset
|
34
|
+
0
|
35
|
+
# FUTURE!: is this even needed? I don't think so given that all rows are
|
36
|
+
# deleted by delete_existing_fixtures... not sure.
|
37
|
+
# @connection. QUERY FOR NEXT ID
|
38
|
+
end
|
39
|
+
|
40
|
+
def each_pair(&block)
|
41
|
+
map { |entry_name, fixture| yield(entry_name, fixture) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def config
|
45
|
+
configure unless @config
|
46
|
+
@config
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def configure
|
52
|
+
klass = @class_name.constantize
|
53
|
+
file_base = @class_name.underscore.singularize
|
54
|
+
|
55
|
+
# default attributes, constructed from reflecting on the active record table
|
56
|
+
attributes = {}
|
57
|
+
klass.columns.each do |column|
|
58
|
+
next if JoinFix.preserves?(column.name)
|
59
|
+
attributes[column.name] = column.default
|
60
|
+
end
|
61
|
+
|
62
|
+
# default associations, constructed from reflecting on the active record class
|
63
|
+
associations = {}
|
64
|
+
klass.reflect_on_all_associations.each do |association|
|
65
|
+
begin
|
66
|
+
config = {:macro => association.macro}.merge(association.options)
|
67
|
+
|
68
|
+
# get the child table name either by reflecting on the association class
|
69
|
+
config[:class_name] ||= association.class_name
|
70
|
+
config[:table_name] = config[:class_name].constantize.table_name unless config[:polymorphic]
|
71
|
+
|
72
|
+
associations[association.name] = config
|
73
|
+
rescue
|
74
|
+
# known failure retrieving class name for has_many :through if the join model doesn't
|
75
|
+
# have a 'belongs_to' pointing to the source model
|
76
|
+
raise "Could not reflect on association: #{association.name}\n" +
|
77
|
+
"Check that your join models are properly configured.\n" +
|
78
|
+
$!.message
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# merge the defaults with the file configurations
|
83
|
+
@config = {
|
84
|
+
'class_name' => klass.class_name,
|
85
|
+
'table_name' => klass.table_name,
|
86
|
+
'attributes' => attributes,
|
87
|
+
'associations' => associations
|
88
|
+
# FUTURE! bring back if you start running methods
|
89
|
+
#'modules' => [JoinFix, "#{@class_name}Template"]
|
90
|
+
}.with_indifferent_access
|
91
|
+
|
92
|
+
@join_configs = {}.with_indifferent_access
|
93
|
+
parent_config = @config
|
94
|
+
associations.each_pair do |association, child_config|
|
95
|
+
# define shared parameters
|
96
|
+
join_config = {
|
97
|
+
:parent_table => parent_config[:table_name],
|
98
|
+
:child_table => child_config[:table_name],
|
99
|
+
:macro => child_config[:macro]
|
100
|
+
}.with_indifferent_access
|
101
|
+
|
102
|
+
case join_config[:macro].to_sym
|
103
|
+
when :belongs_to
|
104
|
+
if child_config[:polymorphic]
|
105
|
+
join_config[:polymorphic] = true
|
106
|
+
join_config[:associable] = association.to_s
|
107
|
+
join_config[:foreign_key] = child_config[:foreign_key] || "#{association}_id"
|
108
|
+
join_config.delete(:child_table) # not necessary, but reflects the fact that the polymorphic entry must specify this directly
|
109
|
+
else
|
110
|
+
join_config[:foreign_key] = child_config[:foreign_key] || "#{join_config[:child_table].singularize}_id"
|
111
|
+
end
|
112
|
+
when :has_one
|
113
|
+
join_config[:foreign_key] = child_config[:foreign_key] || "#{join_config[:parent_table].singularize}_id"
|
114
|
+
when :has_many
|
115
|
+
if child_config[:as]
|
116
|
+
join_config[:as] = child_config[:as]
|
117
|
+
join_config[:foreign_key] = child_config[:foreign_key] || "#{child_config[:as]}_id"
|
118
|
+
join_config[:parent_class] = parent_config[:class_name]
|
119
|
+
elsif child_config[:through]
|
120
|
+
join_config[:through] = child_config[:through]
|
121
|
+
join_config[:join_table] = associations[child_config[:through]][:table_name]
|
122
|
+
join_config[:foreign_key] = "#{join_config[:parent_table].singularize}_id"
|
123
|
+
join_config[:association_foreign_key] = "#{join_config[:child_table].singularize}_id"
|
124
|
+
else
|
125
|
+
join_config[:foreign_key] = child_config[:foreign_key] || "#{join_config[:parent_table].singularize}_id"
|
126
|
+
end
|
127
|
+
when :has_and_belongs_to_many
|
128
|
+
join_config[:join_table] = child_config[:join_table] || (join_config[:parent_table] < join_config[:child_table] ? "#{join_config[:parent_table]}_#{join_config[:child_table]}" : "#{join_config[:child_table]}_#{join_config[:parent_table]}") # echos the way join tables are guessed by ActiveRecord
|
129
|
+
join_config[:foreign_key] = child_config[:foreign_key] || "#{join_config[:parent_table].singularize}_id"
|
130
|
+
join_config[:association_foreign_key] = child_config[:association_foreign_key] || "#{join_config[:child_table].singularize}_id"
|
131
|
+
else
|
132
|
+
raise ArgumentError, "Unknown join type '#{join_config[:macro]}'."
|
133
|
+
end
|
134
|
+
|
135
|
+
join_config[:attributes] = Fixtures.fixture(join_config[:join_table]).config[:attributes] if join_config.has_key?(:join_table)
|
136
|
+
@join_configs[association] = join_config
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def extract_templates
|
141
|
+
# fixture is a template if it contains an association
|
142
|
+
@templates = {}
|
143
|
+
associations = config[:associations].keys
|
144
|
+
each do |entry_name, fixture|
|
145
|
+
#next if key.to_s !~ /[=|!]$/ # FUTURE! bring back if you start running methods
|
146
|
+
entry = fixture.to_hash
|
147
|
+
next if (entry.keys & associations).empty?
|
148
|
+
@templates[entry_name] = entry
|
149
|
+
end
|
150
|
+
delete_if do |entry_name, fixture|
|
151
|
+
@templates.has_key?(entry_name)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def make_entry(entry_name, entry, add_entry_on_complete)
|
156
|
+
@@entry_stack << entry.dup
|
157
|
+
|
158
|
+
attributes = config[:attributes]
|
159
|
+
#modules = config[:modules] # FUTURE! bring back if you start running methods
|
160
|
+
|
161
|
+
entry = attributes.merge(entry)
|
162
|
+
entry.extend JoinFix
|
163
|
+
entry.entry_name = entry_name
|
164
|
+
|
165
|
+
# FUTURE! bring back if you start running methods
|
166
|
+
#modules.each do |module_name|
|
167
|
+
# mod = get_module(module_name, module_options)
|
168
|
+
# template.extend(mod) if mod
|
169
|
+
#end
|
170
|
+
|
171
|
+
# extract templates for entries that will be joined to the current entry
|
172
|
+
# Associated entries are indicated by any key undeclared in attributes, that also does not
|
173
|
+
# match one of the reseved key patterns... ie 'id' or keys ending in '_id'. Additionally includes
|
174
|
+
# join references, which will all be arrays.
|
175
|
+
associated_entries = entry.extract_unless(attributes.keys) do |key, value|
|
176
|
+
JoinFix.preserves?(key) || key.kind_of?(Array)
|
177
|
+
end
|
178
|
+
|
179
|
+
# also extract templates for entries that have an array value. These entries indicate a set of
|
180
|
+
# associated entries that should each be joined to the current entry
|
181
|
+
associated_entries.merge( entry.extract_if { |key, value| value.kind_of?(Array) } )
|
182
|
+
associated_entries.each_pair do |association, association_templates|
|
183
|
+
# ensure that association_templates is an array of template
|
184
|
+
association_templates = [association_templates] unless association_templates.kind_of?(Array)
|
185
|
+
|
186
|
+
# translate the association configuration into a join configuration
|
187
|
+
join_config = join_configs[association]
|
188
|
+
unless join_config
|
189
|
+
raise ArgumentError, "Unknown association '#{association}' for '#{table_name}'."
|
190
|
+
end
|
191
|
+
|
192
|
+
join_table = join_config[:join_table]
|
193
|
+
macro = join_config[:macro]
|
194
|
+
|
195
|
+
# pull the polymorphic association class out of the entry -- polymorphic joins cannot determine
|
196
|
+
# beforehand what table they will join to. The associable_type MUST be specified in the fixture.
|
197
|
+
if join_config[:polymorphic] == true
|
198
|
+
join_config = join_config.dup
|
199
|
+
|
200
|
+
associable_type = "#{join_config[:associable]}_type"
|
201
|
+
unless entry.has_key?(associable_type)
|
202
|
+
raise ArgumentError, "Polymorphic type '#{associable_type}' missing in entry '#{entry_name}' for '#{table_name}'."
|
203
|
+
end
|
204
|
+
|
205
|
+
associable_class = entry[associable_type]
|
206
|
+
join_config[:child_class] = associable_class
|
207
|
+
join_config[:child_table] = associable_class.constantize.table_name
|
208
|
+
end
|
209
|
+
|
210
|
+
# raise an error if the macro doesn't allow for multiple joins, but multiple associated entries are specified
|
211
|
+
unless association_templates.length == 1 || JoinFix.macro_allows_multiple(macro)
|
212
|
+
raise ArgumentError, "Multiple associated entries specified for the single-association macro '#{macro}' in table '#{table_name}'."
|
213
|
+
end
|
214
|
+
|
215
|
+
association_templates.each do |template|
|
216
|
+
# template is a reference to another entry
|
217
|
+
template = {template => JoinFix.new(template)} if template.kind_of?(String)
|
218
|
+
|
219
|
+
# template is a fixture
|
220
|
+
template.each_pair do |child_name, child|
|
221
|
+
# generate the child entry
|
222
|
+
child_fixture = Fixtures.fixture(join_config[:child_table])
|
223
|
+
child = child_fixture.make_entry(child_name, child, false) unless child.kind_of?(JoinFix)
|
224
|
+
|
225
|
+
# generate the join entry
|
226
|
+
join = entry.send("join_#{macro}", child, join_config)
|
227
|
+
|
228
|
+
# add entries
|
229
|
+
# Note: join is sent through the make_entry machinery before adding... this ensures that the
|
230
|
+
# join entry will be processed according to the wizard modules specified for the join table.
|
231
|
+
child_fixture.add_entry(child_name, child)
|
232
|
+
Fixtures.fixture(join_table).make_entry(join_name(entry_name, child_name), join, true) if join
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# add the entry if flagged and return the entry
|
238
|
+
add_entry(entry_name, entry) if add_entry_on_complete
|
239
|
+
@@entry_stack.pop
|
240
|
+
entry
|
241
|
+
end
|
242
|
+
|
243
|
+
def add_entry(entry_name, entry)
|
244
|
+
# FUTURE! bring back if you start running methods
|
245
|
+
#return unless entry.addable?
|
246
|
+
|
247
|
+
# remove any attributes that are equal to their default
|
248
|
+
attributes = config[:attributes]
|
249
|
+
entry.delete_if do |attribute, value|
|
250
|
+
default = attributes[attribute]
|
251
|
+
value == default
|
252
|
+
end
|
253
|
+
|
254
|
+
# create a new fixture if one by the entry_name doesn't exist
|
255
|
+
existing = self[entry_name] ||= Fixture.new({}, config[:class_name])
|
256
|
+
|
257
|
+
# merge new data, checking for data collissions
|
258
|
+
entry.each_pair do |attribute, value|
|
259
|
+
if existing.has_key?(attribute) && existing[attribute] != value
|
260
|
+
raise ArgumentError,
|
261
|
+
"Data collision: #{@table_name}(:#{entry_name}).#{attribute}\n" +
|
262
|
+
"<#{existing[attribute]}> is not equal to\n<#{value}>"
|
263
|
+
end
|
264
|
+
existing[attribute] = value
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def join_name(entry_name, child_name)
|
269
|
+
entry_name < child_name ? "#{entry_name}_#{child_name}" : "#{child_name}_#{entry_name}"
|
270
|
+
end
|
271
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
class Fixtures
|
2
|
+
# Class methods to tie fixture joining into the standard process for generating fixtures.
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# Called by individual Fixtures instances just before they insert fixtures.
|
6
|
+
# This allows templating to occur at the correct time (ie after all fixtures
|
7
|
+
# have been loaded and configured) -- resolution of references naturally requires
|
8
|
+
# that all join tables are available.
|
9
|
+
def template_all_loaded_fixtures
|
10
|
+
all_loaded_fixtures.each_pair do |table_name, fixtures|
|
11
|
+
fixtures.template_fixtures
|
12
|
+
end
|
13
|
+
# note indexing and reference resolution must execute after all
|
14
|
+
# fixtures have been templated because you never know which fixture(s)
|
15
|
+
# will recieve new entries. Additionally, indexing and resolution must
|
16
|
+
# run separately, because reference resolution requires the target ids
|
17
|
+
# to be set.
|
18
|
+
all_loaded_fixtures.values.each {|fixtures| index_fixtures(fixtures) }
|
19
|
+
all_loaded_fixtures.values.each {|fixtures| resolve_references(fixtures)}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Retreives the fixture by the given table name. Raises an error if the fixture has
|
23
|
+
# not yet been loaded.
|
24
|
+
def fixture(table_name)
|
25
|
+
fixture = all_loaded_fixtures[table_name.to_s]
|
26
|
+
raise ArgumentError, "No fixture loaded for: #{table_name}" unless fixture
|
27
|
+
fixture
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def index_fixtures(fixtures)
|
33
|
+
# first find entries with an id and record the ids so that they will be skipped
|
34
|
+
skip_indicies = []
|
35
|
+
fixtures.each_pair do |name, fixture|
|
36
|
+
skip_indicies << fixture["id"].to_i if fixture.has_key?("id")
|
37
|
+
end
|
38
|
+
|
39
|
+
# next find and index entries that do not have an id defined
|
40
|
+
index = fixtures.offset
|
41
|
+
fixtures.each_pair do |name, fixture|
|
42
|
+
# skip entries that already have an id defined
|
43
|
+
next if fixture.has_key?("id")
|
44
|
+
|
45
|
+
# find the next available index
|
46
|
+
# note this must happen before the id assignment,
|
47
|
+
# in case index 1 is marked for skipping
|
48
|
+
while true
|
49
|
+
index += 1
|
50
|
+
break unless skip_indicies.include?(index)
|
51
|
+
end
|
52
|
+
|
53
|
+
fixture["id"] = index
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve_references(fixtures)
|
58
|
+
fixtures.each_pair do |name, fixture|
|
59
|
+
# search the fixture for join references
|
60
|
+
fixture.each_pair do |join_ref, join_name|
|
61
|
+
# next if the key isn't a join reference
|
62
|
+
next unless join_ref.kind_of?(Array)
|
63
|
+
|
64
|
+
foreign_key = join_ref.first
|
65
|
+
join_table_name = join_ref.last
|
66
|
+
|
67
|
+
# next if the foreign key is already defined
|
68
|
+
next if fixture.has_key?(foreign_key)
|
69
|
+
|
70
|
+
begin
|
71
|
+
# raise an error if the join table isn't loaded; the reference cannot be resolved
|
72
|
+
unless Fixtures.all_loaded_fixtures.has_key?(join_table_name)
|
73
|
+
raise ArgumentError, "The join table '#{join_table_name}' has not been loaded."
|
74
|
+
end
|
75
|
+
|
76
|
+
join_fixtures = Fixtures.all_loaded_fixtures[join_table_name]
|
77
|
+
|
78
|
+
# raise an error if the join entry isn't in the join table; the reference cannot be resolved
|
79
|
+
unless join_fixtures.has_key?(join_name)
|
80
|
+
raise ArgumentError, "The join entry '#{join_name}' doesn't exist in '#{join_table_name}'."
|
81
|
+
end
|
82
|
+
|
83
|
+
join_entry = join_fixtures[join_name]
|
84
|
+
|
85
|
+
# raise an exception if a join_id was not found
|
86
|
+
unless join_entry.has_key?("id")
|
87
|
+
raise ArgumentError, "No id present in join entry '#{join_name}'."
|
88
|
+
end
|
89
|
+
rescue
|
90
|
+
raise ArgumentError, "Cannot resolve reference '#{join_reference} => #{join_name}' in '#{@table_name}.#{name}'. #{$!}"
|
91
|
+
end
|
92
|
+
|
93
|
+
# set the join id
|
94
|
+
fixture[foreign_key] = join_entry["id"]
|
95
|
+
end
|
96
|
+
|
97
|
+
# delete the join references
|
98
|
+
fixture.delete_if {|key,value| key.kind_of?(Array) }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|