active_file_record 0.0.2a → 0.0.3a
Sign up to get free protection for your applications and to get access to all the features.
- data/active_file_record.gemspec +1 -1
- data/lib/active_file_record/associations.rb +57 -0
- data/lib/active_file_record/associations/association.rb +101 -0
- data/lib/active_file_record/associations/association_scope.rb +29 -0
- data/lib/active_file_record/associations/belongs_to_association.rb +78 -0
- data/lib/active_file_record/associations/builder/association.rb +46 -0
- data/lib/active_file_record/associations/builder/belongs_to.rb +13 -0
- data/lib/active_file_record/associations/singular_association.rb +64 -0
- data/lib/active_file_record/attribute_assignment.rb +25 -0
- data/lib/active_file_record/attribute_methods.rb +48 -0
- data/lib/active_file_record/attribute_methods/read.rb +26 -0
- data/lib/active_file_record/attribute_methods/write.rb +27 -0
- data/lib/active_file_record/base.rb +81 -0
- data/lib/active_file_record/callbacks.rb +21 -0
- data/lib/active_file_record/criteria.rb +10 -0
- data/lib/active_file_record/file_handler.rb +66 -0
- data/lib/active_file_record/file_handler/active_file.rb +13 -0
- data/lib/active_file_record/file_handler/attribute.rb +5 -0
- data/lib/active_file_record/inheritance.rb +42 -0
- data/lib/active_file_record/integration.rb +13 -0
- data/lib/active_file_record/nodes.rb +2 -0
- data/lib/active_file_record/nodes/binary.rb +19 -0
- data/lib/active_file_record/nodes/equality.rb +11 -0
- data/lib/active_file_record/persistence.rb +53 -0
- data/lib/active_file_record/predications.rb +9 -0
- data/lib/active_file_record/reflection.rb +140 -0
- data/lib/active_file_record/relation.rb +73 -0
- data/lib/active_file_record/relation/finder_methods.rb +88 -0
- data/lib/active_file_record/relation/predicate_builder.rb +21 -0
- data/lib/active_file_record/relation/search_methods.rb +65 -0
- data/lib/active_file_record/scoping.rb +28 -0
- data/lib/active_file_record/scoping/named.rb +72 -0
- data/lib/active_file_record/validations.rb +12 -0
- data/lib/active_file_record/version.rb +1 -1
- metadata +33 -1
data/active_file_record.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.summary = %q{Gem summary}
|
9
9
|
gem.homepage = ""
|
10
10
|
|
11
|
-
gem.files = `git ls-files`.split(
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
12
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
13
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
14
|
gem.name = "active_file_record"
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
require 'active_support/core_ext/enumerable'
|
3
|
+
require 'active_support/core_ext/module/delegation'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'active_support/core_ext/string/conversions'
|
6
|
+
require 'active_support/core_ext/module/remove_method'
|
7
|
+
require 'active_support/core_ext/class/attribute'
|
8
|
+
|
9
|
+
module ActiveFileRecord
|
10
|
+
module Associations
|
11
|
+
extend ActiveSupport::Autoload
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
|
14
|
+
autoload :Association
|
15
|
+
autoload :SingularAssociation
|
16
|
+
autoload :BelongsToAssociation
|
17
|
+
|
18
|
+
module Builder
|
19
|
+
autoload :Association, 'active_file_record/associations/builder/association'
|
20
|
+
autoload :SingularAssociation, 'active_file_record/associations/builder/singular_association'
|
21
|
+
autoload :BelongsTo, 'active_file_record/associations/builder/belongs_to'
|
22
|
+
end
|
23
|
+
|
24
|
+
eager_autoload do
|
25
|
+
autoload :AssociationScope, 'active_file_record/associations/association_scope'
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :association_cache
|
29
|
+
|
30
|
+
def association(name)
|
31
|
+
association = association_instance_get(name)
|
32
|
+
if association.nil?
|
33
|
+
reflection = self.class.reflect_on_association(name)
|
34
|
+
association = reflection.association_class.new(self, reflection)
|
35
|
+
association_instance_set(name, association)
|
36
|
+
end
|
37
|
+
|
38
|
+
association
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def association_instance_get(name)
|
43
|
+
@association_cache[name.to_sym]
|
44
|
+
end
|
45
|
+
|
46
|
+
def association_instance_set(name, association)
|
47
|
+
@association_cache[name] = association
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
def belongs_to(name, options = {})
|
52
|
+
Builder::BelongsTo.build(self, name, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'active_support/core_ext/array/wrap'
|
2
|
+
require 'active_support/core_ext/object/inclusion'
|
3
|
+
|
4
|
+
module ActiveFileRecord
|
5
|
+
module Associations
|
6
|
+
|
7
|
+
class Association
|
8
|
+
attr_reader :owner, :target, :reflection
|
9
|
+
delegate :options, :to => :reflection
|
10
|
+
|
11
|
+
def initialize(owner, reflection)
|
12
|
+
#reflection.check_validity!
|
13
|
+
|
14
|
+
@target = nil
|
15
|
+
@owner, @reflection = owner, reflection
|
16
|
+
@updated = false
|
17
|
+
|
18
|
+
reset
|
19
|
+
reset_scope
|
20
|
+
end
|
21
|
+
|
22
|
+
def reset
|
23
|
+
@loaded = false
|
24
|
+
@target = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset_scope
|
28
|
+
@association_scope = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def reload
|
32
|
+
reset
|
33
|
+
reset_scope
|
34
|
+
load_target
|
35
|
+
self unless target.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def klass
|
39
|
+
reflection.klass
|
40
|
+
end
|
41
|
+
|
42
|
+
def scoped
|
43
|
+
target_scope.merge(association_scope)
|
44
|
+
end
|
45
|
+
|
46
|
+
def target_scope
|
47
|
+
klass.scoped
|
48
|
+
end
|
49
|
+
|
50
|
+
def association_scope
|
51
|
+
if klass
|
52
|
+
@association_scope ||= AssociationScope.new(self).scope
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def set_inverse_instance(record)
|
57
|
+
if record && invertible_for?(record)
|
58
|
+
inverse = record.association(inverse_reflection_for(record).name)
|
59
|
+
inverse.target = owner
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def inverse_reflection_for(record)
|
64
|
+
reflection.inverse_of
|
65
|
+
end
|
66
|
+
|
67
|
+
def stale_target?
|
68
|
+
loaded? && @stale_state != stale_state
|
69
|
+
end
|
70
|
+
|
71
|
+
def loaded?
|
72
|
+
@loaded
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_target
|
76
|
+
if find_target?
|
77
|
+
#begin
|
78
|
+
# if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class)
|
79
|
+
# @target = IdentityMap.get(association_class, owner[reflection.foreign_key])
|
80
|
+
# end
|
81
|
+
#rescue NameError
|
82
|
+
# nil
|
83
|
+
#ensure
|
84
|
+
@target ||= find_target
|
85
|
+
#end
|
86
|
+
end
|
87
|
+
|
88
|
+
loaded! unless loaded?
|
89
|
+
target
|
90
|
+
rescue ActiveFileRecord::RecordNotFound
|
91
|
+
reset
|
92
|
+
end
|
93
|
+
|
94
|
+
def loaded!
|
95
|
+
@loaded = true
|
96
|
+
@stale_state = stale_state
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActiveFileRecord
|
2
|
+
module Associations
|
3
|
+
class AssociationScope
|
4
|
+
|
5
|
+
attr_reader :association
|
6
|
+
|
7
|
+
delegate :klass, :owner, :reflection, :to => :association
|
8
|
+
delegate :chain, :conditions, :to => :reflection
|
9
|
+
|
10
|
+
def initialize(association)
|
11
|
+
@association = association
|
12
|
+
end
|
13
|
+
|
14
|
+
def scope
|
15
|
+
scope = klass.unscoped
|
16
|
+
add_constraints(scope)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def add_constraints(scope)
|
22
|
+
key = reflection.association_primary_key
|
23
|
+
foreign_key = reflection.foreign_key
|
24
|
+
scope.where({key => {:eq => owner[foreign_key].to_i}})
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ActiveFileRecord
|
2
|
+
module Associations
|
3
|
+
class BelongsToAssociation < SingularAssociation
|
4
|
+
def replace(record)
|
5
|
+
raise_on_type_mismatch(record) if record
|
6
|
+
|
7
|
+
update_counters(record)
|
8
|
+
replace_keys(record)
|
9
|
+
set_inverse_instance(record)
|
10
|
+
|
11
|
+
@updated = true if record
|
12
|
+
|
13
|
+
self.target = record
|
14
|
+
end
|
15
|
+
|
16
|
+
def updated?
|
17
|
+
@updated
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def find_target?
|
23
|
+
!loaded? && foreign_key_present? && klass
|
24
|
+
end
|
25
|
+
|
26
|
+
#def update_counters(record)
|
27
|
+
# counter_cache_name = reflection.counter_cache_column
|
28
|
+
#
|
29
|
+
# if counter_cache_name && owner.persisted? && different_target?(record)
|
30
|
+
# if record
|
31
|
+
# record.class.increment_counter(counter_cache_name, record.id)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# if foreign_key_present?
|
35
|
+
# klass.decrement_counter(counter_cache_name, target_id)
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#end
|
39
|
+
|
40
|
+
# Checks whether record is different to the current target, without loading it
|
41
|
+
def different_target?(record)
|
42
|
+
record.nil? && owner[reflection.foreign_key] ||
|
43
|
+
record && record.id != owner[reflection.foreign_key]
|
44
|
+
end
|
45
|
+
|
46
|
+
def replace_keys(record)
|
47
|
+
if record
|
48
|
+
owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)]
|
49
|
+
else
|
50
|
+
owner[reflection.foreign_key] = nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def foreign_key_present?
|
55
|
+
owner[reflection.foreign_key]
|
56
|
+
end
|
57
|
+
|
58
|
+
# NOTE - for now, we're only supporting inverse setting from belongs_to back onto
|
59
|
+
# has_one associations.
|
60
|
+
def invertible_for?(record)
|
61
|
+
inverse = inverse_reflection_for(record)
|
62
|
+
inverse && inverse.macro == :has_one
|
63
|
+
end
|
64
|
+
|
65
|
+
def target_id
|
66
|
+
if options[:primary_key]
|
67
|
+
owner.send(reflection.name).try(:id)
|
68
|
+
else
|
69
|
+
owner[reflection.foreign_key]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def stale_state
|
74
|
+
owner[reflection.foreign_key].to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveFileRecord::Associations::Builder
|
2
|
+
class Association
|
3
|
+
class_attribute :macro
|
4
|
+
|
5
|
+
attr_reader :model, :name, :options, :reflection
|
6
|
+
|
7
|
+
def self.build(model, name, options)
|
8
|
+
new(model, name, options).build
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(model, name, options)
|
12
|
+
@model, @name, @options = model, name, options
|
13
|
+
end
|
14
|
+
|
15
|
+
def mixin
|
16
|
+
@model.generated_feature_methods
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
reflection = model.create_reflection(self.class.macro, name, options, model)
|
21
|
+
define_accessors
|
22
|
+
reflection
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def define_accessors
|
28
|
+
define_readers
|
29
|
+
define_writers
|
30
|
+
end
|
31
|
+
|
32
|
+
def define_readers
|
33
|
+
name = self.name
|
34
|
+
mixin.redefine_method(name) do |*params|
|
35
|
+
association(name).reader(*params)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_writers
|
40
|
+
name = self.name
|
41
|
+
mixin.redefine_method("#{name}=") do |value|
|
42
|
+
association(name).writer(value)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_support/core_ext/object/inclusion'
|
2
|
+
|
3
|
+
module ActiveFileRecord::Associations::Builder
|
4
|
+
class BelongsTo < Association #< SingularAssociation #:nodoc:
|
5
|
+
self.macro = :belongs_to
|
6
|
+
|
7
|
+
def build
|
8
|
+
reflection = super
|
9
|
+
reflection
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ActiveFileRecord
|
2
|
+
module Associations
|
3
|
+
class SingularAssociation < Association #:nodoc:
|
4
|
+
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
|
5
|
+
def reader(force_reload = false)
|
6
|
+
if force_reload
|
7
|
+
klass.uncached { reload }
|
8
|
+
elsif !loaded? || stale_target?
|
9
|
+
reload
|
10
|
+
end
|
11
|
+
|
12
|
+
target
|
13
|
+
end
|
14
|
+
|
15
|
+
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
|
16
|
+
def writer(record)
|
17
|
+
replace(record)
|
18
|
+
end
|
19
|
+
|
20
|
+
def create(attributes = {}, options = {}, &block)
|
21
|
+
create_record(attributes, options, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def create!(attributes = {}, options = {}, &block)
|
25
|
+
create_record(attributes, options, true, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def build(attributes = {}, options = {})
|
29
|
+
record = build_record(attributes, options)
|
30
|
+
yield(record) if block_given?
|
31
|
+
set_new_record(record)
|
32
|
+
record
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def create_scope
|
38
|
+
scoped.scope_for_create.stringify_keys.except(klass.primary_key)
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_target
|
42
|
+
scoped.first.tap { |record| set_inverse_instance(record) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Implemented by subclasses
|
46
|
+
def replace(record)
|
47
|
+
raise NotImplementedError, "Subclasses must implement a replace(record) method"
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_new_record(record)
|
51
|
+
replace(record)
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_record(attributes, options, raise_error = false)
|
55
|
+
record = build_record(attributes, options)
|
56
|
+
yield(record) if block_given?
|
57
|
+
saved = record.save
|
58
|
+
set_new_record(record)
|
59
|
+
raise RecordInvalid.new(record) if !saved && raise_error
|
60
|
+
record
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveFileRecord
|
2
|
+
module AttributeAssignment
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include ActiveModel::MassAssignmentSecurity
|
6
|
+
|
7
|
+
def attributes=(new_attributes)
|
8
|
+
return unless new_attributes.is_a?(Hash)
|
9
|
+
|
10
|
+
assign_attributes(new_attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def assign_attributes(new_attributes, options = {})
|
14
|
+
return if new_attributes.blank?
|
15
|
+
|
16
|
+
attributes = new_attributes.stringify_keys
|
17
|
+
attributes = sanitize_for_mass_assignment(attributes) unless options[:without_protection]
|
18
|
+
|
19
|
+
attributes.each do |k, v|
|
20
|
+
send("#{k}=", v)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActiveFileRecord
|
2
|
+
module AttributeMethods
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
include ActiveModel::AttributeMethods
|
6
|
+
|
7
|
+
included do
|
8
|
+
include Read
|
9
|
+
include Write
|
10
|
+
|
11
|
+
class_attribute :_fields
|
12
|
+
self._fields = []
|
13
|
+
|
14
|
+
def [](attr_name)
|
15
|
+
read_attribute(attr_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(attr_name, value)
|
19
|
+
write_attribute(attr_name, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def attribute_names
|
24
|
+
@attributes.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def fields(*names)
|
30
|
+
self._fields += names
|
31
|
+
define_attribute_methods names
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize_attributes(fields_names)
|
35
|
+
Hash[fields_names.map {|attribute| [attribute, nil]}]
|
36
|
+
end
|
37
|
+
|
38
|
+
def generated_methods
|
39
|
+
@generated_methods ||= begin
|
40
|
+
mod = Module.new
|
41
|
+
include(mod)
|
42
|
+
mod
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|