ninja-model 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/Gemfile +2 -0
  2. data/README.md +1 -0
  3. data/Rakefile +38 -0
  4. data/lib/generators/ninja_model/model/model_generator.rb +19 -0
  5. data/lib/generators/ninja_model/model/templates/model.rb +7 -0
  6. data/lib/generators/ninja_model/scaffold/scaffold_generator.rb +69 -0
  7. data/lib/generators/ninja_model.rb +12 -0
  8. data/lib/ninja-model.rb +1 -0
  9. data/lib/ninja_model/adapters/abstract_adapter.rb +63 -0
  10. data/lib/ninja_model/adapters/adapter_manager.rb +67 -0
  11. data/lib/ninja_model/adapters/adapter_pool.rb +128 -0
  12. data/lib/ninja_model/adapters/adapter_specification.rb +11 -0
  13. data/lib/ninja_model/adapters.rb +70 -0
  14. data/lib/ninja_model/associations/active_record_proxy.rb +53 -0
  15. data/lib/ninja_model/associations/association_proxy.rb +8 -0
  16. data/lib/ninja_model/associations/belongs_to_association.rb +31 -0
  17. data/lib/ninja_model/associations/has_many_association.rb +36 -0
  18. data/lib/ninja_model/associations/has_one_association.rb +19 -0
  19. data/lib/ninja_model/associations/ninja_model_proxy.rb +46 -0
  20. data/lib/ninja_model/associations.rb +198 -0
  21. data/lib/ninja_model/attributes.rb +134 -0
  22. data/lib/ninja_model/base.rb +106 -0
  23. data/lib/ninja_model/callbacks.rb +14 -0
  24. data/lib/ninja_model/configuration.rb +20 -0
  25. data/lib/ninja_model/core_ext/symbol.rb +7 -0
  26. data/lib/ninja_model/errors.rb +5 -0
  27. data/lib/ninja_model/identity.rb +24 -0
  28. data/lib/ninja_model/log_subscriber.rb +18 -0
  29. data/lib/ninja_model/persistence.rb +76 -0
  30. data/lib/ninja_model/predicate.rb +43 -0
  31. data/lib/ninja_model/railtie.rb +27 -0
  32. data/lib/ninja_model/reflection.rb +118 -0
  33. data/lib/ninja_model/relation/finder_methods.rb +74 -0
  34. data/lib/ninja_model/relation/query_methods.rb +62 -0
  35. data/lib/ninja_model/relation/spawn_methods.rb +59 -0
  36. data/lib/ninja_model/relation.rb +80 -0
  37. data/lib/ninja_model/scoping.rb +50 -0
  38. data/lib/ninja_model/validation.rb +38 -0
  39. data/lib/ninja_model/version.rb +3 -0
  40. data/lib/ninja_model.rb +38 -0
  41. data/spec/ninja_model/adapters/adapter_pool_spec.rb +72 -0
  42. data/spec/ninja_model/adapters_spec.rb +8 -0
  43. data/spec/ninja_model/attributes_spec.rb +85 -0
  44. data/spec/ninja_model/base_spec.rb +66 -0
  45. data/spec/ninja_model/identity_spec.rb +41 -0
  46. data/spec/ninja_model/persistence_spec.rb +9 -0
  47. data/spec/ninja_model/predicate_spec.rb +13 -0
  48. data/spec/ninja_model/query_methods_spec.rb +76 -0
  49. data/spec/ninja_model/relation_spec.rb +26 -0
  50. data/spec/ninja_model/scoping_spec.rb +40 -0
  51. data/spec/ninja_model_spec.rb +11 -0
  52. data/spec/spec_helper.rb +9 -0
  53. data/spec/support/active_model_lint.rb +17 -0
  54. metadata +214 -0
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
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,7 @@
1
+ require 'ninja_model'
2
+
3
+ class <%= class_name %> < NinjaModel::Base
4
+ <%- attributes.each do |a| -%>
5
+ attribute :<%= a.name %>, :<%= a.type %>
6
+ <%- end -%>
7
+ 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
@@ -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,11 @@
1
+ module NinjaModel
2
+ module Adapters
3
+ class AdapterSpecification
4
+ attr_accessor :config, :adapter_method
5
+
6
+ def initialize(config, adapter_method)
7
+ @config, @adapter_method = config, adapter_method
8
+ end
9
+ end
10
+ end
11
+ 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,8 @@
1
+ require 'active_record/associations/association_proxy'
2
+
3
+ module NinjaModel
4
+ module Associations
5
+ class AssociationProxy < ActiveRecord::Associations::AssociationProxy
6
+ end
7
+ end
8
+ 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