active_file_record 0.0.2a → 0.0.3a
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/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
|