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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e84505bccf077a25c58e6e57c7fe5ed5e626a0990bf7018711936d4d40374d8a
4
- data.tar.gz: c4da9c5f5c3af7b5eb55747a91ee292052cef9cb7cfb134337a91d901534dceb
3
+ metadata.gz: 819c44015340a15c0fbdd202345818e8fd8a056672e09fe196cf90789fb53f59
4
+ data.tar.gz: bafc27b0aff5c96b068b3dd58216fdd054fda429c332febeb47b0e9c538a0d2d
5
5
  SHA512:
6
- metadata.gz: b03cc2c6bf1826e6e95a4277434127bed837808bcfdf1f82bbd8ad888b05f9d11e248eff8577cd0e9d51c6d2ace1d1e7688be1ddf3e09dcb38bc161b622c81a8
7
- data.tar.gz: fe93b20edaa8152c0448ce071c9d782edb3ded08b716708309678aba1a33b513c9bb9505693a9751a7497e3cd08f44fb18ec3f314c1d040a2b08262479eb1748
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/zilkey/active_hash/pull/173)
3
- - Let `.find(nil)` raise ActiveHash::RecordNotFound (inspired by ActiveRecord) [#174](https://github.com/zilkey/active_hash/pull/174)
4
- - `where` clause now works with range argument [#175](https://github.com/zilkey/active_hash/pull/175)
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/zilkey/active_hash/pull/171) Thanks, @ppworks
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/zilkey/active_hash/pull/164) Thanks, @ihatov08
11
- - Support where.not method [#167](https://github.com/zilkey/active_hash/pull/167) Thanks, @DialBird
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/zilkey/active_hash/pull/160) Thanks, @UgoMare
15
- - Add `ActiveHash::Base.polymorphic_name` [#162](https://github.com/zilkey/active_hash/pull/162)
16
- - Fix to be able to use enum accessor constant with same name as top-level constant[#161](https://github.com/zilkey/active_hash/pull/161) Thanks, @yujideveloper
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/zilkey/active_hash/pull/157)
20
- - Don't generate instance accessors for class attributes [#136](https://github.com/zilkey/active_hash/pull/136) Thanks, @rainhead
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/zilkey/active_hash/pull/156) Thanks, @south37
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/zilkey/active_hash/pull/152) Thanks, @davidstosik
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/zilkey/active_hash/pull/147)
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/zilkey/active_hash/issues/#issue/33)
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://travis-ci.org/zilkey/active_hash.png?branch=master)](https://travis-ci.org/zilkey/active_hash)
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 :is_axis_of_evil, :default => false
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. You can save your records to the the .all array by using standard ActiveRecord create and save methods:
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
- Country.all # [ <Country :id => 1> ]
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
- country.new_record? # => true
220
+ => #<Country:0x00007f861e059938 @attributes={}>
221
+ country.new_record?
222
+ => true
208
223
  country.save
209
- country.new_record? # => false
210
- Country.all # [ <Country :id => 1>, <Country :id => 2> ]
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 bu used in ActiveYaml using erb brackets `<% %>` and `<%= %>` to set the result of a ruby operation as a value in the yaml file.
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
- ".super_secret"
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/zilkey/active_hash}
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
@@ -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
 
@@ -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 @records if options.blank?
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
- if options.has_key?(:conditions)
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
- def pluck(*column_names)
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
@@ -1,5 +1,5 @@
1
1
  module ActiveHash
2
2
  module Gem
3
- VERSION = "2.3.0"
3
+ VERSION = "3.1.1"
4
4
  end
5
5
  end
data/lib/active_hash.rb CHANGED
@@ -12,6 +12,7 @@ rescue LoadError
12
12
  end
13
13
 
14
14
  require 'active_hash/base'
15
+ require 'active_hash/relation'
15
16
  require 'active_file/multiple_files'
16
17
  require 'active_file/hash_and_array_files'
17
18
  require 'active_file/base'
@@ -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
- super.reject do |k, v|
16
- v.kind_of? Hash and k.match(/^\//i)
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
@@ -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.values
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(*args)
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]}", id)
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: 2.3.0
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: 2019-09-28 00:00:00.000000000 Z
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/zilkey/active_hash
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
- rubyforge_project:
90
- rubygems_version: 2.7.6
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: []