omg-attrs 0.1.0 → 0.1.2

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: f3eaed4c8a55dca3f9d6e4c6ecd00781116986cc1c81d1105960225bbfb74a85
4
- data.tar.gz: 65c6269712e3e992694866d0c95a96b8d27b3542e78807c7a842cc1bcc4fbc25
3
+ metadata.gz: 02ce08d9a45a81a1e6e21af0744aadcac77ce59013fc4337860002a7001551e4
4
+ data.tar.gz: 3a8048e24bec33c37d70c1ac73c0c8b6c62a2ced7fd8dde8042fef4904ab6f4f
5
5
  SHA512:
6
- metadata.gz: 4c5c5c1d6c2b3fa0604cb1c930f87b3953b62c3679066deca0818e132187cc37579037886f8ff0d1e0daaf4c426d7a0c68de0da2997b4380b41cc7d209754fa2
7
- data.tar.gz: 04c77582e0256724d3ed1dfa090dd398a3b5da7e5c5e0b450b94bf388532ca076fa7f3a03dbf51ddf0e04201e81b9268284a88e6970105d6785ccff4a61b2ea5
6
+ metadata.gz: 4d060b165eb56a7dff83434896cbacbdb6ca0d031cabef97cfbf15b722c3ea4147b6b004918a544cbb72cf9e221626328cf0f5a8c9c4222ca674dfe2ae1a4ec1
7
+ data.tar.gz: 822fb4378d650d8083c5bcd92b7c8057f80853a1b046270ceaa34d03c6b6ef097b24bc41fb75210008353fd4335a03bfa38d095906bc49d98188367d53ba6589
data/lib/attrs.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pry-byebug'
4
+
3
5
  module Attrs
4
6
  def self.included(base)
5
7
  base.include(InstanceMethods)
@@ -7,8 +9,8 @@ module Attrs
7
9
 
8
10
  module InstanceMethods
9
11
  def attrs(*attrs)
10
- return map { |i| i.attrs(*attrs) } if is_list?(self)
11
-
12
+ return list_attrs(attrs) if is_list?
13
+
12
14
  base_attrs, nested_attrs = attrs.partition { |attr| attr.is_a?(Symbol) }
13
15
  nested_attrs.map! do |attr|
14
16
  if attr.is_a?(Hash)
@@ -21,25 +23,42 @@ module Attrs
21
23
  raise ArgumentError, "Invalid attribute: #{attr}"
22
24
  end
23
25
  end
24
-
25
- base_attrs = base_attrs.to_h { |attr| [attr, get(attr)] }
26
+
27
+ base_attrs = base_attrs.to_h do |attr|
28
+ [attr, get(attr)]
29
+ end
26
30
  nested_attrs.reduce(base_attrs, :merge)
27
31
  end
28
32
 
29
33
  private
30
34
 
31
- def nested_attrs(attr)
32
- attr.to_h do |key, value|
33
- records = get(key)
35
+ ##
36
+ # Returns a hash containing attributes on the list and attributes on individual items.
37
+ # @param attrs [Array]
38
+ # @return [Hash]
39
+ def list_attrs(attrs)
40
+ list, nested = attrs.partition { |attr| attr.is_a?(Symbol) && respond_to?(attr) }
41
+ list_attrs = list.empty? ? {} : list.to_h { |key| [key, get(key)] }
42
+ nested_attrs = nested.empty? ? {} : { items: map { |i| i.attrs(*nested) } }
34
43
 
35
- values = is_list?(records) ?
36
- records.map { |record| record.attrs(*value) } :
37
- records.attrs(*value)
44
+ list_attrs.merge(nested_attrs)
45
+ end
38
46
 
39
- [key, values]
47
+ ##
48
+ # Recursively retrieves nested attributes.
49
+ #
50
+ # @param attr [Array | Hash]
51
+ def nested_attrs(attr)
52
+ attr.to_h do |key, value|
53
+ [key, get(key).attrs(*value)]
40
54
  end
41
55
  end
42
56
 
57
+ ##
58
+ # Gets a single attribute for any object. If the object is a hash, it will return the value for the key.
59
+ #
60
+ # @param key [Symbol]
61
+ # @return [Object]
43
62
  def get(key)
44
63
  if respond_to?(key)
45
64
  send(key)
@@ -51,8 +70,11 @@ module Attrs
51
70
  end
52
71
  end
53
72
 
54
- def is_list?(object)
55
- object.is_a?(Enumerable) && !object.is_a?(Hash)
73
+ ##
74
+ # Hacky way of determining if a non-hash object is a list but is not
75
+ # technically an Array or Enumerable (e.g. ActiveRecord::Relation)
76
+ def is_list?
77
+ respond_to?(:each) && !is_a?(Hash)
56
78
  end
57
79
  end
58
80
  end
data/lib/ext/array.rb ADDED
@@ -0,0 +1,20 @@
1
+ module Ext
2
+ module Array
3
+ ##
4
+ # @param [Hash]
5
+ # @return [Object] First object that matches passed in key/value pairs
6
+ # @example
7
+ # [{ a: 1 }, { b: 2 }].find_by(a: 1) => { a: 1 }
8
+ def find_by(**attrs)
9
+ find { |item| item.match?(**attrs) }
10
+ end
11
+
12
+ ##
13
+ # @return [Array<Object>] all items that match all key/value pairs
14
+ def where(**attrs)
15
+ filter { |item| item.match?(**attrs) }
16
+ end
17
+ end
18
+ end
19
+
20
+ Array.include Ext::Array
data/lib/ext/hash.rb ADDED
@@ -0,0 +1,27 @@
1
+ module Ext
2
+ module Hash
3
+ def method_missing(method_name, *args, &block)
4
+ if key?(method_name.to_s)
5
+ self.class.define_method(method_name) do
6
+ self[method_name.to_s]
7
+ end
8
+
9
+ send(method_name)
10
+ elsif key?(method_name.to_sym)
11
+ self.class.define_method(method_name) do
12
+ self[method_name.to_sym]
13
+ end
14
+
15
+ send(method_name)
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def respond_to_missing?(method_name, *args, &block)
22
+ key?(method_name.to_s) || key?(method_name.to_sym) || super
23
+ end
24
+ end
25
+ end
26
+
27
+ Hash.include Ext::Hash
data/lib/ext/object.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Ext
2
+ module Object
3
+ ##
4
+ # @return [Boolean] whether all key/value pairs match
5
+ def match?(**args)
6
+ args.all? do |key, value|
7
+ key_value_match?(key, value)
8
+ end
9
+ end
10
+
11
+ def key_value_match?(key, value)
12
+ if respond_to?(:[])
13
+ attribute = self[key]
14
+ return true if attribute == value
15
+ return true if attribute.respond_to?(value) && attribute.send(value)
16
+ end
17
+
18
+ if respond_to?(key)
19
+ attribute = send(key)
20
+ return true if attribute == value
21
+ return true if attribute.respond_to?(value) && attribute.send(value)
22
+ end
23
+
24
+ return false
25
+ rescue TypeError, NoMethodError, NameError, ArgumentError
26
+ false
27
+ end
28
+ end
29
+ end
30
+
31
+ ::Object.include Ext::Object
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omg-attrs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Greenfield
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-30 00:00:00.000000000 Z
11
+ date: 2024-09-04 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Makes it easy to slice and dice objects and collections of objects
14
14
  email:
@@ -17,18 +17,15 @@ executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
- - ".rspec"
21
- - ".rubocop.yml"
22
- - README.md
23
- - attrs.gemspec
24
20
  - lib/attrs.rb
25
- - spec/attrs_spec.rb
26
- - spec/spec_helper.rb
27
- homepage: https://github.com/omgreenfield/omg-util/tree/main/attrs
21
+ - lib/ext/array.rb
22
+ - lib/ext/hash.rb
23
+ - lib/ext/object.rb
24
+ homepage: https://github.com/omgreenfield/attrs
28
25
  licenses:
29
26
  - MIT
30
27
  metadata:
31
- homepage_uri: https://github.com/omgreenfield/omg-util/tree/main/attrs
28
+ homepage_uri: https://github.com/omgreenfield/attrs
32
29
  rubygems_mfa_required: 'true'
33
30
  post_install_message:
34
31
  rdoc_options: []
@@ -45,7 +42,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
42
  - !ruby/object:Gem::Version
46
43
  version: '0'
47
44
  requirements: []
48
- rubygems_version: 3.5.9
45
+ rubygems_version: 3.5.17
49
46
  signing_key:
50
47
  specification_version: 4
51
48
  summary: Allows all objects to call `.attrs` which acts as a recursive `.slice` method
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,13 +0,0 @@
1
- AllCops:
2
- TargetRubyVersion: 2.7.8
3
-
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: single_quotes
7
-
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: single_quotes
11
-
12
- Layout/LineLength:
13
- Max: 120
data/README.md DELETED
@@ -1,45 +0,0 @@
1
- # Attrs
2
-
3
- ## Installation
4
-
5
- ### Testing locally
6
- ```sh
7
- # Build gem
8
- gem build attrs.gemspec
9
-
10
- # Install gem
11
- gem i -l /path/to/this/folder/omg-attrs-0.1.0.gem
12
- ```
13
-
14
- ## Usage
15
-
16
- ```ruby
17
- dad_hash = {
18
- age: 35,
19
- hair_color: 'brown',
20
- children: [
21
- { age: 7, hair_color: 'blonde' },
22
- { age: 3, hair_color: 'brown' }
23
- ],
24
- wife: { age: 35, hair_color: 'brown' }
25
- }
26
-
27
- dad_hash.attrs(:age) # => 35
28
- dad_hash.attrs(wife: :age, children: :hair_color) # =>
29
- # {
30
- # wife: 35,
31
- # children: [
32
- # { hair_color: 'blonde' },
33
- # { hair_color: 'brown' },
34
- # ],
35
- # }
36
- ```
37
-
38
- ## Running tests
39
-
40
- ```ruby
41
- rspec
42
-
43
- # or
44
- bundle exec rspec
45
- ```
data/attrs.gemspec DELETED
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = 'omg-attrs'
5
- spec.version = '0.1.0'
6
- spec.authors = ['Matthew Greenfield']
7
- spec.email = ['mattgreenfield1@gmail.com']
8
-
9
- spec.summary = 'Allows all objects to call `.attrs` which acts as a recursive `.slice` method'
10
- spec.description = 'Makes it easy to slice and dice objects and collections of objects'
11
-
12
- spec.homepage = 'https://github.com/omgreenfield/omg-util/tree/main/attrs'
13
- spec.license = 'MIT'
14
- spec.required_ruby_version = '>= 2.7.0'
15
-
16
- spec.metadata['homepage_uri'] = spec.homepage
17
- spec.metadata['rubygems_mfa_required'] = 'true'
18
-
19
- spec.files = Dir.chdir(__dir__) do
20
- `git ls-files -z`.split("\x0").reject do |f|
21
- (File.expand_path(f) == __FILE__) ||
22
- f.start_with?(*%w[spec/.git .github Gemfile])
23
- end
24
- end
25
- spec.require_paths = ['lib']
26
- end
data/spec/attrs_spec.rb DELETED
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'ostruct'
4
-
5
- RSpec.describe Attrs do
6
- it 'allows recursive slice-like attribute fetching' do
7
- dad_hash = {
8
- age: 35,
9
- hair_color: 'brown',
10
- children: [
11
- { age: 7, hair_color: 'blonde' },
12
- { age: 3, hair_color: 'brown' }
13
- ],
14
- wife: { age: 35, hair_color: 'brown' }
15
- }
16
-
17
- expect(dad_hash.attrs(:age, wife: %i[hair_color age], children: %i[hair_color age])).to eq(
18
- {
19
- age: 35,
20
- wife: { hair_color: 'brown', age: 35 },
21
- children: [
22
- { hair_color: 'blonde', age: 7 },
23
- { hair_color: 'brown', age: 3 },
24
- ],
25
- }
26
- )
27
-
28
- dad_object = OpenStruct.new(dad_hash)
29
- expect(dad_object.attrs(:age, wife: %i[hair_color age], children: %i[hair_color age])).to eq(
30
- {
31
- age: 35,
32
- wife: { hair_color: 'brown', age: 35 },
33
- children: [
34
- { hair_color: 'blonde', age: 7 },
35
- { hair_color: 'brown', age: 3 },
36
- ],
37
- }
38
- )
39
-
40
- children_array = dad_object.children
41
- expect(children_array.attrs(:hair_color, :age)).to eq([{ hair_color: 'blonde', age: 7 }, { hair_color: 'brown', age: 3 }])
42
- end
43
- end
data/spec/spec_helper.rb DELETED
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "attrs"
4
-
5
- RSpec.configure do |config|
6
- # Enable flags like --only-failures and --next-failure
7
- config.example_status_persistence_file_path = ".rspec_status"
8
-
9
- # Disable RSpec exposing methods globally on `Module` and `main`
10
- config.disable_monkey_patching!
11
-
12
- config.expect_with :rspec do |c|
13
- c.syntax = :expect
14
- end
15
- end