aiwilliams-dataset 1.2.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/CHANGELOG +54 -0
- data/LICENSE +19 -0
- data/README +111 -0
- data/Rakefile +28 -0
- data/TODO +15 -0
- data/VERSION.yml +4 -0
- data/lib/dataset/base.rb +157 -0
- data/lib/dataset/collection.rb +19 -0
- data/lib/dataset/database/base.rb +30 -0
- data/lib/dataset/database/mysql.rb +34 -0
- data/lib/dataset/database/postgresql.rb +34 -0
- data/lib/dataset/database/sqlite3.rb +32 -0
- data/lib/dataset/extensions/cucumber.rb +20 -0
- data/lib/dataset/extensions/rspec.rb +21 -0
- data/lib/dataset/extensions/test_unit.rb +60 -0
- data/lib/dataset/instance_methods.rb +10 -0
- data/lib/dataset/load.rb +47 -0
- data/lib/dataset/record/fixture.rb +73 -0
- data/lib/dataset/record/meta.rb +58 -0
- data/lib/dataset/record/model.rb +50 -0
- data/lib/dataset/resolver.rb +110 -0
- data/lib/dataset/session.rb +51 -0
- data/lib/dataset/session_binding.rb +317 -0
- data/lib/dataset/version.rb +9 -0
- data/lib/dataset.rb +125 -0
- data/plugit/descriptor.rb +25 -0
- data/tasks/dataset.rake +19 -0
- metadata +83 -0
data/lib/dataset/load.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Dataset
|
2
|
+
class Load # :nodoc:
|
3
|
+
attr_reader :datasets, :dataset_binding, :helper_methods
|
4
|
+
|
5
|
+
def initialize(datasets, parent_binding)
|
6
|
+
@datasets = datasets
|
7
|
+
@dataset_binding = SessionBinding.new(parent_binding)
|
8
|
+
@helper_methods = Module.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(loaded_datasets, dataset_resolver)
|
12
|
+
(datasets - loaded_datasets).each do |dataset|
|
13
|
+
instance = dataset.new
|
14
|
+
instance.extend dataset_binding.record_methods
|
15
|
+
instance.extend dataset_binding.model_finders
|
16
|
+
used_datasets(dataset, dataset_resolver).each do |ds|
|
17
|
+
next unless ds.helper_methods
|
18
|
+
instance.extend ds.helper_methods
|
19
|
+
helper_methods.module_eval do
|
20
|
+
include ds.helper_methods
|
21
|
+
end
|
22
|
+
end
|
23
|
+
instance.load
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def used_datasets(dataset, dataset_resolver, collector = [])
|
28
|
+
dataset.used_datasets.each do |used|
|
29
|
+
ds = dataset_resolver.resolve(used)
|
30
|
+
used_datasets(ds, dataset_resolver, collector)
|
31
|
+
collector << ds
|
32
|
+
end if dataset.used_datasets
|
33
|
+
collector << dataset
|
34
|
+
collector.uniq
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Reload # :nodoc:
|
39
|
+
attr_reader :dataset_binding, :load
|
40
|
+
delegate :datasets, :helper_methods, :to => :load
|
41
|
+
|
42
|
+
def initialize(load)
|
43
|
+
@load = load
|
44
|
+
@dataset_binding = SessionBinding.new(@load.dataset_binding)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'active_record/fixtures'
|
2
|
+
|
3
|
+
module Dataset
|
4
|
+
module Record # :nodoc:
|
5
|
+
|
6
|
+
class Fixture # :nodoc:
|
7
|
+
attr_reader :meta, :symbolic_name, :session_binding
|
8
|
+
|
9
|
+
def initialize(meta, attributes, symbolic_name, session_binding)
|
10
|
+
@meta = meta
|
11
|
+
@attributes = attributes.stringify_keys
|
12
|
+
@symbolic_name = symbolic_name || object_id
|
13
|
+
@session_binding = session_binding
|
14
|
+
|
15
|
+
install_default_attributes!
|
16
|
+
end
|
17
|
+
|
18
|
+
def create
|
19
|
+
record_class.connection.insert_fixture to_fixture, meta.table_name
|
20
|
+
id
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
@attributes['id']
|
25
|
+
end
|
26
|
+
|
27
|
+
def record_class
|
28
|
+
meta.record_class
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_fixture
|
32
|
+
::Fixture.new(to_hash, meta.class_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_hash
|
36
|
+
hash = @attributes.dup
|
37
|
+
hash[meta.inheritance_column] = meta.sti_name if meta.inheriting_record?
|
38
|
+
record_class.reflections.each do |name, reflection|
|
39
|
+
name = name.to_s
|
40
|
+
add_reflection_attributes(hash, name, reflection) if hash[name]
|
41
|
+
end
|
42
|
+
hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def install_default_attributes!
|
46
|
+
@attributes['id'] ||= symbolic_name.to_s.hash.abs
|
47
|
+
install_timestamps!
|
48
|
+
end
|
49
|
+
|
50
|
+
def install_timestamps!
|
51
|
+
meta.timestamp_columns.each do |column|
|
52
|
+
@attributes[column.name] = now(column) unless @attributes.key?(column.name)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def now(column)
|
57
|
+
(ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now).to_s(:db)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def add_reflection_attributes(hash, name, reflection)
|
62
|
+
value = hash.delete(name)
|
63
|
+
case value
|
64
|
+
when Symbol
|
65
|
+
hash[reflection.primary_key_name] = session_binding.find_id(reflection.klass, value)
|
66
|
+
else
|
67
|
+
hash[reflection.primary_key_name] = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dataset
|
2
|
+
module Record # :nodoc:
|
3
|
+
|
4
|
+
# A mechanism to cache information about an ActiveRecord class to speed
|
5
|
+
# things up a bit for insertions, finds, and method generation.
|
6
|
+
class Meta # :nodoc:
|
7
|
+
attr_reader :class_name, :columns, :record_class, :table_name
|
8
|
+
|
9
|
+
# Provides information necessary to insert STI classes correctly for
|
10
|
+
# later reading.
|
11
|
+
delegate :name, :inheritance_column, :sti_name, :to => :record_class
|
12
|
+
|
13
|
+
def initialize(record_class)
|
14
|
+
@record_class = record_class
|
15
|
+
@class_name = record_class.name
|
16
|
+
@table_name = record_class.table_name
|
17
|
+
@columns = record_class.columns
|
18
|
+
end
|
19
|
+
|
20
|
+
def id_cache_key
|
21
|
+
@id_cache_key ||= table_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def inheriting_record?
|
25
|
+
!record_class.descends_from_active_record?
|
26
|
+
end
|
27
|
+
|
28
|
+
def timestamp_columns
|
29
|
+
@timestamp_columns ||= begin
|
30
|
+
timestamps = %w(created_at created_on updated_at updated_on)
|
31
|
+
columns.select do |column|
|
32
|
+
timestamps.include?(column.name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def id_finder_names
|
38
|
+
@id_finder_names ||= begin
|
39
|
+
names = record_class.self_and_descendents_from_active_record.collect {|c| finder_name c}
|
40
|
+
names.uniq.collect {|n| "#{n}_id".to_sym}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def model_finder_names
|
45
|
+
@record_finder_names ||= record_class.self_and_descendents_from_active_record.collect {|c| finder_name(c).pluralize.to_sym}.uniq
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_s
|
49
|
+
"#<RecordMeta: #{table_name}>"
|
50
|
+
end
|
51
|
+
|
52
|
+
def finder_name(klass)
|
53
|
+
klass.name.underscore.gsub('/', '_').sub(/^(\w)_/, '\1').gsub(/_(\w)_/, '_\1')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Dataset
|
2
|
+
module Record # :nodoc:
|
3
|
+
|
4
|
+
class Model # :nodoc:
|
5
|
+
attr_reader :attributes, :model, :meta, :symbolic_name, :session_binding
|
6
|
+
|
7
|
+
def initialize(meta, attributes, symbolic_name, session_binding)
|
8
|
+
@meta = meta
|
9
|
+
@attributes = attributes.stringify_keys
|
10
|
+
@symbolic_name = symbolic_name || object_id
|
11
|
+
@session_binding = session_binding
|
12
|
+
end
|
13
|
+
|
14
|
+
def record_class
|
15
|
+
meta.record_class
|
16
|
+
end
|
17
|
+
|
18
|
+
def id
|
19
|
+
model.id
|
20
|
+
end
|
21
|
+
|
22
|
+
def create
|
23
|
+
model = to_model
|
24
|
+
model.save!
|
25
|
+
model
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash
|
29
|
+
to_model.attributes
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_model
|
33
|
+
@model ||= begin
|
34
|
+
m = meta.record_class.new
|
35
|
+
attributes.each do |k,v|
|
36
|
+
if reflection = record_class.reflect_on_association(k.to_sym)
|
37
|
+
case v
|
38
|
+
when Symbol
|
39
|
+
v = session_binding.find_model(reflection.klass, v)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
m.send "#{k}=", v
|
43
|
+
end
|
44
|
+
m
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Dataset
|
2
|
+
# An error raised when a dataset class cannot be found.
|
3
|
+
#
|
4
|
+
class DatasetNotFound < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# A dataset may be referenced as a class or as a name. A Dataset::Resolver
|
8
|
+
# will take an identifier, whether a class or a name, and return the class.
|
9
|
+
#
|
10
|
+
class Resolver
|
11
|
+
cattr_accessor :default
|
12
|
+
|
13
|
+
def identifiers
|
14
|
+
@identifiers ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Attempt to convert a name to a constant. With the identifier :people, it
|
18
|
+
# will search for 'PeopleDataset', then 'People'.
|
19
|
+
#
|
20
|
+
def resolve(identifier)
|
21
|
+
return identifier if identifier.is_a?(Class)
|
22
|
+
if constant = identifiers[identifier]
|
23
|
+
return constant
|
24
|
+
end
|
25
|
+
|
26
|
+
constant = resolve_class(identifier)
|
27
|
+
unless constant
|
28
|
+
constant = resolve_identifier(identifier)
|
29
|
+
end
|
30
|
+
identifiers[identifier] = constant
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def resolve_identifier(identifier) # :nodoc:
|
35
|
+
constant = resolve_class(identifier)
|
36
|
+
unless constant
|
37
|
+
raise Dataset::DatasetNotFound, "Could not find a dataset '#{identifier.to_s.camelize}' or '#{identifier.to_s.camelize + suffix}'."
|
38
|
+
end
|
39
|
+
constant
|
40
|
+
end
|
41
|
+
|
42
|
+
def resolve_class(identifier)
|
43
|
+
names = [identifier.to_s.camelize, identifier.to_s.camelize + suffix]
|
44
|
+
constant = resolve_these(names.reverse)
|
45
|
+
if constant && constant.superclass != ::Dataset::Base
|
46
|
+
raise Dataset::DatasetNotFound, "Found a class '#{constant.name}', but it does not subclass 'Dataset::Base'."
|
47
|
+
end
|
48
|
+
constant
|
49
|
+
end
|
50
|
+
|
51
|
+
def resolve_these(names) # :nodoc:
|
52
|
+
names.each do |name|
|
53
|
+
constant = name.constantize rescue nil
|
54
|
+
return constant if constant && constant.is_a?(Class)
|
55
|
+
end
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def suffix # :nodoc:
|
60
|
+
@suffix ||= 'Dataset'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Resolves a dataset by looking for a file in the provided directory path
|
65
|
+
# that has a name matching the identifier. Of course, should the identifier
|
66
|
+
# be a class already, it is simply returned.
|
67
|
+
#
|
68
|
+
class DirectoryResolver < Resolver
|
69
|
+
def initialize(*paths)
|
70
|
+
@paths = paths
|
71
|
+
end
|
72
|
+
|
73
|
+
def <<(path)
|
74
|
+
@paths << path
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
def resolve_identifier(identifier) # :nodoc:
|
79
|
+
@paths.each do |path|
|
80
|
+
file = File.join(path, identifier.to_s)
|
81
|
+
unless File.exists?(file + '.rb')
|
82
|
+
file = file + '_' + file_suffix
|
83
|
+
next unless File.exists?(file + '.rb')
|
84
|
+
end
|
85
|
+
require file
|
86
|
+
begin
|
87
|
+
return super
|
88
|
+
rescue Dataset::DatasetNotFound => dnf
|
89
|
+
if dnf.message =~ /\ACould not find/
|
90
|
+
raise Dataset::DatasetNotFound, "Found the dataset file '#{file + '.rb'}', but it did not define #{dnf.message.sub('Could not find ', '')}"
|
91
|
+
else
|
92
|
+
raise Dataset::DatasetNotFound, "Found the dataset file '#{file + '.rb'}' and a class #{dnf.message.sub('Found a class ', '')}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
raise DatasetNotFound, "Could not find a dataset file in #{@paths.inspect} having the name '#{identifier}.rb' or '#{identifier}_#{file_suffix}.rb'."
|
97
|
+
end
|
98
|
+
|
99
|
+
def file_suffix # :nodoc:
|
100
|
+
@file_suffix ||= suffix.downcase
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# The default resolver, used by the Dataset::Sessions that aren't given a
|
105
|
+
# different instance. You can set this to something else in your
|
106
|
+
# test/spec_helper.
|
107
|
+
#
|
108
|
+
Resolver.default = Resolver.new
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Dataset
|
2
|
+
class Session # :nodoc:
|
3
|
+
attr_accessor :dataset_resolver
|
4
|
+
|
5
|
+
def initialize(database, dataset_resolver = Resolver.default)
|
6
|
+
@database = database
|
7
|
+
@dataset_resolver = dataset_resolver
|
8
|
+
@datasets = Hash.new
|
9
|
+
@load_stack = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_dataset(test_class, dataset_identifier)
|
13
|
+
dataset = dataset_resolver.resolve(dataset_identifier)
|
14
|
+
if dataset.used_datasets
|
15
|
+
dataset.used_datasets.each { |used_dataset| self.add_dataset(test_class, used_dataset) }
|
16
|
+
end
|
17
|
+
datasets_for(test_class) << dataset
|
18
|
+
end
|
19
|
+
|
20
|
+
def datasets_for(test_class)
|
21
|
+
if test_class.superclass
|
22
|
+
@datasets[test_class] ||= Collection.new(datasets_for(test_class.superclass) || [])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_datasets_for(test_class)
|
27
|
+
datasets = datasets_for(test_class)
|
28
|
+
if last_load = @load_stack.last
|
29
|
+
if last_load.datasets == datasets
|
30
|
+
current_load = Reload.new(last_load)
|
31
|
+
elsif last_load.datasets.subset?(datasets)
|
32
|
+
@database.capture(last_load.datasets)
|
33
|
+
current_load = Load.new(datasets, last_load.dataset_binding)
|
34
|
+
current_load.execute(last_load.datasets, @dataset_resolver)
|
35
|
+
@load_stack.push(current_load)
|
36
|
+
else
|
37
|
+
@load_stack.pop
|
38
|
+
last_load = @load_stack.last
|
39
|
+
@database.restore(last_load.datasets) if last_load
|
40
|
+
current_load = load_datasets_for(test_class)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
@database.clear
|
44
|
+
current_load = Load.new(datasets, @database)
|
45
|
+
current_load.execute([], @dataset_resolver)
|
46
|
+
@load_stack.push(current_load)
|
47
|
+
end
|
48
|
+
current_load
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
module Dataset
|
2
|
+
# An error that will be raised when an attempt is made to load a named model
|
3
|
+
# that doesn't exist. For example, if you do people(:jenny), and yet no
|
4
|
+
# record was ever created with the symbolic name :jenny, this error will be
|
5
|
+
# raised.
|
6
|
+
#
|
7
|
+
class RecordNotFound < StandardError
|
8
|
+
def initialize(record_type, symbolic_name)
|
9
|
+
super "There is no '#{record_type.name}' found for the symbolic name ':#{symbolic_name}'."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Whenever you use Dataset::RecordMethods, you will get finder methods in
|
14
|
+
# your tests that help you load instances of the records you have created
|
15
|
+
# (or named models).
|
16
|
+
#
|
17
|
+
# create_record :person, :jimmy, :name => 'Jimmy'
|
18
|
+
# person_id(:jimmy) => The id was captured from create_record
|
19
|
+
# people(:jimmy) => The same as Jimmy.find(person_id(:jimmy))
|
20
|
+
#
|
21
|
+
# The methods will not exist in a test unless it utilizes a dataset (or
|
22
|
+
# defines one itself through the block technique) that creates a record for
|
23
|
+
# the type.
|
24
|
+
#
|
25
|
+
# You may also pass multiple names to these methods, which will have them
|
26
|
+
# return an array of values.
|
27
|
+
#
|
28
|
+
# people(:jimmy, :jane, :jeff) => [<# Person :name => 'Jimmy'>, <# Person :name => 'Jane'>, <# Person :name => 'Jeff'>]
|
29
|
+
# person_id(:jimmy, :jane, :jeff) => [1, 2, 3]
|
30
|
+
#
|
31
|
+
# NOTE the plurality of the instance finder, versus the singularity of the
|
32
|
+
# id finder.
|
33
|
+
#
|
34
|
+
# == Single Table Inheritence
|
35
|
+
#
|
36
|
+
# class Person < ActiveRecord::Base; end
|
37
|
+
# class User < Person; end
|
38
|
+
#
|
39
|
+
# create_record :user, :bobby, :name => 'Bobby'
|
40
|
+
#
|
41
|
+
# people(:bobby) OR users(:bobby)
|
42
|
+
#
|
43
|
+
module ModelFinders
|
44
|
+
def create_finder(record_meta) # :nodoc:
|
45
|
+
@finders_generated ||= []
|
46
|
+
|
47
|
+
return if @finders_generated.include?(record_meta)
|
48
|
+
|
49
|
+
record_meta.model_finder_names.each do |finder_name|
|
50
|
+
unless instance_methods.include?(finder_name)
|
51
|
+
define_method finder_name do |*symbolic_names|
|
52
|
+
names = Array(symbolic_names)
|
53
|
+
models = names.inject([]) do |c,n|
|
54
|
+
c << dataset_session_binding.find_model(record_meta, n); c
|
55
|
+
end
|
56
|
+
names.size == 1 ? models.first : models
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
record_meta.id_finder_names.each do |finder_name|
|
62
|
+
unless instance_methods.include?(finder_name)
|
63
|
+
define_method finder_name do |*symbolic_names|
|
64
|
+
names = Array(symbolic_names)
|
65
|
+
ids = names.inject([]) do |c,n|
|
66
|
+
c << dataset_session_binding.find_id(record_meta, n); c
|
67
|
+
end
|
68
|
+
names.size == 1 ? ids.first : ids
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
@finders_generated << record_meta
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Any Dataset::Base subclass, dataset block, or test method in a
|
78
|
+
# dataset-using test context (including setup/teardown/before/after) may
|
79
|
+
# create and access models through these methods. Note that you should use
|
80
|
+
# Dataset::ModelFinders if you can for finding your created data.
|
81
|
+
#
|
82
|
+
module RecordMethods
|
83
|
+
|
84
|
+
# Similar to old fashioned fixtures, this will do a direct database
|
85
|
+
# insert, without running any validations or preventing you from writing
|
86
|
+
# attr_protected attributes. Very nice for speed, but kind of a pain if
|
87
|
+
# you have complex structures or hard to keep right validations.
|
88
|
+
#
|
89
|
+
# create_record :type, :symbolic_name, :attr1 => 'value', :attr2 => 'value', :etc => 'etc'
|
90
|
+
#
|
91
|
+
# The _symbolic_name_ is an optional parameter. You may replace _type_
|
92
|
+
# with an ActiveRecord::Base subclass or anything that works with:
|
93
|
+
#
|
94
|
+
# to_s.classify.constantize
|
95
|
+
#
|
96
|
+
# The id of the model will be a hash of the symbolic name.
|
97
|
+
#
|
98
|
+
def create_record(*args)
|
99
|
+
dataset_session_binding.create_record(*args)
|
100
|
+
end
|
101
|
+
|
102
|
+
# This will instantiate your model class and assign each attribute WITHOUT
|
103
|
+
# using mass assignment. Validations will be run. Very nice for complex
|
104
|
+
# structures or hard to keep right validations, but potentially a bit
|
105
|
+
# slower, since it runs through all that ActiveRecord code.
|
106
|
+
#
|
107
|
+
# create_model :type, :symbolic_name, :attr1 => 'value', :attr2 => 'value', :etc => 'etc'
|
108
|
+
#
|
109
|
+
# The _symbolic_name_ is an optional parameter. You may replace _type_
|
110
|
+
# with an ActiveRecord::Base subclass or anything that works with:
|
111
|
+
#
|
112
|
+
# to_s.classify.constantize
|
113
|
+
#
|
114
|
+
# The id of the record will be kept from the instance that is saved.
|
115
|
+
#
|
116
|
+
def create_model(*args)
|
117
|
+
dataset_session_binding.create_model(*args)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Dataset will track each of the records it creates by symbolic name to
|
121
|
+
# id. When you need the id of a record, there is no need to go to the
|
122
|
+
# database.
|
123
|
+
#
|
124
|
+
# find_id :person, :bobby => 23425234
|
125
|
+
#
|
126
|
+
# You may pass one name or many, with many returning an Array of ids.
|
127
|
+
#
|
128
|
+
def find_id(*args)
|
129
|
+
dataset_session_binding.find_id(*args)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Dataset will track each of the records it creates by symbolic name to
|
133
|
+
# id. When you need an instance of a record, the stored id will be used to
|
134
|
+
# do the fastest lookup possible: Person.find(23425234).
|
135
|
+
#
|
136
|
+
# find_model :person, :bobby => <#Person :id => 23425234, :name => 'Bobby'>
|
137
|
+
#
|
138
|
+
# You may pass one name or many, with many returning an Array of
|
139
|
+
# instances.
|
140
|
+
#
|
141
|
+
def find_model(*args)
|
142
|
+
dataset_session_binding.find_model(*args)
|
143
|
+
end
|
144
|
+
|
145
|
+
# This is a great help when you want to create records in a custom helper
|
146
|
+
# method, then make it and maybe things associated to it available to
|
147
|
+
# tests through the Dataset::ModelFinders.
|
148
|
+
#
|
149
|
+
# thingy = create_very_complex_thingy_and_stuff
|
150
|
+
# name_model thingy, :thingy_bob
|
151
|
+
# name_model thingy.part, :thingy_part
|
152
|
+
#
|
153
|
+
# In tests:
|
154
|
+
#
|
155
|
+
# thingies(:thingy_bob)
|
156
|
+
# parts(:thingy_part)
|
157
|
+
#
|
158
|
+
def name_model(*args)
|
159
|
+
dataset_session_binding.name_model(*args)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Converts string names into symbols for use in naming models
|
163
|
+
#
|
164
|
+
# name_to_sym 'my name' => :my_name
|
165
|
+
# name_to_sym 'RPaul' => :r_paul
|
166
|
+
#
|
167
|
+
def name_to_sym(name)
|
168
|
+
dataset_session_binding.name_to_sym(name)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class SessionBinding # :nodoc:
|
173
|
+
attr_reader :database, :parent_binding
|
174
|
+
attr_reader :model_finders, :record_methods
|
175
|
+
attr_reader :block_variables
|
176
|
+
|
177
|
+
def initialize(database_or_parent_binding)
|
178
|
+
@id_cache = Hash.new {|h,k| h[k] = {}}
|
179
|
+
@record_methods = new_record_methods_module
|
180
|
+
@model_finders = new_model_finders_module
|
181
|
+
@block_variables = Hash.new
|
182
|
+
|
183
|
+
case database_or_parent_binding
|
184
|
+
when Dataset::SessionBinding
|
185
|
+
@parent_binding = database_or_parent_binding
|
186
|
+
@database = parent_binding.database
|
187
|
+
@model_finders.module_eval { include database_or_parent_binding.model_finders }
|
188
|
+
@block_variables.update(database_or_parent_binding.block_variables)
|
189
|
+
else
|
190
|
+
@database = database_or_parent_binding
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def copy_block_variables(dataset_block)
|
195
|
+
dataset_block.instance_variables.each do |name|
|
196
|
+
self.block_variables[name] = dataset_block.instance_variable_get(name)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def create_model(record_type, *args)
|
201
|
+
insert(Dataset::Record::Model, record_type, *args)
|
202
|
+
end
|
203
|
+
|
204
|
+
def create_record(record_type, *args)
|
205
|
+
insert(Dataset::Record::Fixture, record_type, *args)
|
206
|
+
end
|
207
|
+
|
208
|
+
def find_id(record_type_or_meta, symbolic_name)
|
209
|
+
record_meta = record_meta_for_type(record_type_or_meta)
|
210
|
+
if local_id = @id_cache[record_meta.id_cache_key][symbolic_name]
|
211
|
+
local_id
|
212
|
+
elsif !parent_binding.nil?
|
213
|
+
parent_binding.find_id record_meta, symbolic_name
|
214
|
+
else
|
215
|
+
raise RecordNotFound.new(record_meta, symbolic_name)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def find_model(record_type_or_meta, symbolic_name)
|
220
|
+
record_meta = record_meta_for_type(record_type_or_meta)
|
221
|
+
if local_id = @id_cache[record_meta.id_cache_key][symbolic_name]
|
222
|
+
record_meta.record_class.find local_id
|
223
|
+
elsif !parent_binding.nil?
|
224
|
+
parent_binding.find_model record_meta, symbolic_name
|
225
|
+
else
|
226
|
+
raise RecordNotFound.new(record_meta, symbolic_name)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def install_block_variables(target)
|
231
|
+
block_variables.each do |k,v|
|
232
|
+
target.instance_variable_set(k,v)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def name_model(record, symbolic_name)
|
237
|
+
record_meta = database.record_meta(record.class)
|
238
|
+
@model_finders.create_finder(record_meta)
|
239
|
+
@id_cache[record_meta.id_cache_key][symbolic_name] = record.id
|
240
|
+
record
|
241
|
+
end
|
242
|
+
|
243
|
+
def record_meta_for_type(record_type)
|
244
|
+
record_type.is_a?(Record::Meta) ? record_type : begin
|
245
|
+
record_class = resolve_record_class(record_type)
|
246
|
+
database.record_meta(record_class)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def name_to_sym(name)
|
251
|
+
name.to_s.underscore.gsub("'", "").gsub("\"", "").gsub(" ", "_").to_sym if name
|
252
|
+
end
|
253
|
+
|
254
|
+
protected
|
255
|
+
def insert(dataset_record_class, record_type, *args)
|
256
|
+
symbolic_name, attributes = extract_creation_arguments args
|
257
|
+
record_meta = record_meta_for_type(record_type)
|
258
|
+
record = dataset_record_class.new(record_meta, attributes, symbolic_name, self)
|
259
|
+
return_value = nil
|
260
|
+
|
261
|
+
@model_finders.create_finder(record_meta)
|
262
|
+
ActiveRecord::Base.silence do
|
263
|
+
return_value = record.create
|
264
|
+
@id_cache[record_meta.id_cache_key][symbolic_name] = record.id
|
265
|
+
end
|
266
|
+
return_value
|
267
|
+
end
|
268
|
+
|
269
|
+
def extract_creation_arguments(arguments)
|
270
|
+
if arguments.size == 2 && arguments.last.kind_of?(Hash)
|
271
|
+
arguments
|
272
|
+
elsif arguments.size == 1 && arguments.last.kind_of?(Hash)
|
273
|
+
[nil, arguments.last]
|
274
|
+
elsif arguments.size == 1 && arguments.last.kind_of?(Symbol)
|
275
|
+
[arguments.last, Hash.new]
|
276
|
+
else
|
277
|
+
[nil, Hash.new]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def new_model_finders_module
|
282
|
+
mod = Module.new
|
283
|
+
dataset_binding = self
|
284
|
+
mod.module_eval do
|
285
|
+
define_method :dataset_session_binding do
|
286
|
+
dataset_binding
|
287
|
+
end
|
288
|
+
end
|
289
|
+
mod.extend ModelFinders
|
290
|
+
mod
|
291
|
+
end
|
292
|
+
|
293
|
+
def new_record_methods_module
|
294
|
+
mod = Module.new do
|
295
|
+
include RecordMethods
|
296
|
+
end
|
297
|
+
dataset_binding = self
|
298
|
+
mod.module_eval do
|
299
|
+
define_method :dataset_session_binding do
|
300
|
+
dataset_binding
|
301
|
+
end
|
302
|
+
end
|
303
|
+
mod
|
304
|
+
end
|
305
|
+
|
306
|
+
def resolve_record_class(record_type)
|
307
|
+
case record_type
|
308
|
+
when Symbol
|
309
|
+
resolve_record_class record_type.to_s.singularize.camelize
|
310
|
+
when Class
|
311
|
+
record_type
|
312
|
+
when String
|
313
|
+
record_type.constantize
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|