ninja-model 0.4.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.
- data/Gemfile +2 -0
- data/README.md +1 -0
- data/Rakefile +38 -0
- data/lib/generators/ninja_model/model/model_generator.rb +19 -0
- data/lib/generators/ninja_model/model/templates/model.rb +7 -0
- data/lib/generators/ninja_model/scaffold/scaffold_generator.rb +69 -0
- data/lib/generators/ninja_model.rb +12 -0
- data/lib/ninja-model.rb +1 -0
- data/lib/ninja_model/adapters/abstract_adapter.rb +63 -0
- data/lib/ninja_model/adapters/adapter_manager.rb +67 -0
- data/lib/ninja_model/adapters/adapter_pool.rb +128 -0
- data/lib/ninja_model/adapters/adapter_specification.rb +11 -0
- data/lib/ninja_model/adapters.rb +70 -0
- data/lib/ninja_model/associations/active_record_proxy.rb +53 -0
- data/lib/ninja_model/associations/association_proxy.rb +8 -0
- data/lib/ninja_model/associations/belongs_to_association.rb +31 -0
- data/lib/ninja_model/associations/has_many_association.rb +36 -0
- data/lib/ninja_model/associations/has_one_association.rb +19 -0
- data/lib/ninja_model/associations/ninja_model_proxy.rb +46 -0
- data/lib/ninja_model/associations.rb +198 -0
- data/lib/ninja_model/attributes.rb +134 -0
- data/lib/ninja_model/base.rb +106 -0
- data/lib/ninja_model/callbacks.rb +14 -0
- data/lib/ninja_model/configuration.rb +20 -0
- data/lib/ninja_model/core_ext/symbol.rb +7 -0
- data/lib/ninja_model/errors.rb +5 -0
- data/lib/ninja_model/identity.rb +24 -0
- data/lib/ninja_model/log_subscriber.rb +18 -0
- data/lib/ninja_model/persistence.rb +76 -0
- data/lib/ninja_model/predicate.rb +43 -0
- data/lib/ninja_model/railtie.rb +27 -0
- data/lib/ninja_model/reflection.rb +118 -0
- data/lib/ninja_model/relation/finder_methods.rb +74 -0
- data/lib/ninja_model/relation/query_methods.rb +62 -0
- data/lib/ninja_model/relation/spawn_methods.rb +59 -0
- data/lib/ninja_model/relation.rb +80 -0
- data/lib/ninja_model/scoping.rb +50 -0
- data/lib/ninja_model/validation.rb +38 -0
- data/lib/ninja_model/version.rb +3 -0
- data/lib/ninja_model.rb +38 -0
- data/spec/ninja_model/adapters/adapter_pool_spec.rb +72 -0
- data/spec/ninja_model/adapters_spec.rb +8 -0
- data/spec/ninja_model/attributes_spec.rb +85 -0
- data/spec/ninja_model/base_spec.rb +66 -0
- data/spec/ninja_model/identity_spec.rb +41 -0
- data/spec/ninja_model/persistence_spec.rb +9 -0
- data/spec/ninja_model/predicate_spec.rb +13 -0
- data/spec/ninja_model/query_methods_spec.rb +76 -0
- data/spec/ninja_model/relation_spec.rb +26 -0
- data/spec/ninja_model/scoping_spec.rb +40 -0
- data/spec/ninja_model_spec.rb +11 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/active_model_lint.rb +17 -0
- metadata +214 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Ninja-model
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
Bundler::GemHelper.install_tasks
|
4
|
+
|
5
|
+
require 'rake'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
require 'cucumber'
|
9
|
+
require 'cucumber/rake/task'
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
12
|
+
t.verbose = false
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Run specs with documentation format'
|
16
|
+
RSpec::Core::RakeTask.new(:specd) do |t|
|
17
|
+
t.verbose = false
|
18
|
+
t.rspec_opts = '--format d'
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::RDocTask.new do |r|
|
22
|
+
r.rdoc_dir = 'doc/html'
|
23
|
+
r.main = "README.md"
|
24
|
+
r.rdoc_files.include('README.md', 'lib/**/*.rb')
|
25
|
+
r.rdoc_files.exclude('lib/generators/**/*')
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace :spec do
|
29
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
30
|
+
t.rcov = true
|
31
|
+
t.rcov_opts = %w{--text-report --sort coverage}
|
32
|
+
t.rcov_opts << %w{--exclude gems\/,spec\/}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
37
|
+
t.cucumber_opts = "features --format progress"
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'generators/ninja_model'
|
2
|
+
|
3
|
+
module NinjaModel
|
4
|
+
module Generators
|
5
|
+
class ModelGenerator < NamedBase
|
6
|
+
argument :attributes, :type => :array, :default => [], :banner => 'field:type field:type'
|
7
|
+
|
8
|
+
def initialize(*args, &block)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_model
|
13
|
+
template 'model.rb', "app/models/#{singular_name}.rb"
|
14
|
+
end
|
15
|
+
|
16
|
+
hook_for :test_framework, :as => :model
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'generators/ninja_model'
|
2
|
+
|
3
|
+
module NinjaModel
|
4
|
+
module Generators
|
5
|
+
class ScaffoldGenerator < ModelGenerator
|
6
|
+
argument :attributes, :type => :array, :default => [], :banner => 'field:type field:type'
|
7
|
+
|
8
|
+
def initialize(*args, &block)
|
9
|
+
super
|
10
|
+
|
11
|
+
@controller_actions = []
|
12
|
+
actions.each do |action|
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_controller
|
18
|
+
template 'controller.rb', "app/controllers/#{plural_name}_controller.rb"
|
19
|
+
unless options.skip_helper?
|
20
|
+
template 'helper.rb', "app/helpers/#{plural_name}_helper.rb"File.join(plugin_path, 'app/helpers',
|
21
|
+
"#{plural_file_path}_helper.rb")
|
22
|
+
end
|
23
|
+
|
24
|
+
controller_actions.each do |action|
|
25
|
+
if %w[index show new edit].include?(action)
|
26
|
+
template "views/#{action}.html.erb", File.join(plugin_path,
|
27
|
+
"app/views/#{plural_name}/#{action}.html.erb")
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
template 'views/_form.html.erb', File.join(plugin_path, "app/views/#{plural_name}/_form.html.erb")
|
33
|
+
|
34
|
+
if class_path.length < 0
|
35
|
+
plugin_route("resources #{plural_file_name.to_sym.inspect},
|
36
|
+
:controller => '#{(class_path + [plural_name]).join('/')}")
|
37
|
+
else
|
38
|
+
plugin_route("resources #{plural_name.to_sym.inspect}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
attr_reader :controller_actions
|
45
|
+
|
46
|
+
def controller_methods(dir)
|
47
|
+
controller_actions.map do |action|
|
48
|
+
read_template("#{dir}/#{action}.rb")
|
49
|
+
end.join("\n").strip
|
50
|
+
end
|
51
|
+
|
52
|
+
def read_template(relative_path)
|
53
|
+
ERB.new(File.read(find_in_source_paths(relative_path)), nil, '-').result(binding)
|
54
|
+
end
|
55
|
+
|
56
|
+
def all_actions
|
57
|
+
%w[index show new create edit update destroy]
|
58
|
+
end
|
59
|
+
|
60
|
+
def action?(name)
|
61
|
+
controller_actions.include? name.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def actions?(*names)
|
65
|
+
names.all? { |name| action? name }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/named_base'
|
3
|
+
|
4
|
+
module NinjaModel
|
5
|
+
module Generators
|
6
|
+
class NamedBase < Rails::Generators::NamedBase
|
7
|
+
def self.source_root
|
8
|
+
@ninja_model_source_root ||= File.join(File.dirname(__FILE__), 'ninja_model', generator_name, 'templates')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/ninja-model.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'ninja_model'
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module NinjaModel
|
2
|
+
module Adapters
|
3
|
+
class AbstractAdapter
|
4
|
+
class << self
|
5
|
+
def instance
|
6
|
+
@instance ||= new
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(config, logger = nil)
|
11
|
+
@active = nil
|
12
|
+
@config, @logger = config, logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def adapter_name
|
16
|
+
'Abstract'
|
17
|
+
end
|
18
|
+
|
19
|
+
def persistent_connection?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def active?
|
24
|
+
@active != false
|
25
|
+
end
|
26
|
+
|
27
|
+
def reconnect!
|
28
|
+
@active = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def disconnect!
|
32
|
+
@active = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def reset!
|
36
|
+
end
|
37
|
+
|
38
|
+
def verify!
|
39
|
+
reconnect! unless active?
|
40
|
+
end
|
41
|
+
|
42
|
+
def create(model)
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def read(query)
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def update(model)
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
def destroy(model)
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def raw_connection
|
59
|
+
@connection
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module NinjaModel
|
2
|
+
module Adapters
|
3
|
+
class AdapterManager
|
4
|
+
attr_reader :adapter_pools
|
5
|
+
|
6
|
+
def initialize(pools = {})
|
7
|
+
@adapter_pools = pools
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_adapter(name, spec)
|
11
|
+
@adapter_pools[name] = Adapters::AdapterPool.new(spec)
|
12
|
+
end
|
13
|
+
|
14
|
+
def release_active_adapters!
|
15
|
+
@adapter_pools.each_value do |pool|
|
16
|
+
pool.release_adapter
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def release_reloadable_adapters!
|
21
|
+
@adapter_pools.each_value do |pool|
|
22
|
+
pool.shutdown_reloadable_adapters!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def release_all_adapters!
|
27
|
+
@adapter_pools.each_value do |pool|
|
28
|
+
pool.shutdown!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def retrieve_adapter(klass)
|
33
|
+
pool = retrieve_adapter_pool(klass)
|
34
|
+
(pool && pool.instance) or raise StandardError
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_adapter(klass)
|
38
|
+
pool = @adapter_pools[klass.name]
|
39
|
+
return nil unless pool
|
40
|
+
@adapter_pools.delete_if { |key, value| value == pool }
|
41
|
+
pool.shutdown!
|
42
|
+
pool.spec.config
|
43
|
+
end
|
44
|
+
|
45
|
+
def retrieve_adapter_pool(klass)
|
46
|
+
pool = @adapter_pools[klass.name]
|
47
|
+
return pool if pool
|
48
|
+
return nil if NinjaModel::Base == klass
|
49
|
+
retrieve_adapter_pool klass.superclass
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class AdapterManagement
|
54
|
+
def initialize(app)
|
55
|
+
@app = app
|
56
|
+
end
|
57
|
+
|
58
|
+
def call(env)
|
59
|
+
@app.call(env)
|
60
|
+
ensure
|
61
|
+
unless env.key?('rake.test')
|
62
|
+
NinjaModel::Base.clear_active_instances!
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module NinjaModel
|
2
|
+
module Adapters
|
3
|
+
class AdapterPool
|
4
|
+
attr_reader :spec, :instances
|
5
|
+
def initialize(spec)
|
6
|
+
@spec = spec
|
7
|
+
@instances = []
|
8
|
+
@checked_out_instances = []
|
9
|
+
@assigned_instances = {}
|
10
|
+
@instance_mutex = Monitor.new
|
11
|
+
@instance_queue = @instance_mutex.new_cond
|
12
|
+
@max_size = 5
|
13
|
+
@timeout = 3
|
14
|
+
end
|
15
|
+
|
16
|
+
def instance
|
17
|
+
NinjaModel.logger.debug("instance called for #{current_instance_id}")
|
18
|
+
@assigned_instances[current_instance_id] ||= checkout
|
19
|
+
end
|
20
|
+
|
21
|
+
def release_instance(with_id = current_instance_id)
|
22
|
+
inst = @assigned_instances.delete(with_id)
|
23
|
+
checkin inst if inst
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_instance
|
27
|
+
instance_id = current_instance_id
|
28
|
+
fresh_instance = true unless @assigned_instances[instance_id]
|
29
|
+
yield instance
|
30
|
+
ensure
|
31
|
+
release_instance(instance_id) if fresh_instance
|
32
|
+
end
|
33
|
+
|
34
|
+
def connected?
|
35
|
+
!@instances.empty?
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def clear_stale_cached_instances!
|
41
|
+
NinjaModel.logger.debug("clearing stale instances: #{@assigned_instances.keys}")
|
42
|
+
keys = @assigned_instances.keys - Thread.list.find_all { |t|
|
43
|
+
t.alive?
|
44
|
+
}.map { |thread| thread.object_id }
|
45
|
+
keys.each do |key|
|
46
|
+
NinjaModel.logger.debug("Checking in stale connection for #{key}")
|
47
|
+
checkin @assigned_instances[key]
|
48
|
+
@assigned_instances.delete(key)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def checkout
|
53
|
+
NinjaModel.logger.debug("checking out a connection for #{current_instance_id}")
|
54
|
+
@instance_mutex.synchronize do
|
55
|
+
loop do
|
56
|
+
instance = if @checked_out_instances.size < @instances.size
|
57
|
+
checkout_existing_instance
|
58
|
+
elsif @instances.size < @max_size
|
59
|
+
checkout_new_instance
|
60
|
+
end
|
61
|
+
return instance if instance
|
62
|
+
|
63
|
+
# If we're here, we didn't get a valid instance
|
64
|
+
@instance_queue.wait(@timeout)
|
65
|
+
if (@checked_out_instances.size < @instances.size)
|
66
|
+
next
|
67
|
+
else
|
68
|
+
NinjaModel.logger.debug("Connection pool full. Clearing cached instances.")
|
69
|
+
clear_stale_cached_instances!
|
70
|
+
if @max_size == @checked_out_instances.size
|
71
|
+
NinjaModel.logger.warn("Connection pool full?")
|
72
|
+
NinjaModel.logger.warn("@checked_out_instances: #{@checked_out_instances.inspect}")
|
73
|
+
NinjaModel.logger.warn("@assigned_instances: #{@assigned_instances.inspect}")
|
74
|
+
raise ConnectionTimeoutError, "[ninja-model] *ERROR* Could not obtain an adapter instance within #{@timeout} seconds. The max adapter pool size is currently #{@max_size}...consider increasing it."
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def checkin(inst)
|
82
|
+
@instance_mutex.synchronize do
|
83
|
+
@checked_out_instances.delete inst
|
84
|
+
@instance_queue.signal
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def new_instance
|
89
|
+
NinjaModel::Base.send(spec.adapter_method, spec.config)
|
90
|
+
end
|
91
|
+
|
92
|
+
def current_instance_id
|
93
|
+
Thread.current.object_id
|
94
|
+
end
|
95
|
+
|
96
|
+
def checkout_new_instance
|
97
|
+
i = new_instance
|
98
|
+
@instances << i
|
99
|
+
checkout_and_verify(i)
|
100
|
+
end
|
101
|
+
|
102
|
+
def checkout_existing_instance
|
103
|
+
i = (@instances - @checked_out_instances).first
|
104
|
+
checkout_and_verify(i)
|
105
|
+
end
|
106
|
+
|
107
|
+
def checkout_and_verify(inst)
|
108
|
+
inst.verify!
|
109
|
+
@checked_out_instances << inst
|
110
|
+
inst
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class AdapterManagement
|
115
|
+
def initialize(app)
|
116
|
+
@app = app
|
117
|
+
end
|
118
|
+
|
119
|
+
def call(env)
|
120
|
+
@app.call(env)
|
121
|
+
ensure
|
122
|
+
unless env.key?('rask.test')
|
123
|
+
NinjaModel::Base.clear_active_instances!
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
module NinjaModel
|
4
|
+
|
5
|
+
module Adapters
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
extend ActiveSupport::Autoload
|
8
|
+
|
9
|
+
autoload :AdapterSpecification
|
10
|
+
autoload :AdapterManager
|
11
|
+
autoload :AdapterPool
|
12
|
+
autoload :AbstractAdapter
|
13
|
+
|
14
|
+
class << self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Base
|
19
|
+
class_attribute :adapter_manager
|
20
|
+
self.adapter_manager = NinjaModel::Adapters::AdapterManager.new
|
21
|
+
|
22
|
+
def adapter
|
23
|
+
self.class.adapter
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.set_adapter(spec = nil)
|
27
|
+
case spec
|
28
|
+
when nil
|
29
|
+
raise AdapterNotSpecified unless defined?(Rails.env)
|
30
|
+
set_adapter(Rails.env)
|
31
|
+
when AdapterSpecification
|
32
|
+
self.adapter_manager.create_adapter(name, spec)
|
33
|
+
when Symbol, String
|
34
|
+
if config = NinjaModel.configuration.specs[spec.to_s]
|
35
|
+
set_adapter(config)
|
36
|
+
else
|
37
|
+
raise AdapterNotSpecified, "#{spec} is not configured"
|
38
|
+
end
|
39
|
+
else
|
40
|
+
spec = spec.symbolize_keys
|
41
|
+
unless spec.key?(:adapter) then raise AdapterNotSpecified, "configuration does not specify adapter" end
|
42
|
+
begin
|
43
|
+
require File.join(NinjaModel.configuration.adapter_path, "#{spec[:adapter]}_adapter")
|
44
|
+
rescue LoadError => e
|
45
|
+
raise "Please install (or create) the #{spec[:adapter]} adapter. Search path is #{NinjaModel.configuration.adapter_path}"
|
46
|
+
end
|
47
|
+
adapter_method = "#{spec[:adapter]}_adapter"
|
48
|
+
if !respond_to?(adapter_method)
|
49
|
+
raise AdapterNotFound, "ninja configuration specifies nonexistent #{spec[:adapter]} adapter"
|
50
|
+
end
|
51
|
+
shutdown_adapter
|
52
|
+
set_adapter(AdapterSpecification.new(spec, adapter_method))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
def adapter
|
58
|
+
retrieve_adapter
|
59
|
+
end
|
60
|
+
|
61
|
+
def retrieve_adapter
|
62
|
+
adapter_manager.retrieve_adapter(self)
|
63
|
+
end
|
64
|
+
|
65
|
+
def shutdown_adapter(klass = self)
|
66
|
+
adapter_manager.remove_adapter(klass)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
module NinjaModel
|
3
|
+
module Associations
|
4
|
+
class ActiveRecordProxy
|
5
|
+
def initialize(ninja_model)
|
6
|
+
@klass = ninja_model
|
7
|
+
@klass.class_eval do
|
8
|
+
def proxy
|
9
|
+
@proxy ||= begin
|
10
|
+
self.class.proxy.instance(self)
|
11
|
+
end
|
12
|
+
@proxy.attributes = self.attributes.delete_if { |k,v| k.eql?('id') }
|
13
|
+
@proxy
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
@proxy_klass = ninja_model.parent.const_set("#{@klass.model_name}Proxy", Class.new(ActiveRecord::Base))
|
18
|
+
@proxy_klass.class_eval do
|
19
|
+
cattr_accessor :columns
|
20
|
+
self.columns = []
|
21
|
+
def self.column(name, sql_type = nil, default = nil)
|
22
|
+
self.columns << ActiveRecord::ConnectionAdapters::Column.new(name, nil, sql_type.to_s, default)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
@klass.model_attributes.each do |attr|
|
27
|
+
@proxy_klass.send :column, attr.name, attr.type, attr.default
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def instance(obj)
|
32
|
+
proxy = @proxy_klass.new
|
33
|
+
proxy.send :init_with, {'attributes' => obj.attributes}
|
34
|
+
proxy
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_association(macro, association_id, options)
|
38
|
+
unless macro.eql?(:belongs_to)
|
39
|
+
options = {:foreign_key => derive_foreign_key}.merge(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
@proxy = nil
|
43
|
+
@proxy_klass.send macro, association_id, options
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def derive_foreign_key
|
49
|
+
"#{@klass.name.underscore}_id".to_sym
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module NinjaModel
|
2
|
+
module Associations
|
3
|
+
class BelongsToAssociation < AssociationProxy
|
4
|
+
def initialize(owner, reflection)
|
5
|
+
@owner, @reflection = owner, reflection
|
6
|
+
end
|
7
|
+
|
8
|
+
def replace(record)
|
9
|
+
if record.nil?
|
10
|
+
@target = @owner[@reflection.primary_key_name] = nil
|
11
|
+
else
|
12
|
+
@target = (AssociationProxy === record ? record.target : record)
|
13
|
+
@owner[@reflection.primary_key_name] = record_id(record) if record.persisted?
|
14
|
+
@updated = true
|
15
|
+
end
|
16
|
+
loaded
|
17
|
+
record
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def find_target
|
23
|
+
@reflection.klass.scoped.where(@reflection.association_foreign_key => @owner.send(@reflection.primary_key_name)).first
|
24
|
+
end
|
25
|
+
|
26
|
+
def record_id(record)
|
27
|
+
record.send(@reflection.options[:primary_key] || :id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module NinjaModel
|
2
|
+
module Associations
|
3
|
+
class HasManyAssociation
|
4
|
+
def initialize(owner, reflection)
|
5
|
+
@owner, @reflection = owner, reflection
|
6
|
+
@relation = reflection.klass.scoped.where(reflection.primary_key_name.to_sym.eq(owner.id))
|
7
|
+
end
|
8
|
+
|
9
|
+
delegate :each, :collect, :map, :to_a, :size, :blank?, :empty?, :to => :relation
|
10
|
+
|
11
|
+
def method_missing(method, *args)
|
12
|
+
if @relation.respond_to?(method)
|
13
|
+
@relation.send(method, *args)
|
14
|
+
else
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def relation
|
20
|
+
@relation
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
@relation.to_a.inspect
|
25
|
+
end
|
26
|
+
|
27
|
+
def blank?
|
28
|
+
@relation.blank?
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_ary
|
32
|
+
@relation.to_a
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module NinjaModel
|
2
|
+
module Associations
|
3
|
+
class HasOneAssociation < AssociationProxy
|
4
|
+
|
5
|
+
def create(attrs = {}, replace_existing = true)
|
6
|
+
new_record
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def find_target
|
12
|
+
@reflection.klass.scoped.where(@reflection.primary_key_name => @owner.send(:id)).first
|
13
|
+
end
|
14
|
+
|
15
|
+
def new_record(replace_existing)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|