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
@@ -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