active_hash 3.1.1 → 3.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +353 -0
- data/README.md +11 -8
- data/active_hash.gemspec +8 -1
- data/lib/active_file/base.rb +11 -4
- data/lib/active_file/hash_and_array_files.rb +1 -1
- data/lib/active_hash/base.rb +26 -49
- data/lib/active_hash/condition.rb +44 -0
- data/lib/active_hash/conditions.rb +21 -0
- data/lib/active_hash/relation.rb +103 -78
- data/lib/active_hash/version.rb +1 -1
- data/lib/active_hash.rb +2 -0
- data/lib/active_yaml/base.rb +10 -2
- data/lib/associations/associations.rb +24 -14
- data/lib/associations/reflection_extensions.rb +25 -0
- metadata +12 -5
- data/CHANGELOG +0 -242
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
class ActiveHash::Relation::Condition
|
|
2
|
+
attr_reader :constraints, :inverted
|
|
3
|
+
|
|
4
|
+
def initialize(constraints)
|
|
5
|
+
@constraints = constraints
|
|
6
|
+
@inverted = false
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def invert!
|
|
10
|
+
@inverted = !inverted
|
|
11
|
+
|
|
12
|
+
self
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def matches?(record)
|
|
16
|
+
match = begin
|
|
17
|
+
return true unless constraints
|
|
18
|
+
|
|
19
|
+
expectation_method = inverted ? :any? : :all?
|
|
20
|
+
|
|
21
|
+
constraints.send(expectation_method) do |attribute, expected|
|
|
22
|
+
value = record.public_send(attribute)
|
|
23
|
+
|
|
24
|
+
matches_value?(value, expected)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
inverted ? !match : match
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def matches_value?(value, comparison)
|
|
34
|
+
return comparison.any? { |v| matches_value?(value, v) } if comparison.is_a?(Array)
|
|
35
|
+
return comparison.cover?(value) if comparison.is_a?(Range)
|
|
36
|
+
return comparison.match?(value) if comparison.is_a?(Regexp)
|
|
37
|
+
|
|
38
|
+
normalize(value) == normalize(comparison)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def normalize(value)
|
|
42
|
+
value.respond_to?(:to_s) ? value.to_s : value
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class ActiveHash::Relation::Conditions
|
|
2
|
+
attr_reader :conditions
|
|
3
|
+
|
|
4
|
+
delegate :<<, :map, to: :conditions
|
|
5
|
+
|
|
6
|
+
def initialize(conditions = [])
|
|
7
|
+
@conditions = conditions
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def matches?(record)
|
|
11
|
+
conditions.all? do |condition|
|
|
12
|
+
condition.matches?(record)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.wrap(conditions)
|
|
17
|
+
return conditions if conditions.is_a?(self)
|
|
18
|
+
|
|
19
|
+
new(conditions)
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/active_hash/relation.rb
CHANGED
|
@@ -7,24 +7,89 @@ module ActiveHash
|
|
|
7
7
|
delegate :empty?, :length, :first, :second, :third, :last, to: :records
|
|
8
8
|
delegate :sample, to: :records
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
attr_reader :conditions, :order_values, :klass, :all_records
|
|
11
|
+
|
|
12
|
+
def initialize(klass, all_records, conditions = nil, order_values = nil)
|
|
11
13
|
self.klass = klass
|
|
12
14
|
self.all_records = all_records
|
|
13
|
-
self.
|
|
14
|
-
self.
|
|
15
|
+
self.conditions = Conditions.wrap(conditions || [])
|
|
16
|
+
self.order_values = order_values || []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def where(conditions_hash = :chain)
|
|
20
|
+
return WhereChain.new(self) if conditions_hash == :chain
|
|
21
|
+
|
|
22
|
+
spawn.where!(conditions_hash)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class WhereChain
|
|
26
|
+
attr_reader :relation
|
|
27
|
+
|
|
28
|
+
def initialize(relation)
|
|
29
|
+
@relation = relation
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def not(conditions_hash)
|
|
33
|
+
relation.conditions << Condition.new(conditions_hash).invert!
|
|
34
|
+
relation
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def order(*options)
|
|
39
|
+
spawn.order!(*options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def reorder(*options)
|
|
43
|
+
spawn.reorder!(*options)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def where!(conditions_hash, inverted = false)
|
|
47
|
+
self.conditions << Condition.new(conditions_hash)
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def invert_where
|
|
52
|
+
spawn.invert_where!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def invert_where!
|
|
56
|
+
conditions.map(&:invert!)
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def spawn
|
|
61
|
+
self.class.new(klass, all_records, conditions, order_values)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def order!(*options)
|
|
65
|
+
check_if_method_has_arguments!(:order, options)
|
|
66
|
+
self.order_values += preprocess_order_args(options)
|
|
15
67
|
self
|
|
16
68
|
end
|
|
17
69
|
|
|
18
|
-
def
|
|
19
|
-
|
|
70
|
+
def reorder!(*options)
|
|
71
|
+
check_if_method_has_arguments!(:order, options)
|
|
20
72
|
|
|
21
|
-
self.
|
|
22
|
-
|
|
73
|
+
self.order_values = preprocess_order_args(options)
|
|
74
|
+
@records = apply_order_values(records, order_values)
|
|
75
|
+
|
|
76
|
+
self
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def records
|
|
80
|
+
@records ||= begin
|
|
81
|
+
filtered_records = apply_conditions(all_records, conditions)
|
|
82
|
+
ordered_records = apply_order_values(filtered_records, order_values) # rubocop:disable Lint/UselessAssignment
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def reload
|
|
87
|
+
@records = nil # Reset records
|
|
23
88
|
self
|
|
24
89
|
end
|
|
25
90
|
|
|
26
91
|
def all(options = {})
|
|
27
|
-
if options.
|
|
92
|
+
if options.key?(:conditions)
|
|
28
93
|
where(options[:conditions])
|
|
29
94
|
else
|
|
30
95
|
where({})
|
|
@@ -58,10 +123,11 @@ module ActiveHash
|
|
|
58
123
|
end
|
|
59
124
|
|
|
60
125
|
def find_by_id(id)
|
|
61
|
-
return where(id: id).first if query_hash.present?
|
|
62
|
-
|
|
63
126
|
index = klass.send(:record_index)[id.to_s] # TODO: Make index in Base publicly readable instead of using send?
|
|
64
|
-
|
|
127
|
+
return unless index
|
|
128
|
+
|
|
129
|
+
record = all_records[index]
|
|
130
|
+
record if conditions.matches?(record)
|
|
65
131
|
end
|
|
66
132
|
|
|
67
133
|
def count
|
|
@@ -73,7 +139,16 @@ module ActiveHash
|
|
|
73
139
|
end
|
|
74
140
|
|
|
75
141
|
def pluck(*column_names)
|
|
76
|
-
|
|
142
|
+
symbolized_column_names = column_names.map(&:to_sym)
|
|
143
|
+
|
|
144
|
+
if symbolized_column_names.length == 1
|
|
145
|
+
column_name = symbolized_column_names.first
|
|
146
|
+
all.map { |record| record[column_name] }
|
|
147
|
+
else
|
|
148
|
+
all.map do |record|
|
|
149
|
+
symbolized_column_names.map { |column_name| record[column_name] }
|
|
150
|
+
end
|
|
151
|
+
end
|
|
77
152
|
end
|
|
78
153
|
|
|
79
154
|
def ids
|
|
@@ -84,86 +159,32 @@ module ActiveHash
|
|
|
84
159
|
pluck(*column_names).first
|
|
85
160
|
end
|
|
86
161
|
|
|
87
|
-
def reload
|
|
88
|
-
@records = filter_all_records_by_query_hash
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def order(*options)
|
|
92
|
-
check_if_method_has_arguments!(:order, options)
|
|
93
|
-
relation = where({})
|
|
94
|
-
return relation if options.blank?
|
|
95
|
-
|
|
96
|
-
processed_args = preprocess_order_args(options)
|
|
97
|
-
candidates = relation.dup
|
|
98
|
-
|
|
99
|
-
order_by_args!(candidates, processed_args)
|
|
100
|
-
|
|
101
|
-
candidates
|
|
102
|
-
end
|
|
103
|
-
|
|
104
162
|
def to_ary
|
|
105
163
|
records.dup
|
|
106
164
|
end
|
|
107
165
|
|
|
108
166
|
def method_missing(method_name, *args)
|
|
109
|
-
return super unless
|
|
167
|
+
return super unless klass.scopes.key?(method_name)
|
|
110
168
|
|
|
111
|
-
instance_exec(*args, &
|
|
169
|
+
instance_exec(*args, &klass.scopes[method_name])
|
|
112
170
|
end
|
|
113
171
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
private
|
|
117
|
-
|
|
118
|
-
attr_writer :query_hash, :klass, :all_records, :records_dirty
|
|
119
|
-
|
|
120
|
-
def records
|
|
121
|
-
if !defined?(@records) || @records.nil? || records_dirty
|
|
122
|
-
reload
|
|
123
|
-
else
|
|
124
|
-
@records
|
|
125
|
-
end
|
|
172
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
173
|
+
klass.scopes.key?(method_name) || super
|
|
126
174
|
end
|
|
127
175
|
|
|
128
|
-
|
|
129
|
-
self.records_dirty = false
|
|
130
|
-
return all_records if query_hash.blank?
|
|
131
|
-
|
|
132
|
-
# use index if searching by id
|
|
133
|
-
if query_hash.key?(:id) || query_hash.key?("id")
|
|
134
|
-
ids = (query_hash.delete(:id) || query_hash.delete("id"))
|
|
135
|
-
ids = range_to_array(ids) if ids.is_a?(Range)
|
|
136
|
-
candidates = Array.wrap(ids).map { |id| klass.find_by_id(id) }.compact
|
|
137
|
-
end
|
|
176
|
+
private
|
|
138
177
|
|
|
139
|
-
|
|
178
|
+
attr_writer :conditions, :order_values, :klass, :all_records
|
|
140
179
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
end
|
|
144
|
-
end
|
|
180
|
+
def apply_conditions(records, conditions)
|
|
181
|
+
return records if conditions.blank?
|
|
145
182
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if match.kind_of?(Array)
|
|
149
|
-
match.any? { |v| normalize(v) == normalize(record[col]) }
|
|
150
|
-
else
|
|
151
|
-
normalize(match) === normalize(record[col])
|
|
152
|
-
end
|
|
183
|
+
records.select do |record|
|
|
184
|
+
conditions.matches?(record)
|
|
153
185
|
end
|
|
154
186
|
end
|
|
155
187
|
|
|
156
|
-
def normalize(v)
|
|
157
|
-
v.respond_to?(:to_sym) ? v.to_sym : v
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def range_to_array(range)
|
|
161
|
-
return range.to_a unless range.end.nil?
|
|
162
|
-
|
|
163
|
-
e = records.last[:id]
|
|
164
|
-
(range.begin..e).to_a
|
|
165
|
-
end
|
|
166
|
-
|
|
167
188
|
def check_if_method_has_arguments!(method_name, args)
|
|
168
189
|
return unless args.blank?
|
|
169
190
|
|
|
@@ -179,7 +200,9 @@ module ActiveHash
|
|
|
179
200
|
ary.map! { |e| e.split(/\W+/) }.reverse!
|
|
180
201
|
end
|
|
181
202
|
|
|
182
|
-
def
|
|
203
|
+
def apply_order_values(records, args)
|
|
204
|
+
ordered_records = records.dup
|
|
205
|
+
|
|
183
206
|
args.each do |arg|
|
|
184
207
|
field, dir = if arg.is_a?(Hash)
|
|
185
208
|
arg.to_a.flatten.map(&:to_sym)
|
|
@@ -189,7 +212,7 @@ module ActiveHash
|
|
|
189
212
|
arg.to_sym
|
|
190
213
|
end
|
|
191
214
|
|
|
192
|
-
|
|
215
|
+
ordered_records.sort! do |a, b|
|
|
193
216
|
if dir.present? && dir.to_sym.upcase.equal?(:DESC)
|
|
194
217
|
b[field] <=> a[field]
|
|
195
218
|
else
|
|
@@ -197,6 +220,8 @@ module ActiveHash
|
|
|
197
220
|
end
|
|
198
221
|
end
|
|
199
222
|
end
|
|
223
|
+
|
|
224
|
+
ordered_records
|
|
200
225
|
end
|
|
201
226
|
end
|
|
202
227
|
end
|
data/lib/active_hash/version.rb
CHANGED
data/lib/active_hash.rb
CHANGED
data/lib/active_yaml/base.rb
CHANGED
|
@@ -4,6 +4,10 @@ module ActiveYaml
|
|
|
4
4
|
|
|
5
5
|
class Base < ActiveFile::Base
|
|
6
6
|
extend ActiveFile::HashAndArrayFiles
|
|
7
|
+
|
|
8
|
+
cattr_accessor :process_erb, instance_accessor: false
|
|
9
|
+
@@process_erb = true
|
|
10
|
+
|
|
7
11
|
class << self
|
|
8
12
|
def load_file
|
|
9
13
|
if (data = raw_data).is_a?(Array)
|
|
@@ -20,11 +24,15 @@ module ActiveYaml
|
|
|
20
24
|
private
|
|
21
25
|
if Psych::VERSION >= "4.0.0"
|
|
22
26
|
def load_path(path)
|
|
23
|
-
|
|
27
|
+
result = File.read(path)
|
|
28
|
+
result = ERB.new(result).result if process_erb
|
|
29
|
+
YAML.unsafe_load(result)
|
|
24
30
|
end
|
|
25
31
|
else
|
|
26
32
|
def load_path(path)
|
|
27
|
-
|
|
33
|
+
result = File.read(path)
|
|
34
|
+
result = ERB.new(result).result if process_erb
|
|
35
|
+
YAML.load(result)
|
|
28
36
|
end
|
|
29
37
|
end
|
|
30
38
|
end
|
|
@@ -4,19 +4,19 @@ module ActiveHash
|
|
|
4
4
|
module ActiveRecordExtensions
|
|
5
5
|
|
|
6
6
|
def belongs_to(name, scope = nil, **options)
|
|
7
|
-
|
|
7
|
+
klass_name = options.key?(:class_name) ? options[:class_name] : name.to_s.camelize
|
|
8
8
|
klass =
|
|
9
9
|
begin
|
|
10
|
-
|
|
11
|
-
rescue
|
|
12
|
-
nil
|
|
13
|
-
rescue LoadError
|
|
10
|
+
klass_name.constantize
|
|
11
|
+
rescue StandardError, LoadError
|
|
14
12
|
nil
|
|
15
13
|
end
|
|
14
|
+
|
|
16
15
|
if klass && klass < ActiveHash::Base
|
|
16
|
+
options = { class_name: klass_name }.merge(options)
|
|
17
17
|
belongs_to_active_hash(name, options)
|
|
18
18
|
else
|
|
19
|
-
super
|
|
19
|
+
super
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -49,13 +49,20 @@ module ActiveHash
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
if ActiveRecord::Reflection.respond_to?(:create)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
52
|
+
if defined?(ActiveHash::Reflection::BelongsToReflection)
|
|
53
|
+
reflection = ActiveHash::Reflection::BelongsToReflection.new(association_id.to_sym, nil, options, self)
|
|
54
|
+
if options[:through]
|
|
55
|
+
reflection = ActiveRecord::ThroughReflection.new(reflection)
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
reflection = ActiveRecord::Reflection.create(
|
|
59
|
+
:belongs_to,
|
|
60
|
+
association_id.to_sym,
|
|
61
|
+
nil,
|
|
62
|
+
options,
|
|
63
|
+
self
|
|
64
|
+
)
|
|
65
|
+
end
|
|
59
66
|
|
|
60
67
|
ActiveRecord::Reflection.add_reflection(
|
|
61
68
|
self,
|
|
@@ -85,12 +92,12 @@ module ActiveHash
|
|
|
85
92
|
end
|
|
86
93
|
|
|
87
94
|
def self.included(base)
|
|
95
|
+
require_relative "reflection_extensions"
|
|
88
96
|
base.extend Methods
|
|
89
97
|
end
|
|
90
98
|
|
|
91
99
|
module Methods
|
|
92
100
|
def has_many(association_id, options = {})
|
|
93
|
-
|
|
94
101
|
define_method(association_id) do
|
|
95
102
|
options = {
|
|
96
103
|
:class_name => association_id.to_s.classify,
|
|
@@ -110,6 +117,9 @@ module ActiveHash
|
|
|
110
117
|
klass.where(foreign_key => primary_key_value)
|
|
111
118
|
end
|
|
112
119
|
end
|
|
120
|
+
define_method("#{association_id.to_s.underscore.singularize}_ids") do
|
|
121
|
+
public_send(association_id).map(&:id)
|
|
122
|
+
end
|
|
113
123
|
end
|
|
114
124
|
|
|
115
125
|
def has_one(association_id, options = {})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module ActiveHash
|
|
2
|
+
module Reflection
|
|
3
|
+
class BelongsToReflection < ActiveRecord::Reflection::BelongsToReflection
|
|
4
|
+
def compute_class(name)
|
|
5
|
+
if polymorphic?
|
|
6
|
+
raise ArgumentError, "Polymorphic associations do not support computing the class."
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
klass = active_record.send(:compute_type, name)
|
|
11
|
+
rescue NameError => error
|
|
12
|
+
if error.name.match?(/(?:\A|::)#{name}\z/)
|
|
13
|
+
message = "Missing model class #{name} for the #{active_record}##{self.name} association."
|
|
14
|
+
message += " You can specify a different model class with the :class_name option." unless options[:class_name]
|
|
15
|
+
raise NameError.new(message, name)
|
|
16
|
+
else
|
|
17
|
+
raise
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
klass
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: active_hash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jeff Dean
|
|
@@ -29,7 +29,7 @@ authors:
|
|
|
29
29
|
autorequire:
|
|
30
30
|
bindir: bin
|
|
31
31
|
cert_chain: []
|
|
32
|
-
date:
|
|
32
|
+
date: 2023-05-06 00:00:00.000000000 Z
|
|
33
33
|
dependencies:
|
|
34
34
|
- !ruby/object:Gem::Dependency
|
|
35
35
|
name: activesupport
|
|
@@ -66,7 +66,7 @@ executables: []
|
|
|
66
66
|
extensions: []
|
|
67
67
|
extra_rdoc_files: []
|
|
68
68
|
files:
|
|
69
|
-
- CHANGELOG
|
|
69
|
+
- CHANGELOG.md
|
|
70
70
|
- LICENSE
|
|
71
71
|
- README.md
|
|
72
72
|
- active_hash.gemspec
|
|
@@ -75,17 +75,24 @@ files:
|
|
|
75
75
|
- lib/active_file/multiple_files.rb
|
|
76
76
|
- lib/active_hash.rb
|
|
77
77
|
- lib/active_hash/base.rb
|
|
78
|
+
- lib/active_hash/condition.rb
|
|
79
|
+
- lib/active_hash/conditions.rb
|
|
78
80
|
- lib/active_hash/relation.rb
|
|
79
81
|
- lib/active_hash/version.rb
|
|
80
82
|
- lib/active_json/base.rb
|
|
81
83
|
- lib/active_yaml/aliases.rb
|
|
82
84
|
- lib/active_yaml/base.rb
|
|
83
85
|
- lib/associations/associations.rb
|
|
86
|
+
- lib/associations/reflection_extensions.rb
|
|
84
87
|
- lib/enum/enum.rb
|
|
85
88
|
homepage: http://github.com/active-hash/active_hash
|
|
86
89
|
licenses:
|
|
87
90
|
- MIT
|
|
88
|
-
metadata:
|
|
91
|
+
metadata:
|
|
92
|
+
homepage_uri: http://github.com/active-hash/active_hash
|
|
93
|
+
changelog_uri: https://github.com/active-hash/active_hash/blob/master/CHANGELOG.md
|
|
94
|
+
source_code_uri: http://github.com/active-hash/active_hash
|
|
95
|
+
bug_tracker_uri: https://github.com/active-hash/active_hash/issues
|
|
89
96
|
post_install_message:
|
|
90
97
|
rdoc_options: []
|
|
91
98
|
require_paths:
|
|
@@ -101,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
101
108
|
- !ruby/object:Gem::Version
|
|
102
109
|
version: '0'
|
|
103
110
|
requirements: []
|
|
104
|
-
rubygems_version: 3.2.
|
|
111
|
+
rubygems_version: 3.2.33
|
|
105
112
|
signing_key:
|
|
106
113
|
specification_version: 4
|
|
107
114
|
summary: An ActiveRecord-like model that uses a hash or file as a datasource
|