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.
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
@@ -0,0 +1,27 @@
1
+ require 'ninja_model'
2
+ require 'rails/all'
3
+
4
+ module NinjaModel
5
+ class Railtie < Rails::Railtie
6
+
7
+ config.ninja_model = NinjaModel::Configuration.create
8
+
9
+ #config.generators.orm :ninja_model, :migration => false
10
+
11
+ initializer 'ninja_model.logger' do |app|
12
+ NinjaModel::set_logger(Rails.logger)
13
+ end
14
+
15
+ config.after_initialize do |app|
16
+ config_path = File.join(Rails.root, app.config.ninja_model.config_file_path)
17
+ if File.exists?(config_path)
18
+ require 'erb'
19
+ require 'yaml'
20
+ app.config.ninja_model.specs = YAML::load(ERB.new(IO.read(config_path)).result)
21
+ NinjaModel::Base.set_adapter
22
+ else
23
+ NinjaModel.logger.warn "[ninja-model] *WARNING* Unable to find configuration file at #{config_path}"
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,118 @@
1
+ module NinjaModel
2
+ module Reflection
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def create_reflection(macro, name, options, ninja_model)
7
+ case macro
8
+ when :has_many, :belongs_to, :has_one
9
+ reflection = AssociationReflection.new(macro, name, options, ninja_model)
10
+ when :composed_of
11
+ end
12
+ write_inheritable_hash :reflections, name => reflection
13
+ reflection
14
+ end
15
+
16
+ def ninja_model?(macro, association)
17
+ klass = association.to_s.camelize
18
+ klass = klass.singularize unless [:has_one, :belongs_to].include?(macro)
19
+ klass = klass.constantize
20
+ defined?(klass) && klass.ancestors.include?(NinjaModel::Base)
21
+ end
22
+
23
+ def reflections
24
+ read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
25
+ end
26
+
27
+ def reflect_on_association(association)
28
+ reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
29
+ end
30
+ end
31
+
32
+ class MacroReflection
33
+ def initialize(macro, name, options, ninja_model)
34
+ @macro, @name, @options, @ninja_model = macro, name, options, ninja_model
35
+ end
36
+
37
+ attr_reader :ninja_model, :name, :macro, :options
38
+
39
+ def klass
40
+ @klass ||= class_name.constantize
41
+ end
42
+
43
+ def class_name
44
+ @class_name ||= options[:class_name] || derive_class_name
45
+ end
46
+
47
+ private
48
+
49
+ def derive_class_name
50
+ name.to_s.camelize
51
+ end
52
+ end
53
+
54
+ class AssociationReflection < MacroReflection
55
+ def initialize(macro, name, options, ninja_model)
56
+ super
57
+ @collection = [:has_many].include?(macro)
58
+ end
59
+
60
+ def build_association(*options)
61
+ klass.new(*options)
62
+ end
63
+
64
+ def create_association(*options)
65
+ klass.create(*options)
66
+ end
67
+
68
+ def create_association!(*options)
69
+ klass.create!(*options)
70
+ end
71
+
72
+ def primary_key_name
73
+ @primary_key_name ||= options[:foreign_key] || derive_primary_key_name
74
+ end
75
+
76
+ def association_foreign_key
77
+ @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
78
+ end
79
+
80
+ def check_validity!
81
+ check_validity_of_inverse!
82
+ end
83
+
84
+ def check_validity_of_inverse!
85
+ end
86
+
87
+ def collection?
88
+ @collection
89
+ end
90
+
91
+ def validate?
92
+ !options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
93
+ end
94
+
95
+ private
96
+
97
+ def belongs_to?
98
+ macro == :belongs_to
99
+ end
100
+
101
+ def derive_class_name
102
+ class_name = name.to_s.camelize
103
+ class_name = class_name.singularize if collection?
104
+ class_name
105
+ end
106
+
107
+ def derive_primary_key_name
108
+ if belongs_to?
109
+ "#{name}_id"
110
+ elsif options[:as]
111
+ "#{options[:as]}_id"
112
+ else
113
+ ninja_model.name.foreign_key
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,74 @@
1
+ module NinjaModel
2
+ module FinderMethods
3
+ def first(*args)
4
+ if args.any?
5
+ else
6
+ find_first
7
+ end
8
+ end
9
+
10
+ def last(*args)
11
+ if args.any?
12
+ else
13
+ end
14
+ end
15
+
16
+ def all(*args)
17
+ args.any? ? apply_finder_options(args.first).to_a : to_a
18
+ end
19
+
20
+ def find(*args)
21
+ options = args.extract_options!
22
+
23
+ if options.present?
24
+ apply_finder_options(options).find(*args)
25
+ else
26
+ case args.first
27
+ when :first, :last, :all
28
+ send(args.first)
29
+ else
30
+ find_with_ids(*args)
31
+ end
32
+ end
33
+ end
34
+
35
+ def exists?(id)
36
+ where(primary_key.to_sym => id).limit(1)
37
+ relation.first ? true : false
38
+ end
39
+
40
+ protected
41
+
42
+ def find_with_ids(*ids)
43
+ expects_array = ids.first.kind_of?(Array)
44
+
45
+ return ids.first if expects_array && ids.first.empty?
46
+
47
+ ids = ids.flatten.compact.uniq
48
+
49
+ case ids.size
50
+ when 0
51
+ raise RecordNotFound, "Couldn't find #{@klass.name} without an ID"
52
+ when 1
53
+ result = find_one(ids.first)
54
+ expects_array ? [result] : result
55
+ else
56
+ find_some(ids)
57
+ end
58
+ end
59
+
60
+ def find_one(id)
61
+ id = id.id if NinjaModel::Base === id
62
+
63
+ where(primary_key.to_sym => id).first
64
+ end
65
+
66
+ def find_first
67
+ if loaded?
68
+ @records.first
69
+ else
70
+ @first ||= limit(1).to_a[0]
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,62 @@
1
+ require 'active_support'
2
+
3
+ module NinjaModel
4
+ module QueryMethods
5
+ extend ActiveSupport::Concern
6
+
7
+ def order(*args)
8
+ relation = clone
9
+ relation.ordering += args.flatten unless args.blank?
10
+ relation
11
+ end
12
+
13
+ def where(opts, *rest)
14
+ relation = clone
15
+ relation.predicates += build_predicates(opts, rest)
16
+ relation
17
+ end
18
+
19
+ def limit(value)
20
+ relation = clone
21
+ relation.limit_value = value
22
+ relation
23
+ end
24
+
25
+ private
26
+
27
+ def build_predicates(opts, other = [])
28
+ case opts
29
+ when String
30
+ raise ArgumentError,
31
+ "NinjaModel doesn't work with strings...yet. You'll need to use a predicate (see NinjaModel::Predicate::PREDICATES for a list)."
32
+ when Array
33
+ opts.collect do |o|
34
+ build_predicates(o)
35
+ end.flatten!
36
+ when Hash
37
+ opts.to_a.map do |o|
38
+ raise ArgumentError, "Not sure what to do with #{o}" unless o.length.eql?(2)
39
+ k = o[0]
40
+ v = o[1]
41
+
42
+ case k
43
+ when NinjaModel::Predicate
44
+ k.value = klass.model_attributes_hash[k.attribute].convert(v)
45
+ k
46
+ when Symbol, String
47
+ raise ArgumentError, "#{klass} doesn't have an attribute #{k}." unless klass.attribute_method?(k)
48
+ p = NinjaModel::Predicate.new(k.to_sym, :eq)
49
+ p.value = klass.model_attributes_hash[p.attribute].convert(v)
50
+ p
51
+ else
52
+ raise ArgumentError, "#{k} isn't a predicate or a symbol."
53
+ end
54
+ end
55
+ when NinjaModel::Predicate
56
+ [opts]
57
+ else
58
+ raise ArgumentError, "Unknown argument to #{self}.where: #{opts.inspect}"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,59 @@
1
+ require 'active_support'
2
+
3
+ module NinjaModel
4
+ module SpawnMethods
5
+
6
+ def merge(r)
7
+ merged_relation = clone
8
+ return merged_relation unless r
9
+ return to_a & r if r.is_a?(Array)
10
+
11
+ order_value = r.ordering
12
+ if order_value.present?
13
+ merged_relation.ordering = merged_relation.ordering + order_value
14
+ end
15
+
16
+ merged_predicates = @predicates + r.predicates
17
+
18
+ unless @predicates.empty?
19
+ seen = []
20
+ merged_predicates = merged_predicates.reverse.reject { |w|
21
+ nuke = false
22
+ if w.respond_to?(:operator) && w.operator == :==
23
+ attribute = w.attribute
24
+ nuke = seen[attribute]
25
+ seen[attribute] = true
26
+ end
27
+ nuke
28
+ }.reverse
29
+ end
30
+ merged_relation.predicates = merged_predicates
31
+
32
+ Relation::SINGLE_VALUE_ATTRS.reject { |m| m == :lock }.each do |method|
33
+ value = r.send(:"#{method}_value")
34
+ merged_relation.send(:"#{method}_value=", value) unless value.nil?
35
+ end
36
+
37
+ merged_relation
38
+
39
+ end
40
+
41
+ VALID_FIND_OPTIONS = [:conditions, :limit, :offset, :order]
42
+
43
+ def apply_finder_options(options)
44
+ relation = clone
45
+ return relation unless options
46
+
47
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
48
+ finders = options.dup
49
+ finders.delete_if { |key, value| value.nil? }
50
+
51
+ ([:order, :limit, :offset] & finders.keys).each do |finder|
52
+ relation = relation.send(finder, finders[finder])
53
+ end
54
+
55
+ relation = relation.where(finders[:conditions]) if options.key?(:conditions)
56
+ relation
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,80 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module NinjaModel
4
+ class Relation
5
+ include QueryMethods, FinderMethods, SpawnMethods
6
+
7
+ delegate :length, :each, :map, :collect, :all?, :include?, :to => :to_a
8
+
9
+ attr_reader :klass, :loaded
10
+
11
+ attr_accessor :ordering, :predicates, :limit_value, :offset_value
12
+
13
+ alias :loaded? :loaded
14
+
15
+ SINGLE_VALUE_ATTRS = [:limit, :offset]
16
+ MULTI_VALUE_ATTRS = [:ordering, :predicates]
17
+
18
+ def initialize(klass)
19
+ @klass = klass
20
+ @loaded = false
21
+
22
+ SINGLE_VALUE_ATTRS.each do |v|
23
+ instance_variable_set("@#{v}_value".to_sym, nil)
24
+ end
25
+
26
+ MULTI_VALUE_ATTRS.each do |v|
27
+ instance_variable_set("@#{v}".to_sym, [])
28
+ end
29
+ end
30
+
31
+ def to_a
32
+ @records ||= begin
33
+ records = @klass.adapter.read(self)
34
+ @loaded = true
35
+ records
36
+ end
37
+ end
38
+ alias :to_ary :to_a
39
+
40
+ def scoping
41
+ @klass.scoped_methods << self
42
+ begin
43
+ yield
44
+ ensure
45
+ @klass.scoped_methods.pop
46
+ end
47
+ end
48
+
49
+ def size
50
+ to_a.length
51
+ end
52
+
53
+ def blank?
54
+ empty?
55
+ end
56
+
57
+ def empty?
58
+ size.zero?
59
+ end
60
+
61
+ protected
62
+
63
+ alias :inspect! :inspect
64
+ def inspect
65
+ to_a.inspect
66
+ end
67
+
68
+ def method_missing(method, *args, &block)
69
+ if Array.method_defined?(method)
70
+ to_a.send(method, *args, &block)
71
+ elsif @klass.scopes[method]
72
+ merge(@klass.send(method, *args, &block))
73
+ elsif @klass.respond_to?(method)
74
+ scoping { @klass.send(method, *args, &block) }
75
+ else
76
+ super
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,50 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/array'
3
+ require 'active_support/core_ext/kernel/singleton_class'
4
+
5
+ module NinjaModel
6
+ module Scoping
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ def scoped(options = nil)
11
+ if options
12
+ scoped.apply_finder_options(options)
13
+ else
14
+ current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone
15
+ end
16
+ end
17
+
18
+ def scopes
19
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
20
+ end
21
+
22
+ def scope(name, scope_options = {}, &block)
23
+ name = name.to_sym
24
+ valid_scope_name?(name)
25
+ extension = Module.new(&block) if block_given?
26
+
27
+ scopes[name] = lambda do |*args|
28
+ options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
29
+ relation = if options.is_a?(Hash)
30
+ scoped.apply_finder_options(options)
31
+ elsif options
32
+ scoped.merge(options)
33
+ else
34
+ scoped
35
+ end
36
+
37
+ extension ? relation.extending(extension) : relation
38
+ end
39
+
40
+ singleton_class.send(:redefine_method, name, &scopes[name])
41
+ end
42
+
43
+ def valid_scope_name?(name)
44
+ if !scopes[name] && respond_to?(name, true)
45
+ logger.warn "Creating scope :#{name}. Overwriting existing method #{self.name}.#{name}."
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ require 'active_model'
2
+
3
+ module NinjaModel
4
+ module Validation
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::Validations
7
+
8
+ def save(options={})
9
+ run_callbacks :validation do
10
+ perform_validations(options) ? super : false
11
+ end
12
+ end
13
+
14
+ def valid?(context = nil)
15
+ context ||= (persisted? ? :update : :create)
16
+ output = super(context)
17
+ errors.empty? && output
18
+ end
19
+
20
+ protected
21
+
22
+ def perform_validations(options={})
23
+ perform_validation = case options
24
+ when Hash
25
+ options[:validate] != false
26
+ else
27
+ ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller
28
+ options
29
+ end
30
+
31
+ if perform_validation
32
+ valid?(options.is_a?(Hash) ? options[:context] : nil)
33
+ else
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module NinjaModel
2
+ VERSION = "0.4.2"
3
+ end
@@ -0,0 +1,38 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+
4
+ module NinjaModel
5
+ extend ActiveSupport::Autoload
6
+
7
+ autoload :Base
8
+ autoload :Adapters
9
+ autoload :Associations
10
+ autoload :Attributes
11
+ autoload :Callbacks
12
+ autoload :Configuration
13
+ autoload :Identity
14
+ autoload :Persistence
15
+ autoload :Predicate
16
+ autoload :Reflection
17
+ autoload :Relation
18
+ autoload :Scoping
19
+ autoload :Validation
20
+
21
+ autoload_under 'relation' do
22
+ autoload :QueryMethods
23
+ autoload :FinderMethods
24
+ autoload :SpawnMethods
25
+ end
26
+
27
+
28
+ class << self
29
+ attr_accessor :logger
30
+
31
+ def set_logger(logger)
32
+ ::NinjaModel.logger = logger
33
+ end
34
+ end
35
+ end
36
+
37
+ require 'ninja_model/railtie'
38
+ require 'ninja_model/errors'
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ require 'ninja_model'
3
+
4
+ describe NinjaModel::Adapters::AdapterPool do
5
+ before(:each) do
6
+ @spec = mock('AdapterSpecification') do
7
+ stubs(:adapter_method).returns(:dummy_adapter)
8
+ stubs(:config)
9
+ end
10
+
11
+ @adapter = mock('AbstractAdapter') do
12
+ stubs(:verify!)
13
+ end
14
+
15
+ @pool = NinjaModel::Adapters::AdapterPool.new(@spec)
16
+ @thread = mock('thread') do
17
+ stubs('object_id').returns(123)
18
+ end
19
+ Thread.stubs(:current).returns(@thread)
20
+ NinjaModel::Base.stubs(:dummy_adapter).returns(@adapter)
21
+ end
22
+
23
+ it 'should properly respond to connected?' do
24
+ @pool.connected?.should be_false
25
+ @pool.instance
26
+ @pool.connected?.should be_true
27
+ end
28
+
29
+ it 'should initialize from an AdapterSpecification' do
30
+ spec = mock('AdapterSpecification')
31
+ NinjaModel::Adapters::AdapterPool.new(spec).spec.should eql(spec)
32
+ end
33
+
34
+ it 'should return a valid instance_id' do
35
+ (@pool.send :current_instance_id).should eql(@thread.object_id)
36
+ end
37
+
38
+ describe 'when creating an instance' do
39
+ it 'should create a valid instance' do
40
+ (@pool.send :new_instance).should eql(@adapter)
41
+ end
42
+
43
+ it 'should store the instance' do
44
+ @pool.stubs(:new_instance).returns(@adapter)
45
+ @pool.instance
46
+ @pool.instances.should include(@adapter)
47
+ end
48
+
49
+ it 'should assign the instance to the current_thread' do
50
+ @pool.stubs(:new_instance).returns(@adapter)
51
+ @pool.instance
52
+ @pool.stubs(:checkout).returns("new_instance")
53
+ @pool.instance.should_not eql("new_instance")
54
+ @pool.instance.should eql(@adapter)
55
+ end
56
+
57
+ it 'should reuse an unallocated instance' do
58
+ @pool.stubs(:current_instance_id).returns(123)
59
+ inst = @pool.instance
60
+ @pool.release_instance(123)
61
+ @pool.instance.should eql(inst)
62
+ end
63
+ end
64
+
65
+ it 'should properly allocate/deallocate instances' do
66
+ @pool.stubs(:current_instance_id).returns(123)
67
+ @pool.expects(:checkout).returns(@adapter)
68
+ @pool.expects(:checkin)
69
+ @pool.with_instance do |instance|
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe NinjaModel::Adapters do
4
+ describe 'set_adapter' do
5
+
6
+ end
7
+
8
+ end