lazy_lazer 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 60a5387afe075f801b80682c550567d99da5a88e
4
- data.tar.gz: 39ebc9c0c59e3b5010d2e6d1e5dca543c2f0a494
3
+ metadata.gz: a24047c46cd370e8fb1d092129dacb1816f50ebc
4
+ data.tar.gz: 072b5a1b15857682e4317eb8f275685462199e3e
5
5
  SHA512:
6
- metadata.gz: 2726ff1abd8149b613a7cc6a689d4a1c3cf3e4f67924d59af9b02cac6186afc1a6de256498ea0edcff9e427303f1d6188eb672d222ced672d4b432d928c2ebc2
7
- data.tar.gz: c26457eb59bef476eac2c5de64c84a041bddfd212e40bfa901e7adb13dc22c578f76e3c9d79be89c1da1c4e97f5a4941632e0d180290985c619976ff425c6573
6
+ metadata.gz: de1fb716edf702586d593c085256d984ee3bd8cf0be8811381ac1d6eec10451146fa16f0e88f89a9561ad54d2242a255fc5351c1c6f0d961a2b01dc0e1c62f2d
7
+ data.tar.gz: ada536d3a04d7ce62beff22a4e2afb1d1cf38bf1aa51cae3f08e1e2bed9f2848cecb42b5af8d047c4cea6a2260864749ee9ac5975748f525bbc51c72f29749c8
data/.rubocop.yml CHANGED
@@ -20,3 +20,5 @@ Metrics/CyclomaticComplexity:
20
20
  Severity: refactor
21
21
  Metrics/PerceivedComplexity:
22
22
  Severity: refactor
23
+ Metrics/MethodLength:
24
+ Severity: refactor
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  **lazy lazer**
2
2
 
3
3
  **features**:
4
- - simple codebase (~100 lines of code)
4
+ - simple codebase (~110 lines of code, ~200 lines of tests)
5
5
  - doesn't inherit all of the Hash and Enumerable cruft
6
6
  - super lazy, doesn't even parse attributes until it's necessary
7
7
 
@@ -9,35 +9,26 @@
9
9
  class User
10
10
  include LazyLazer
11
11
 
12
- # User.new(first_name: 'John') #=> Error: missing `id`
13
- # User.new(id: 1).id? #=> true
14
12
  property :id, required: true
15
-
16
- # user = User.new(id: 1)
17
- # user.email #=> nil
18
- property :email, default: nil
19
-
20
- # user = User.new(id: 1)
21
- # user.language #=> :en_US
22
- property :language, default: :en_US
23
-
24
- # user = User.new(id: 1, first_name: 'John')
25
- # user.name #=> 'John'
26
- # user.first_name #=> NoMethodError: ...
27
- property :last_name, default: -> { %w[Doe Bloggs Hansen].sample }
28
-
29
- # user = User.new(id: 1, created_at: 1502834161)
30
- # user.created_at #=> 2017-08-15 22:56:13 +0100
31
- property :created_at, with: ->(time) { Time.at(time) }
32
-
33
- # user = User.new(id: 1, age: '45')
34
- # user.age #=> 45
13
+ property :email, default: 'unknown@example.com'
14
+ property :created_at, from: 'creation_time_utc', with: ->(time) { Time.at(time) }
35
15
  property :age, with: :to_i
36
16
 
37
- def reload
38
- update_attributes!(email: "#{last_name}@gmail.com") # update your attributes
17
+ property :favorite_ice_cream
18
+
19
+ def lazer_reload
39
20
  self.fully_loaded = true # mark model as fully updated
40
- self # a rails convention, totally optional
21
+ { favorite_ice_cream: %w[vanilla strawberry chocolate].sample }
41
22
  end
42
23
  end
24
+
25
+ user = User.new(id: 152, creation_time_utc: 1500000000, age: '21')
26
+
27
+ user.id #=> 152
28
+ user.email #=> "unknown@example.com"
29
+ user.created_at #=> 2017-07-14 03:40:00 +0100
30
+ user.age #=> 21
31
+
32
+ user.favorite_ice_cream #=> "chocolate"
33
+ user.reload.favorite_ice_cream #=> "vanilla"
43
34
  ```
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LazyLazer
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
data/lib/lazy_lazer.rb CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  require_relative 'lazy_lazer/version'
4
4
  require_relative 'lazy_lazer/errors'
5
- require_relative 'lazy_lazer/utilities'
6
5
 
7
- # LazyLazer is a lazy loading model.
6
+ # LazyLazer
8
7
  module LazyLazer
9
8
  # Hook into `include LazyLazer`.
10
9
  # @param [Module] base the object to include the methods in
@@ -12,8 +11,22 @@ module LazyLazer
12
11
  def self.included(base)
13
12
  base.extend(ClassMethods)
14
13
  base.include(InstanceMethods)
15
- base.instance_variable_set(:@_lazer_properties, {})
16
- base.instance_variable_set(:@_lazer_required_properties, [])
14
+ base.instance_variable_set(
15
+ :@lazer_metadata,
16
+ properties: [],
17
+ required: {},
18
+ default: {},
19
+ from: {},
20
+ with: {}
21
+ )
22
+ end
23
+
24
+ # Get the source key from an instance
25
+ # @param [Object] instance the instance
26
+ # @param [Symbol] key the property key
27
+ # @return [Symbol] the source key if found or the passed key if not found
28
+ def self.source_key(instance, key)
29
+ instance.class.lazer_metadata[:from].fetch(key, key)
17
30
  end
18
31
 
19
32
  # The methods to extend the class with.
@@ -22,78 +35,118 @@ module LazyLazer
22
35
  # @param [Class] klass the subclass
23
36
  # @return [void]
24
37
  def inherited(klass)
25
- klass.instance_variable_set(:@_lazer_properties, @_lazer_properties)
26
- klass.instance_variable_set(:@_lazer_required_properties, @_lazer_required_properties)
38
+ klass.instance_variable_set(:@lazer_metadata, @lazer_metadata)
27
39
  end
28
40
 
29
- # @return [Hash<Symbol, Hash>] defined properties and their options
30
- def properties
31
- @_lazer_properties
41
+ # @return [Hash<Symbol, Hash>] the lazer configuration for this model
42
+ def lazer_metadata
43
+ @lazer_metadata
32
44
  end
33
45
 
34
46
  # Define a property.
35
47
  # @param [Symbol] name the name of the property method
36
48
  # @param [Hash] options the options to create the property with
37
- # @option options [Boolean] :required (false) whether existence of this
38
- # property should be checked on model creation
39
- # @option options [Symbol, Array<Symbol>] :from (name) the key(s) to get
40
- # the value of the property from
41
- # @option options [Object, Proc] :default the default value to return if
42
- # not provided
43
- # @option options [Proc, Symbol] :with an optional transformation to apply
44
- # to the value of the key
49
+ # @option options [Boolean] :required (false) whether existence of this property should be
50
+ # checked on model creation
51
+ # @option options [Object, Proc] :default the default value to return if not provided
52
+ # @option options [Symbol] :from the key in the source object to get the property from
53
+ # @option options [Proc, Symbol] :with an optional transformation to apply to the value
54
+ # @note both :required and :default can't be provided
55
+ # @return [Symbol] the name of the created property
45
56
  def property(name, **options)
57
+ raise 'both :required and :default cannot be given' if options[:required] && options[:default]
46
58
  sym_name = name.to_sym
47
- properties[sym_name] = options
48
- @_lazer_required_properties << sym_name if options[:required]
49
- define_method(name) { read_attribute(name) }
59
+ @lazer_metadata[:properties] << sym_name
60
+ options.each_pair { |option, value| @lazer_metadata[option][sym_name] = value }
61
+ define_method(sym_name) { read_attribute(sym_name) }
50
62
  end
51
63
  end
52
64
 
53
- # The base model class. This could be included directly.
65
+ # The methods to extend the instance with.
54
66
  module InstanceMethods
55
- # Initializer.
56
- #
67
+ # Create a new instance of the class from a set of source attributes.
57
68
  # @param [Hash] attributes the model attributes
58
69
  # @return [void]
59
70
  def initialize(attributes = {})
60
- # Check all required attributes.
61
- self.class.instance_variable_get(:@_lazer_required_properties).each do |prop|
62
- raise RequiredAttribute, "#{self} requires `#{prop}`" unless attributes.key?(prop)
71
+ # Check that all required attributes exist.
72
+ self.class.lazer_metadata[:required].keys.each do |property|
73
+ key = LazyLazer.source_key(self, property)
74
+ raise RequiredAttribute, "#{self} requires `#{key}`" unless attributes.key?(key)
63
75
  end
64
76
 
65
- @_lazer_attribute_source = attributes.dup
66
- @_lazer_attribute_cache = {}
77
+ @_lazer_source = attributes.dup
78
+ @_lazer_cache = {}
67
79
  end
68
80
 
81
+ # Converts all the attributes that haven't been converted yet and returns the final hash.
69
82
  # @param [Boolean] strict whether to fully load all attributes
70
83
  # @return [Hash] a hash representation of the model
71
84
  def to_h(strict = true)
72
85
  if strict
73
- remaining = @_lazer_attribute_source.keys - @_lazer_attribute_cache.keys
74
- remaining.each do |key|
75
- @_lazer_attribute_cache[key] = read_attribute(key)
76
- end
86
+ todo = self.class.lazer_metadata[:properties] - @_lazer_cache.keys
87
+ todo.each { |k| read_attribute(k) }
77
88
  end
78
- @_lazer_attribute_cache
89
+ @_lazer_cache
79
90
  end
80
91
 
81
- # Reload the object.
82
- def reload; end
92
+ # @abstract Provides reloading behaviour for lazy loading.
93
+ # @return [Hash] the result of reloading the hash
94
+ # @note if necessary, subclasses can make this method private, so this isn't tested.
95
+ def lazer_reload
96
+ self.fully_loaded = true
97
+ {}
98
+ end
99
+
100
+ # Reload the object. Calls {#lazer_reload}, then merges the results into the internal store.
101
+ # Also evicts the internal cache of the new keys.
102
+ # @return [self] the updated object
103
+ def reload
104
+ new_attributes = lazer_reload
105
+ @_lazer_source.merge!(new_attributes)
106
+ @_lazer_cache = {}
107
+ self
108
+ end
83
109
 
84
110
  # Return the value of the attribute.
85
111
  # @param [Symbol] name the attribute name
86
112
  # @raise MissingAttribute if the key was not found
87
113
  def read_attribute(name)
88
- return @_lazer_attribute_cache[name] if @_lazer_attribute_cache.key?(name)
89
- reload if self.class.properties.key?(name) && !fully_loaded?
90
- options = self.class.properties.fetch(name, {})
114
+ # Returns the cached attribute.
115
+ return @_lazer_cache[name] if @_lazer_cache.key?(name)
116
+
117
+ # Converts the property into the lookup key.
118
+ source_key = LazyLazer.source_key(self, name)
91
119
 
92
- if !@_lazer_attribute_source.key?(name) && !options.key?(:default)
93
- raise MissingAttribute, "#{name} is missing for #{self}"
120
+ # Reloads if a key that should be there isn't.
121
+ reload if !@_lazer_source.key?(source_key) &&
122
+ self.class.lazer_metadata[:required].include?(name) &&
123
+ !fully_loaded?
124
+
125
+ # Complains if even after reloading, the key is missing (and there's no default).
126
+ if !@_lazer_source.key?(source_key) && !self.class.lazer_metadata[:default].key?(name)
127
+ raise MissingAttribute, "`#{source_key}` missing for #{self}"
128
+ end
129
+
130
+ # Gets the value or gets the default.
131
+ value_or_default = @_lazer_source.fetch(source_key) do
132
+ default = self.class.lazer_metadata[:default][name]
133
+ default.is_a?(Proc) ? instance_exec(&default) : default
94
134
  end
95
- uncoerced = Utilities.lookup_default(@_lazer_attribute_source, name, options[:default])
96
- Utilities.transform_value(uncoerced, options[:with])
135
+
136
+ # Transforms the result using :with, if found.
137
+ transformer = self.class.lazer_metadata[:with][name]
138
+ coerced =
139
+ case transformer
140
+ when Symbol
141
+ value_or_default.public_send(transformer)
142
+ when Proc
143
+ instance_exec(value_or_default, &transformer)
144
+ else
145
+ value_or_default
146
+ end
147
+
148
+ # Add to cache and return the result.
149
+ @_lazer_cache[name] = coerced
97
150
  end
98
151
 
99
152
  # Return the value of the attribute, returning nil if not found
@@ -108,7 +161,7 @@ module LazyLazer
108
161
  # @param [Symbol] attribute the attribute to update
109
162
  # @param [Object] value the new value
110
163
  def write_attribute(attribute, value)
111
- @_lazer_attribute_cache[attribute] = value
164
+ @_lazer_cache[attribute] = value
112
165
  end
113
166
 
114
167
  # Update multiple attributes at once.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lazy_lazer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Avinash Dwarapu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-08-17 00:00:00.000000000 Z
11
+ date: 2017-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -130,7 +130,6 @@ files:
130
130
  - lazy_lazer.gemspec
131
131
  - lib/lazy_lazer.rb
132
132
  - lib/lazy_lazer/errors.rb
133
- - lib/lazy_lazer/utilities.rb
134
133
  - lib/lazy_lazer/version.rb
135
134
  homepage: https://github.com/avinashbot/lazy_lazer
136
135
  licenses:
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LazyLazer
4
- # Utility methods.
5
- # @api private
6
- module Utilities
7
- # Get a value from a hash, calling the default if needed.
8
- # @param [Hash] source the hash to lookup
9
- # @param [Symbol] key the key to lookup
10
- # @param [Proc, Object] default the default value or Proc to call
11
- # @return the object or the default value
12
- def self.lookup_default(source, key, default)
13
- return source[key] if source.key?(key)
14
- return default.call if default.is_a?(Proc)
15
- default
16
- end
17
-
18
- # Transforms a value using a "transformer" supplied using :with.
19
- # @param [Object] value the value to transform
20
- # @param [nil, Symbol, Proc] transformer the transformation method
21
- # @param [Object] context the context to run the proc in
22
- # @return [Object] the result of applying the transformation to the value
23
- def self.transform_value(value, transformer)
24
- case transformer
25
- when nil
26
- value
27
- when Symbol
28
- value.public_send(transformer)
29
- when Proc
30
- transformer.call(value)
31
- end
32
- end
33
- end
34
- end