active_hash 2.3.0 → 3.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +41 -15
- data/README.md +62 -14
- data/active_hash.gemspec +2 -1
- data/lib/active_file/base.rb +2 -2
- data/lib/active_hash/base.rb +23 -100
- data/lib/active_hash/relation.rb +202 -0
- data/lib/active_hash/version.rb +1 -1
- data/lib/active_hash.rb +1 -0
- data/lib/active_yaml/aliases.rb +11 -6
- data/lib/active_yaml/base.rb +7 -1
- data/lib/associations/associations.rb +5 -7
- metadata +22 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 819c44015340a15c0fbdd202345818e8fd8a056672e09fe196cf90789fb53f59
|
4
|
+
data.tar.gz: bafc27b0aff5c96b068b3dd58216fdd054fda429c332febeb47b0e9c538a0d2d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f729484d6e9cd6c5c20a6a8682a6b6c0a52ae71dbee77c9279c5c88c5206a1504d14bf6a46b795a77e7a982ed13f7e03370f5decc1672ed0f0651781ddc04953
|
7
|
+
data.tar.gz: 59bbb5a4027531f1f5e18bba31ad7dd4723df0e47d1d8b9b9472fa8ccc8f3d765814d937e8651cdbccffc12ee43035efc567b946c00e463b8518d8e2892c3318
|
data/CHANGELOG
CHANGED
@@ -1,32 +1,58 @@
|
|
1
|
+
2022-07-14 (v3.1.1)
|
2
|
+
- Make scopes chainable [#248](https://github.com/active-hash/active_hash/pull/248) @andreynering
|
3
|
+
- Set default key attributes [#251](https://github.com/active-hash/active_hash/pull/251/commits/68a0a121d110ac83f4bbf0024f027714fd24debf) @adampal
|
4
|
+
- Migrate from Travis to GitHub Actions for CI @kbrock
|
5
|
+
- Add primary_key support for has_one [#218](https://github.com/active-hash/active_hash/pull/218) @yujideveloper
|
6
|
+
- Return a chainable relation when using .not [#205](https://github.com/active-hash/active_hash/pull/205) @pfeiffer
|
7
|
+
- Correct fields with YAML aliases in array style [#226](https://github.com/active-hash/active_hash/pull/226) @stomk
|
8
|
+
- Add ActiveHash::Relation#size method for compatibily [#227](https://github.com/active-hash/active_hash/pull/227) @sluceno
|
9
|
+
- Implement ActiveRecord::RecordNotFound interface [#207](https://github.com/active-hash/active_hash/pull/207) @ChrisBr
|
10
|
+
- Fix find_by_id with filter chain [#210](https://github.com/active-hash/active_hash/pull/210) @ChrisBr
|
11
|
+
- Suppress Ruby 2.7 kwargs warnings [#206](https://github.com/active-hash/active_hash/pull/206) @yhirano55
|
12
|
+
- Call reload if @records is not defined [#208](https://github.com/active-hash/active_hash/pull/208) @jonmagic
|
13
|
+
- Switch to rspec3 (and update the Gemfile) [#209](https://github.com/active-hash/active_hash/pull/209) @djberg96
|
14
|
+
- Implement filter by RegEx [#211](https://github.com/active-hash/active_hash/pull/211) @ChrisBr
|
15
|
+
- Supports .pick method [#195](https://github.com/active-hash/active_hash/pull/195/files) @yhirano55
|
16
|
+
- Lots of other small performance improvements, documentation and testing. Thanks to everyone who contributed!
|
17
|
+
|
18
|
+
2020-01-15 (v3.1.0)
|
19
|
+
- Add ActiveHash::Base.order method inspired by ActiveRecord [#177](https://github.com/active-hash/active_hash/pull/177)
|
20
|
+
- Add #to_ary to ActiveHash::Relation [#182](https://github.com/active-hash/active_hash/pull/182)
|
21
|
+
- Allow #find to behave like Enumerable#find if id is nil and a block is given [#183](https://github.com/active-hash/active_hash/pull/183)
|
22
|
+
- Delegate :sample to `records` [#189](https://github.com/active-hash/active_hash/pull/189)
|
23
|
+
|
24
|
+
2019-09-28 (v3.0.0)
|
25
|
+
- Make #where chainable [#178](https://github.com/active-hash/active_hash/pull/178)
|
26
|
+
|
1
27
|
2019-09-28 (v2.3.0)
|
2
|
-
- Add ::scope method (inspired by ActiveRecord) [#173](https://github.com/
|
3
|
-
- Let `.find(nil)` raise ActiveHash::RecordNotFound (inspired by ActiveRecord) [#174](https://github.com/
|
4
|
-
- `where` clause now works with range argument [#175](https://github.com/
|
28
|
+
- Add ::scope method (inspired by ActiveRecord) [#173](https://github.com/active-hash/active_hash/pull/173)
|
29
|
+
- Let `.find(nil)` raise ActiveHash::RecordNotFound (inspired by ActiveRecord) [#174](https://github.com/active-hash/active_hash/pull/174)
|
30
|
+
- `where` clause now works with range argument [#175](https://github.com/active-hash/active_hash/pull/175)
|
5
31
|
|
6
32
|
2019-03-06 (v2.2.1)
|
7
|
-
- Allow empty YAML [#171](https://github.com/
|
33
|
+
- Allow empty YAML [#171](https://github.com/active-hash/active_hash/pull/171) Thanks, @ppworks
|
8
34
|
|
9
35
|
2018-11-22 (v2.2.0)
|
10
|
-
- Support pluck method [#164](https://github.com/
|
11
|
-
- Support where.not method [#167](https://github.com/
|
36
|
+
- Support pluck method [#164](https://github.com/active-hash/active_hash/pull/164) Thanks, @ihatov08
|
37
|
+
- Support where.not method [#167](https://github.com/active-hash/active_hash/pull/167) Thanks, @DialBird
|
12
38
|
|
13
39
|
2018-04-05 (v2.1.0)
|
14
|
-
- Allow to use ERB (embedded ruby) in yml files [#160](https://github.com/
|
15
|
-
- Add `ActiveHash::Base.polymorphic_name` [#162](https://github.com/
|
16
|
-
- Fix to be able to use enum accessor constant with same name as top-level constant[#161](https://github.com/
|
40
|
+
- Allow to use ERB (embedded ruby) in yml files [#160](https://github.com/active-hash/active_hash/pull/160) Thanks, @UgoMare
|
41
|
+
- Add `ActiveHash::Base.polymorphic_name` [#162](https://github.com/active-hash/active_hash/pull/162)
|
42
|
+
- Fix to be able to use enum accessor constant with same name as top-level constant[#161](https://github.com/active-hash/active_hash/pull/161) Thanks, @yujideveloper
|
17
43
|
|
18
44
|
2018-02-27 (v2.0.0)
|
19
|
-
- Drop old Ruby and Rails support [#157](https://github.com/
|
20
|
-
- Don't generate instance accessors for class attributes [#136](https://github.com/
|
45
|
+
- Drop old Ruby and Rails support [#157](https://github.com/active-hash/active_hash/pull/157)
|
46
|
+
- Don't generate instance accessors for class attributes [#136](https://github.com/active-hash/active_hash/pull/136) Thanks, @rainhead
|
21
47
|
|
22
48
|
2017-06-14 (v1.5.3)
|
23
|
-
- Support symbol values in where and find_by [#156](https://github.com/
|
49
|
+
- Support symbol values in where and find_by [#156](https://github.com/active-hash/active_hash/pull/156) Thanks, @south37
|
24
50
|
|
25
51
|
2017-06-14 (v1.5.2)
|
26
|
-
- Fix find_by when passed an invalid id [#152](https://github.com/
|
52
|
+
- Fix find_by when passed an invalid id [#152](https://github.com/active-hash/active_hash/pull/152) Thanks, @davidstosik
|
27
53
|
|
28
54
|
2017-04-20 (v1.5.1)
|
29
|
-
- Fix a bug on `.where` [#147](https://github.com/
|
55
|
+
- Fix a bug on `.where` [#147](https://github.com/active-hash/active_hash/pull/147)
|
30
56
|
|
31
57
|
2017-03-24 (v1.5.0)
|
32
58
|
- add support for `.find_by!`(@syguer)
|
@@ -114,7 +140,7 @@
|
|
114
140
|
|
115
141
|
2011-01-22
|
116
142
|
- improved method_missing errors for dynamic finders
|
117
|
-
- prevent users from trying to overwrite :attributes (https://github.com/
|
143
|
+
- prevent users from trying to overwrite :attributes (https://github.com/active-hash/active_hash/issues/#issue/33)
|
118
144
|
|
119
145
|
2010-12-08
|
120
146
|
- ruby 1.9.2 compatibility
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ActiveHash
|
2
2
|
|
3
|
-
[![Build Status](https://
|
3
|
+
[![Build Status](https://github.com/active-hash/active_hash/actions/workflows/ruby.yml/badge.svg)](https://github.com/active-hash/active_hash/actions/workflows/ruby.yml)
|
4
4
|
|
5
5
|
ActiveHash is a simple base class that allows you to use a ruby hash as a readonly datasource for an ActiveRecord-like model.
|
6
6
|
|
@@ -14,6 +14,14 @@ ActiveHash also ships with:
|
|
14
14
|
|
15
15
|
* ActiveFile: a base class that you can use to create file data sources
|
16
16
|
* ActiveYaml: a base class that will turn YAML into a hash and load the data into an ActiveHash object
|
17
|
+
F
|
18
|
+
## !!! Important notice !!!
|
19
|
+
We have changed returned value to chainable by v3.0.0. It's not just an `Array` instance anymore.
|
20
|
+
If it breaks your application, please report us on [issues](https://github.com/active-hash/active_hash/issues), and use v2.x.x as following..
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'active_hash', '~> 2.3.0'
|
24
|
+
```
|
17
25
|
|
18
26
|
## Installation
|
19
27
|
|
@@ -123,7 +131,7 @@ NOTE: auto-defined fields will _not_ override fields you've defined, either on t
|
|
123
131
|
If some of your hash values contain nil, and you want to provide a default, you can specify defaults with the :field method:
|
124
132
|
```ruby
|
125
133
|
class Country < ActiveHash::Base
|
126
|
-
field
|
134
|
+
field :is_axis_of_evil, :default => false
|
127
135
|
end
|
128
136
|
```
|
129
137
|
## Defining Data
|
@@ -156,11 +164,13 @@ Country.find 1 # => returns the first country object with that i
|
|
156
164
|
Country.find [1,2] # => returns all Country objects with ids in the array
|
157
165
|
Country.find :all # => same as .all
|
158
166
|
Country.find :all, args # => the second argument is totally ignored, but allows it to play nicely with AR
|
167
|
+
Country.find { |country| country.name.start_with?('U') } # => returns the first country for which the block evaluates to true
|
159
168
|
Country.find_by_id 1 # => find the first object that matches the id
|
160
169
|
Country.find_by(name: 'US') # => returns the first country object with specified argument
|
161
170
|
Country.find_by!(name: 'US') # => same as find_by, but raise exception when not found
|
162
171
|
Country.where(name: 'US') # => returns all records with name: 'US'
|
163
172
|
Country.where.not(name: 'US') # => returns all records without name: 'US'
|
173
|
+
Country.order(name: :desc) # => returns all records ordered by name attribute in DESC order
|
164
174
|
```
|
165
175
|
It also gives you a few dynamic finder methods. For example, if you defined :name as a field, you'd get:
|
166
176
|
```ruby
|
@@ -198,16 +208,24 @@ Country#name= # => sets the name
|
|
198
208
|
```
|
199
209
|
## Saving in-memory records
|
200
210
|
|
201
|
-
The ActiveHash::Base.all method functions like an in-memory data store.
|
211
|
+
The ActiveHash::Base.all method functions like an in-memory data store. You can save your records as ActiveHash::Relation object by using standard ActiveRecord create and save methods:
|
202
212
|
```ruby
|
203
|
-
Country.all
|
213
|
+
Country.all
|
214
|
+
=> #<ActiveHash::Relation:0x00007f861e043bb0 @klass=Country, @all_records=[], @query_hash={}, @records_dirty=false>
|
204
215
|
Country.create
|
205
|
-
|
216
|
+
=> #<Country:0x00007f861b7abce8 @attributes={:id=>1}>
|
217
|
+
Country.all
|
218
|
+
=> #<ActiveHash::Relation:0x00007f861b7b3628 @klass=Country, @all_records=[#<Country:0x00007f861b7abce8 @attributes={:id=>1}>], @query_hash={}, @records_dirty=false>
|
206
219
|
country = Country.new
|
207
|
-
|
220
|
+
=> #<Country:0x00007f861e059938 @attributes={}>
|
221
|
+
country.new_record?
|
222
|
+
=> true
|
208
223
|
country.save
|
209
|
-
|
210
|
-
|
224
|
+
=> true
|
225
|
+
country.new_record?
|
226
|
+
# => false
|
227
|
+
Country.all
|
228
|
+
=> #<ActiveHash::Relation:0x00007f861e0ca610 @klass=Country, @all_records=[#<Country:0x00007f861b7abce8 @attributes={:id=>1}>, #<Country:0x00007f861e059938 @attributes={:id=>2}>], @query_hash={}, @records_dirty=false>
|
211
229
|
```
|
212
230
|
Notice that when adding records to the collection, it will auto-increment the id for you by default. If you use string ids, it will not auto-increment the id. Available methods are:
|
213
231
|
```
|
@@ -331,7 +349,7 @@ end
|
|
331
349
|
The above example will look for the file "/u/data/sample.yml".
|
332
350
|
|
333
351
|
Since ActiveYaml just creates a hash from the YAML file, you will have all fields specified in YAML auto-defined for you. You can format your YAML as an array, or as a hash:
|
334
|
-
```
|
352
|
+
```yaml
|
335
353
|
# array style
|
336
354
|
- id: 1
|
337
355
|
name: US
|
@@ -351,6 +369,35 @@ mexico:
|
|
351
369
|
id: 3
|
352
370
|
name: Mexico
|
353
371
|
```
|
372
|
+
|
373
|
+
### Automatic Key Attribute
|
374
|
+
|
375
|
+
When using the hash format for your YAML file, ActiveYaml will automatically add a `key` attribute with the name of the object. You can overwrite this by setting the key attribute in the YAML file.
|
376
|
+
For example:
|
377
|
+
```
|
378
|
+
au:
|
379
|
+
id: 1
|
380
|
+
name: Australia
|
381
|
+
```
|
382
|
+
|
383
|
+
When you access the object you can do `Country.find(1).key => 'au'`. Or `Country.find_by_key('au')`
|
384
|
+
|
385
|
+
If you want a different key on only some objects you can mix and match:
|
386
|
+
|
387
|
+
```
|
388
|
+
au:
|
389
|
+
id: 1
|
390
|
+
key: aus
|
391
|
+
name: Australia
|
392
|
+
nz:
|
393
|
+
id: 2
|
394
|
+
name: New Zealand
|
395
|
+
```
|
396
|
+
|
397
|
+
`Country.find(1).key => 'aus'`
|
398
|
+
|
399
|
+
`Country.find(2).key => 'nz'`
|
400
|
+
|
354
401
|
### Multiple files per model
|
355
402
|
|
356
403
|
You can use multiple files to store your data. You will have to choose between hash or array style as you cannot use both for one model.
|
@@ -364,7 +411,7 @@ end
|
|
364
411
|
|
365
412
|
Aliases can be used in ActiveYaml using either array or hash style by including `ActiveYaml::Aliases`.
|
366
413
|
With that module included, keys beginning with a '/' character can be safely added, and will be ignored, allowing you to add aliases anywhere in your code:
|
367
|
-
```
|
414
|
+
```yaml
|
368
415
|
# Array Style
|
369
416
|
- /aliases:
|
370
417
|
soda_flavor: &soda_flavor
|
@@ -390,7 +437,8 @@ coke:
|
|
390
437
|
name: Coke
|
391
438
|
flavor: *soda_flavor
|
392
439
|
price: *soda_price
|
393
|
-
|
440
|
+
```
|
441
|
+
```ruby
|
394
442
|
class Soda < ActiveYaml::Base
|
395
443
|
include ActiveYaml::Aliases
|
396
444
|
end
|
@@ -402,9 +450,9 @@ Soda.first.price # => 1.0
|
|
402
450
|
|
403
451
|
### Using ERB ruby in YAML
|
404
452
|
|
405
|
-
Embedded ruby can
|
453
|
+
Embedded ruby can be used in ActiveYaml using erb brackets `<% %>` and `<%= %>` to set the result of a ruby operation as a value in the yaml file.
|
406
454
|
|
407
|
-
```
|
455
|
+
```yaml
|
408
456
|
- id: 1
|
409
457
|
email: <%= "user#{rand(100)}@email.com" %>
|
410
458
|
password: <%= ENV['USER_PASSWORD'] %>
|
@@ -474,7 +522,7 @@ class Country < ActiveFile::Base
|
|
474
522
|
|
475
523
|
class << self
|
476
524
|
def extension
|
477
|
-
"
|
525
|
+
"super_secret"
|
478
526
|
end
|
479
527
|
|
480
528
|
def load_file
|
data/active_hash.gemspec
CHANGED
@@ -33,7 +33,7 @@ Gem::Specification.new do |s|
|
|
33
33
|
s.email = %q{jeff@zilkey.com}
|
34
34
|
s.summary = %q{An ActiveRecord-like model that uses a hash or file as a datasource}
|
35
35
|
s.description = %q{Includes the ability to specify data using hashes, yml files or JSON files}
|
36
|
-
s.homepage = %q{http://github.com/
|
36
|
+
s.homepage = %q{http://github.com/active-hash/active_hash}
|
37
37
|
s.license = "MIT"
|
38
38
|
|
39
39
|
s.files = [
|
@@ -45,5 +45,6 @@ Gem::Specification.new do |s|
|
|
45
45
|
].flatten
|
46
46
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
47
47
|
s.add_runtime_dependency('activesupport', '>= 5.0.0')
|
48
|
+
s.add_development_dependency "pry"
|
48
49
|
s.required_ruby_version = '>= 2.4.0'
|
49
50
|
end
|
data/lib/active_file/base.rb
CHANGED
@@ -47,9 +47,9 @@ module ActiveFile
|
|
47
47
|
protected :actual_root_path
|
48
48
|
|
49
49
|
[:find, :find_by_id, :all, :where, :method_missing].each do |method|
|
50
|
-
define_method(method) do |*args|
|
50
|
+
define_method(method) do |*args, &block|
|
51
51
|
reload unless data_loaded
|
52
|
-
return super(*args)
|
52
|
+
return super(*args, &block)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
data/lib/active_hash/base.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
module ActiveHash
|
2
|
-
|
3
2
|
class RecordNotFound < StandardError
|
3
|
+
attr_reader :model, :primary_key, :id
|
4
|
+
|
5
|
+
def initialize(message = nil, model = nil, primary_key = nil, id = nil)
|
6
|
+
@primary_key = primary_key
|
7
|
+
@model = model
|
8
|
+
@id = id
|
9
|
+
|
10
|
+
super(message)
|
11
|
+
end
|
4
12
|
end
|
5
13
|
|
6
14
|
class ReservedFieldError < StandardError
|
@@ -14,7 +22,7 @@ module ActiveHash
|
|
14
22
|
|
15
23
|
class Base
|
16
24
|
|
17
|
-
class_attribute :_data, :dirty, :default_attributes
|
25
|
+
class_attribute :_data, :dirty, :default_attributes, :scopes
|
18
26
|
|
19
27
|
class WhereChain
|
20
28
|
def initialize(scope)
|
@@ -23,18 +31,19 @@ module ActiveHash
|
|
23
31
|
end
|
24
32
|
|
25
33
|
def not(options)
|
26
|
-
return @
|
34
|
+
return @scope if options.blank?
|
27
35
|
|
28
36
|
# use index if searching by id
|
29
37
|
if options.key?(:id) || options.key?("id")
|
30
38
|
ids = @scope.pluck(:id) - Array.wrap(options.delete(:id) || options.delete("id"))
|
31
39
|
candidates = ids.map { |id| @scope.find_by_id(id) }.compact
|
32
40
|
end
|
33
|
-
return candidates if options.blank?
|
34
41
|
|
35
|
-
(candidates || @records || []).reject do |record|
|
36
|
-
match_options?(record, options)
|
42
|
+
filtered_records = (candidates || @records || []).reject do |record|
|
43
|
+
options.present? && match_options?(record, options)
|
37
44
|
end
|
45
|
+
|
46
|
+
ActiveHash::Relation.new(@scope.klass, filtered_records, {})
|
38
47
|
end
|
39
48
|
|
40
49
|
def match_options?(record, options)
|
@@ -182,76 +191,11 @@ module ActiveHash
|
|
182
191
|
record
|
183
192
|
end
|
184
193
|
|
185
|
-
def all(options={})
|
186
|
-
|
187
|
-
where(options[:conditions])
|
188
|
-
else
|
189
|
-
@records ||= []
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def where(options = :chain)
|
194
|
-
if options == :chain
|
195
|
-
return WhereChain.new(self)
|
196
|
-
elsif options.blank?
|
197
|
-
return @records
|
198
|
-
end
|
199
|
-
|
200
|
-
# use index if searching by id
|
201
|
-
if options.key?(:id) || options.key?("id")
|
202
|
-
ids = (options.delete(:id) || options.delete("id"))
|
203
|
-
ids = range_to_array(ids) if ids.is_a?(Range)
|
204
|
-
candidates = Array.wrap(ids).map { |id| find_by_id(id) }.compact
|
205
|
-
end
|
206
|
-
return candidates if options.blank?
|
207
|
-
|
208
|
-
(candidates || @records || []).select do |record|
|
209
|
-
match_options?(record, options)
|
210
|
-
end
|
211
|
-
end
|
212
|
-
|
213
|
-
def find_by(options)
|
214
|
-
where(options).first
|
215
|
-
end
|
216
|
-
|
217
|
-
def find_by!(options)
|
218
|
-
find_by(options) || (raise RecordNotFound.new("Couldn't find #{name}"))
|
219
|
-
end
|
220
|
-
|
221
|
-
def match_options?(record, options)
|
222
|
-
options.all? do |col, match|
|
223
|
-
if match.kind_of?(Array)
|
224
|
-
match.any? { |v| normalize(v) == normalize(record[col]) }
|
225
|
-
else
|
226
|
-
normalize(record[col]) == normalize(match)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
private :match_options?
|
232
|
-
|
233
|
-
def normalize(v)
|
234
|
-
v.respond_to?(:to_sym) ? v.to_sym : v
|
235
|
-
end
|
236
|
-
|
237
|
-
private :normalize
|
238
|
-
|
239
|
-
def range_to_array(range)
|
240
|
-
return range.to_a unless range.end.nil?
|
241
|
-
|
242
|
-
e = data.last[:id]
|
243
|
-
(range.begin..e).to_a
|
244
|
-
end
|
245
|
-
|
246
|
-
private :range_to_array
|
247
|
-
|
248
|
-
def count
|
249
|
-
all.length
|
194
|
+
def all(options = {})
|
195
|
+
ActiveHash::Relation.new(self, @records || [], options[:conditions] || {})
|
250
196
|
end
|
251
197
|
|
252
|
-
|
253
|
-
column_names.map { |column_name| all.map(&column_name.to_sym) }.inject(&:zip)
|
254
|
-
end
|
198
|
+
delegate :where, :find, :find_by, :find_by!, :find_by_id, :count, :pluck, :ids, :pick, :first, :last, :order, to: :all
|
255
199
|
|
256
200
|
def transaction
|
257
201
|
yield
|
@@ -269,30 +213,6 @@ module ActiveHash
|
|
269
213
|
@records = []
|
270
214
|
end
|
271
215
|
|
272
|
-
def find(id, * args)
|
273
|
-
case id
|
274
|
-
when :all
|
275
|
-
all
|
276
|
-
when :first
|
277
|
-
all(*args).first
|
278
|
-
when Array
|
279
|
-
id.map { |i| find(i) }
|
280
|
-
when nil
|
281
|
-
raise RecordNotFound.new("Couldn't find #{name} without an ID")
|
282
|
-
else
|
283
|
-
find_by_id(id) || begin
|
284
|
-
raise RecordNotFound.new("Couldn't find #{name} with ID=#{id}")
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
def find_by_id(id)
|
290
|
-
index = record_index[id.to_s]
|
291
|
-
index and @records[index]
|
292
|
-
end
|
293
|
-
|
294
|
-
delegate :first, :last, :to => :all
|
295
|
-
|
296
216
|
def fields(*args)
|
297
217
|
options = args.extract_options!
|
298
218
|
args.each do |field|
|
@@ -475,10 +395,13 @@ module ActiveHash
|
|
475
395
|
end
|
476
396
|
|
477
397
|
private :mark_clean
|
478
|
-
|
398
|
+
|
479
399
|
def scope(name, body)
|
480
400
|
raise ArgumentError, 'body needs to be callable' unless body.respond_to?(:call)
|
481
|
-
|
401
|
+
|
402
|
+
self.scopes ||= {}
|
403
|
+
self.scopes[name] = body
|
404
|
+
|
482
405
|
the_meta_class.instance_eval do
|
483
406
|
define_method(name) do |*args|
|
484
407
|
instance_exec(*args, &body)
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module ActiveHash
|
2
|
+
class Relation
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
delegate :each, to: :records # Make Enumerable work
|
6
|
+
delegate :equal?, :==, :===, :eql?, :sort!, to: :records
|
7
|
+
delegate :empty?, :length, :first, :second, :third, :last, to: :records
|
8
|
+
delegate :sample, to: :records
|
9
|
+
|
10
|
+
def initialize(klass, all_records, query_hash = nil)
|
11
|
+
self.klass = klass
|
12
|
+
self.all_records = all_records
|
13
|
+
self.query_hash = query_hash
|
14
|
+
self.records_dirty = false
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def where(query_hash = :chain)
|
19
|
+
return ActiveHash::Base::WhereChain.new(self) if query_hash == :chain
|
20
|
+
|
21
|
+
self.records_dirty = true unless query_hash.nil? || query_hash.keys.empty?
|
22
|
+
self.query_hash.merge!(query_hash || {})
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def all(options = {})
|
27
|
+
if options.has_key?(:conditions)
|
28
|
+
where(options[:conditions])
|
29
|
+
else
|
30
|
+
where({})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_by(options)
|
35
|
+
where(options).first
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_by!(options)
|
39
|
+
find_by(options) || (raise RecordNotFound.new("Couldn't find #{klass.name}", klass.name))
|
40
|
+
end
|
41
|
+
|
42
|
+
def find(id = nil, *args, &block)
|
43
|
+
case id
|
44
|
+
when :all
|
45
|
+
all
|
46
|
+
when :first
|
47
|
+
all(*args).first
|
48
|
+
when Array
|
49
|
+
id.map { |i| find(i) }
|
50
|
+
when nil
|
51
|
+
raise RecordNotFound.new("Couldn't find #{klass.name} without an ID", klass.name, "id") unless block_given?
|
52
|
+
records.find(&block) # delegate to Enumerable#find if a block is given
|
53
|
+
else
|
54
|
+
find_by_id(id) || begin
|
55
|
+
raise RecordNotFound.new("Couldn't find #{klass.name} with ID=#{id}", klass.name, "id", id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_by_id(id)
|
61
|
+
return where(id: id).first if query_hash.present?
|
62
|
+
|
63
|
+
index = klass.send(:record_index)[id.to_s] # TODO: Make index in Base publicly readable instead of using send?
|
64
|
+
index and records[index]
|
65
|
+
end
|
66
|
+
|
67
|
+
def count
|
68
|
+
length
|
69
|
+
end
|
70
|
+
|
71
|
+
def size
|
72
|
+
length
|
73
|
+
end
|
74
|
+
|
75
|
+
def pluck(*column_names)
|
76
|
+
column_names.map { |column_name| all.map(&column_name.to_sym) }.inject(&:zip)
|
77
|
+
end
|
78
|
+
|
79
|
+
def ids
|
80
|
+
pluck(:id)
|
81
|
+
end
|
82
|
+
|
83
|
+
def pick(*column_names)
|
84
|
+
pluck(*column_names).first
|
85
|
+
end
|
86
|
+
|
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
|
+
def to_ary
|
105
|
+
records.dup
|
106
|
+
end
|
107
|
+
|
108
|
+
def method_missing(method_name, *args)
|
109
|
+
return super unless self.klass.scopes.key?(method_name)
|
110
|
+
|
111
|
+
instance_exec(*args, &self.klass.scopes[method_name])
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_reader :query_hash, :klass, :all_records, :records_dirty
|
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
|
126
|
+
end
|
127
|
+
|
128
|
+
def filter_all_records_by_query_hash
|
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
|
138
|
+
|
139
|
+
return candidates if query_hash.blank?
|
140
|
+
|
141
|
+
(candidates || all_records || []).select do |record|
|
142
|
+
match_options?(record, query_hash)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def match_options?(record, options)
|
147
|
+
options.all? do |col, match|
|
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
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
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
|
+
def check_if_method_has_arguments!(method_name, args)
|
168
|
+
return unless args.blank?
|
169
|
+
|
170
|
+
raise ArgumentError,
|
171
|
+
"The method .#{method_name}() must contain arguments."
|
172
|
+
end
|
173
|
+
|
174
|
+
def preprocess_order_args(order_args)
|
175
|
+
order_args.reject!(&:blank?)
|
176
|
+
return order_args.reverse! unless order_args.first.is_a?(String)
|
177
|
+
|
178
|
+
ary = order_args.first.split(', ')
|
179
|
+
ary.map! { |e| e.split(/\W+/) }.reverse!
|
180
|
+
end
|
181
|
+
|
182
|
+
def order_by_args!(candidates, args)
|
183
|
+
args.each do |arg|
|
184
|
+
field, dir = if arg.is_a?(Hash)
|
185
|
+
arg.to_a.flatten.map(&:to_sym)
|
186
|
+
elsif arg.is_a?(Array)
|
187
|
+
arg.map(&:to_sym)
|
188
|
+
else
|
189
|
+
arg.to_sym
|
190
|
+
end
|
191
|
+
|
192
|
+
candidates.sort! do |a, b|
|
193
|
+
if dir.present? && dir.to_sym.upcase.equal?(:DESC)
|
194
|
+
b[field] <=> a[field]
|
195
|
+
else
|
196
|
+
a[field] <=> b[field]
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
data/lib/active_hash/version.rb
CHANGED
data/lib/active_hash.rb
CHANGED
data/lib/active_yaml/aliases.rb
CHANGED
@@ -5,6 +5,8 @@ module ActiveYaml
|
|
5
5
|
base.extend(ClassMethods)
|
6
6
|
end
|
7
7
|
|
8
|
+
ALIAS_KEY_REGEXP = /^\//.freeze
|
9
|
+
|
8
10
|
module ClassMethods
|
9
11
|
|
10
12
|
def insert(record)
|
@@ -12,16 +14,19 @@ module ActiveYaml
|
|
12
14
|
end
|
13
15
|
|
14
16
|
def raw_data
|
15
|
-
|
16
|
-
|
17
|
+
d = super
|
18
|
+
if d.kind_of?(Array)
|
19
|
+
d.reject do |h|
|
20
|
+
h.keys.any? { |k| k.match(ALIAS_KEY_REGEXP) }
|
21
|
+
end
|
22
|
+
else
|
23
|
+
d.reject do |k, v|
|
24
|
+
v.kind_of?(Hash) && k.match(ALIAS_KEY_REGEXP)
|
25
|
+
end
|
17
26
|
end
|
18
27
|
end
|
19
28
|
|
20
29
|
end
|
21
|
-
|
22
|
-
def initialize(attributes={})
|
23
|
-
super unless attributes.keys.index { |k| k.to_s.match(/^\//i) }
|
24
|
-
end
|
25
30
|
end
|
26
31
|
|
27
32
|
end
|
data/lib/active_yaml/base.rb
CHANGED
@@ -9,7 +9,7 @@ module ActiveYaml
|
|
9
9
|
if (data = raw_data).is_a?(Array)
|
10
10
|
data
|
11
11
|
elsif data.respond_to?(:values)
|
12
|
-
data.
|
12
|
+
data.map{ |key, value| {"key" => key}.merge(value) }
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -18,9 +18,15 @@ module ActiveYaml
|
|
18
18
|
end
|
19
19
|
|
20
20
|
private
|
21
|
+
if Psych::VERSION >= "4.0.0"
|
22
|
+
def load_path(path)
|
23
|
+
YAML.unsafe_load(ERB.new(File.read(path)).result)
|
24
|
+
end
|
25
|
+
else
|
21
26
|
def load_path(path)
|
22
27
|
YAML.load(ERB.new(File.read(path)).result)
|
23
28
|
end
|
29
|
+
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
end
|
@@ -3,10 +3,7 @@ module ActiveHash
|
|
3
3
|
|
4
4
|
module ActiveRecordExtensions
|
5
5
|
|
6
|
-
def belongs_to(
|
7
|
-
our_args = args.dup
|
8
|
-
options = our_args.extract_options!
|
9
|
-
name = our_args.shift
|
6
|
+
def belongs_to(name, scope = nil, **options)
|
10
7
|
options = {:class_name => name.to_s.camelize }.merge(options)
|
11
8
|
klass =
|
12
9
|
begin
|
@@ -19,7 +16,7 @@ module ActiveHash
|
|
19
16
|
if klass && klass < ActiveHash::Base
|
20
17
|
belongs_to_active_hash(name, options)
|
21
18
|
else
|
22
|
-
super
|
19
|
+
super(name, **options)
|
23
20
|
end
|
24
21
|
end
|
25
22
|
|
@@ -119,7 +116,8 @@ module ActiveHash
|
|
119
116
|
define_method(association_id) do
|
120
117
|
options = {
|
121
118
|
:class_name => association_id.to_s.classify,
|
122
|
-
:foreign_key => self.class.to_s.foreign_key
|
119
|
+
:foreign_key => self.class.to_s.foreign_key,
|
120
|
+
:primary_key => self.class.primary_key
|
123
121
|
}.merge(options)
|
124
122
|
|
125
123
|
scope = options[:class_name].constantize
|
@@ -127,7 +125,7 @@ module ActiveHash
|
|
127
125
|
if scope.respond_to?(:scoped) && options[:conditions]
|
128
126
|
scope = scope.scoped(:conditions => options[:conditions])
|
129
127
|
end
|
130
|
-
scope.send("find_by_#{options[:foreign_key]}",
|
128
|
+
scope.send("find_by_#{options[:foreign_key]}", send(options[:primary_key]))
|
131
129
|
end
|
132
130
|
end
|
133
131
|
|
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:
|
4
|
+
version: 3.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeff Dean
|
@@ -26,10 +26,10 @@ authors:
|
|
26
26
|
- Brett Richardson
|
27
27
|
- Rachel Heaton
|
28
28
|
- Keisuke Izumiya
|
29
|
-
autorequire:
|
29
|
+
autorequire:
|
30
30
|
bindir: bin
|
31
31
|
cert_chain: []
|
32
|
-
date:
|
32
|
+
date: 2022-07-14 00:00:00.000000000 Z
|
33
33
|
dependencies:
|
34
34
|
- !ruby/object:Gem::Dependency
|
35
35
|
name: activesupport
|
@@ -45,6 +45,20 @@ dependencies:
|
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: 5.0.0
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: pry
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
48
62
|
description: Includes the ability to specify data using hashes, yml files or JSON
|
49
63
|
files
|
50
64
|
email: jeff@zilkey.com
|
@@ -61,17 +75,18 @@ files:
|
|
61
75
|
- lib/active_file/multiple_files.rb
|
62
76
|
- lib/active_hash.rb
|
63
77
|
- lib/active_hash/base.rb
|
78
|
+
- lib/active_hash/relation.rb
|
64
79
|
- lib/active_hash/version.rb
|
65
80
|
- lib/active_json/base.rb
|
66
81
|
- lib/active_yaml/aliases.rb
|
67
82
|
- lib/active_yaml/base.rb
|
68
83
|
- lib/associations/associations.rb
|
69
84
|
- lib/enum/enum.rb
|
70
|
-
homepage: http://github.com/
|
85
|
+
homepage: http://github.com/active-hash/active_hash
|
71
86
|
licenses:
|
72
87
|
- MIT
|
73
88
|
metadata: {}
|
74
|
-
post_install_message:
|
89
|
+
post_install_message:
|
75
90
|
rdoc_options: []
|
76
91
|
require_paths:
|
77
92
|
- lib
|
@@ -86,9 +101,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
101
|
- !ruby/object:Gem::Version
|
87
102
|
version: '0'
|
88
103
|
requirements: []
|
89
|
-
|
90
|
-
|
91
|
-
signing_key:
|
104
|
+
rubygems_version: 3.2.22
|
105
|
+
signing_key:
|
92
106
|
specification_version: 4
|
93
107
|
summary: An ActiveRecord-like model that uses a hash or file as a datasource
|
94
108
|
test_files: []
|