lean-attributes 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- Yzc3ZDljN2E0MGU2N2Y3ZTg2YWI5NzY4Y2M1NWMyYjIzM2E2MzJmMQ==
4
+ YjNkZDIxZDUzMGFkMWE5MGNiM2U3NGVhMTkzOWVmNDFkNWJmNTMwZg==
5
5
  data.tar.gz: !binary |-
6
- ZWY1NWQ4MGI3NjczYTdiZGRjODU4MjM0YjU5MWE1MzY5NDZlMDY1Ng==
6
+ NmUwMTkxOGE4MDkyZjFiNmUwODFmNzQ4Y2U4OTlmNTlkYWUxN2Q4NA==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Mjk3OGIyNjBlNDMzMWU2ZGI3MzBlNWI0MzNjOGM0NTA0NjUwODU5N2RiMGUz
10
- NjJkOTM3MTNkNzg1YzMyZWYyZGJiZmMzYzI3MTg5MjBkY2FhNmFjYzRjOWI5
11
- OWJjM2U5YTk4NDg1MmQxN2JiYzlkZTBlMDMwYjEyOTgxZWY5Njk=
9
+ MDUyMGU1OTY2NmUyYzY2YTNjMmE5MmE4MDk1YmQ1MTBiOTc0MGZjNzM4MzJl
10
+ NWY1ZmRhOGZjYzdjZGU5MGYzOGM4NDk2Y2I5YTkxNjhiMjVhYTg3NDYzY2Ux
11
+ YjI2ZjY5YjRmMWY5YjQ5NTFiMDBlYzljMjY2Y2I4ZTFiNzgzYWM=
12
12
  data.tar.gz: !binary |-
13
- NzY1MGFmODVlNGI2Nzc4NWU1NTUwNGI4MDY4NWQ4NzE1YTNkM2Y0YmRjYzkw
14
- MGI2ODQ0OTAxNDJiMmFlYTdhNTdjYTUwODFjNDNkOTEwMWU1MWY4MWU0OTQ3
15
- YWNiYjQ3ZjZmMTNlMjA2YmRkYjA2YWMyYWQ3NDgxZDIwY2Q0MjY=
13
+ MDQ3YzZhZTBiMThmYTMxMzdhMjg1MWQ1ZmY4MWM2Nzc2MWE4OGRjYTQzNGVl
14
+ Y2FiODYwZGQwMmNhNDRkNzU3ZWEyYjg5NThkMjQ3MTJjZTQ1YWUxY2RkMDlh
15
+ NTBiNmIzZGY5ZTg5ODMwOGQzZDQwNTQxMzk1ZGI4NDJiYTExMDE=
data/CHANGELOG.md ADDED
@@ -0,0 +1,36 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [Unreleased][unreleased]
6
+
7
+ ## [0.2.0] - 2015-09-18
8
+ ### Added
9
+ - Adds content to README.md
10
+ - Adds comprehensive YARD documentation
11
+ - Adds #attributes method to return attribute values as a hash
12
+
13
+ ## Changed
14
+ - Renames generated coercion methods from `coerce_<attribute>_to_<type>` to `coerce_<attribute>`
15
+ - `Lean::Attribute::CoercionHelpers` is no longer an included module but a utility for precompiling coercion methods
16
+
17
+ ### Fixed
18
+ - Fixes a bug where Time attributes would be improperly parsed to January 1st
19
+ - Fixes a bug where coercion methods were not being called as documented
20
+
21
+ ## [0.1.0] - 2015-09-09
22
+ ### Changed
23
+ - Makes calls to `coerce_<attribute>_to_<type>` unconditional when setting an attribute
24
+ - Lean::Attributes::Coercion is now Lean::Attributes::CoercionHelpers
25
+
26
+ ## [0.0.2] - 2015-09-09 [YANKED]
27
+ ### Fixed
28
+ - Fixes error when setting Symbol attributes
29
+
30
+ ## [0.0.1] - 2015-09-08
31
+ ### Added
32
+ - Initial release
33
+
34
+ [unreleased]: https://github.com/lleolin/lean-attributes/compare/v0.1.0...HEAD
35
+ [0.1.0]: https://github.com/lleolin/lean-attributes/compare/v0.0.2...v0.1.0
36
+ [0.0.2]: https://github.com/lleolin/lean-attributes/compare/v0.0.1...v0.0.2
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Lean::Attributes
2
2
 
3
+ Lean::Attributes is inspired by gems like [Virtus](https://github.com/solnic/virtus) or [FastAttributes](https://github.com/applift/fast_attributes). It allows one to define typed attributes on arbitrary Ruby classes. Lean::Attributes aims to be roughly as fast as FastAttributes but has a few syntactical differences in addition to support for default values.
4
+
3
5
  ## Status
4
6
  [![Gem Version](https://badge.fury.io/rb/lean-attributes.svg)](http://badge.fury.io/rb/lean-attributes)
5
7
  [![Build Status](https://travis-ci.org/lleolin/lean-attributes.svg)](https://travis-ci.org/lleolin/lean-attributes)
@@ -7,3 +9,120 @@
7
9
  [![Code Climate](https://codeclimate.com/github/lleolin/lean-attributes/badges/gpa.svg)](https://codeclimate.com/github/lleolin/lean-attributes)
8
10
  [![Dependency Status](https://gemnasium.com/lleolin/lean-attributes.svg)](https://gemnasium.com/lleolin/lean-attributes)
9
11
  [![Inline docs](http://inch-ci.org/github/lleolin/lean-attributes.svg?branch=master)](http://inch-ci.org/github/lleolin/lean-attributes)
12
+
13
+ ## Installation
14
+ Add this line to your Gemfile:
15
+ ```ruby
16
+ gem 'lean-attributes', '~> 0.2'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself with:
24
+
25
+ $ gem install lean-attributes
26
+
27
+ ## Usage
28
+ ```ruby
29
+ require 'lean-attributes'
30
+
31
+ class Book
32
+ include Lean::Attributes
33
+
34
+ attribute :title, String
35
+ attribute :name, String
36
+ attribute :pages, Integer
37
+ attribute :authors, Array
38
+ attribute :published, Date
39
+ attribute :sold, Time, default: :time_now
40
+ attribute :finished, DateTime
41
+ attribute :format, Symbol, default: :hardcover
42
+
43
+ private
44
+
45
+ def time_now
46
+ Time.now.utc
47
+ end
48
+ end
49
+
50
+ book = Book.new(
51
+ title: 'There and Back Again',
52
+ name: 'The Hobbit',
53
+ pages: '200',
54
+ authors: 'Tolkien',
55
+ published: '1937-09-21',
56
+ )
57
+ book.finished = '1937-08-20 12:35'
58
+
59
+ book.format # => :hardcover
60
+ book.sold # => 2015-09-15 23:06:34 UTC
61
+ book # =>
62
+ # #<Book:0x007fcb7613b610
63
+ # @authors=["Tolkien"],
64
+ # @finished=
65
+ # #<DateTime: 1937-08-20T12:35:00+00:00 ((2428766j,45300s,0n),+0s,2299161j)>,
66
+ # @format=:hardcover,
67
+ # @name="The Hobbit",
68
+ # @pages=200,
69
+ # @published=#<Date: 1937-09-21 ((2428798j,0s,0n),+0s,2299161j)>,
70
+ # @sold=2015-09-15 23:06:34 UTC,
71
+ # @title="There and Back Again">
72
+ ```
73
+ ### Coercion
74
+ The coercion capabilities included in this gem are poor by design and will only handle trivial use cases. Whenever you set an attribute, generated coercion methods are called. You can override these methods to create your own coercion rules.
75
+
76
+ ```ruby
77
+ class ReadingProgress
78
+ include Lean::Attributes
79
+
80
+ attribute :current_page, Integer, default: 1
81
+
82
+ private
83
+
84
+ def coerce_current_page(value)
85
+ value = begin
86
+ value.to_i
87
+ rescue NoMethodError
88
+ 1
89
+ end
90
+
91
+ value = 1 if value < 1
92
+ value
93
+ end
94
+ end
95
+ ```
96
+
97
+ Another way to contain such behavior is to create a `CurrentPage` class to represent your attribute instead of an Integer. The power is yours.
98
+
99
+ ## Benchmarks
100
+ Lean::Attributes is meant to be relatively lightweight and fast.
101
+
102
+ ```
103
+ FastAttributes: without values : 3499535.3 i/s
104
+ Lean::Attributes: without values : 3467944.9 i/s - 1.01x slower
105
+ Lean::Attributes: integer values for integer attributes : 151398.8 i/s - 23.11x slower
106
+ FastAttributes: integer values for integer attributes : 140099.8 i/s - 24.98x slower
107
+ Lean::Attributes: string values for integer attributes : 139070.9 i/s - 25.16x slower
108
+ FastAttributes: string values for integer attributes : 124387.2 i/s - 28.13x slower
109
+ Virtus: integer values for integer attributes : 36776.0 i/s - 95.16x slower
110
+ Attrio: integer values for integer attributes : 19452.5 i/s - 179.90x slower
111
+ Virtus: without values : 18184.8 i/s - 192.44x slower
112
+ Attrio: string values for integer attributes : 17689.9 i/s - 197.83x slower
113
+ Attrio: without values : 12769.1 i/s - 274.06x slower
114
+ Virtus: string values for integer attributes : 5043.4 i/s - 693.88x slower
115
+ ```
116
+
117
+ ## Versioning
118
+ Lean:Attributes uses [Semantic Versioning 2.0.0](http://semver.org)
119
+
120
+ ## Contributing
121
+ 1. Fork it ( https://github.com/lleolin/lean-attributes/fork )
122
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
123
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
124
+ 4. Push to the branch (`git push origin my-new-feature`)
125
+ 5. Create new Pull Request
126
+
127
+ ## Copyright
128
+ Copyright © 2015 R. Elliott Mason – Released under MIT License
@@ -1 +1,5 @@
1
1
  require 'lean-attributes/attributes'
2
+
3
+ # `Lean` namespace
4
+ module Lean
5
+ end
@@ -3,21 +3,63 @@ require 'lean-attributes/attributes/coercion_helpers'
3
3
  require 'lean-attributes/attributes/initializer'
4
4
 
5
5
  module Lean
6
+ # Allows one to define typed, coercible attributes on Ruby classes with
7
+ # the {Lean::Attributes::ClassMethods#attribute attribute} method.
8
+ #
9
+ # @since 0.0.1
10
+ # @api public
11
+ #
12
+ # @see Lean::Attributes::ClassMethods#attribute
6
13
  module Attributes
7
- def self.included(base)
8
- base.class_eval do
9
- include Basic
10
- include CoercionHelpers
11
- include Initializer
12
- end
13
- end
14
-
14
+ # Includes basic methods for defining attributes without polluting the
15
+ # inclusive class with coercion or initialization methods. You may mean to
16
+ # use {Lean::Attributes}.
17
+ #
18
+ # @since 0.1.0
19
+ #
20
+ # @see Lean::Attributes
21
+ # @see Lean::Attributes::ClassMethods
15
22
  module Basic
23
+ # Adds `.attribute` definition only
24
+ #
25
+ # @since 0.1.0
26
+ # @api private
27
+ #
28
+ # @see ClassMethods
16
29
  def self.included(base)
17
30
  base.class_eval do
18
31
  extend ClassMethods
19
32
  end
20
33
  end
34
+
35
+ # Returns a hash containing defined attribute names and their values.
36
+ #
37
+ # @return [Hash] defined attributes and their values
38
+ #
39
+ # @since 0.2.0
40
+ # @api public
41
+ def attributes
42
+ @attributes ||=
43
+ self.class.defined_attributes.each_with_object({}) do |attr, memo|
44
+ memo[attr] = send(attr)
45
+ end
46
+ end
47
+ end
48
+
49
+ # `include Lean::Attributes` will add {Basic} functionality, in addition
50
+ # to {Initializer}
51
+ #
52
+ # @since 0.1.0
53
+ # @api private
54
+ #
55
+ # @see Basic
56
+ # @see ClassMethods
57
+ # @see Initializer
58
+ def self.included(base)
59
+ base.class_eval do
60
+ include Basic
61
+ include Initializer
62
+ end
21
63
  end
22
64
  end
23
65
  end
@@ -1,26 +1,84 @@
1
1
  module Lean
2
2
  module Attributes
3
+ # Represents an attribute defined by
4
+ # {Lean::Attributes::ClassMethods#attribute}
5
+ #
6
+ # @since 0.0.1
7
+ # @api private
8
+ #
9
+ # @see Lean::Attributes::ClassMethods#attribute
3
10
  class Attribute
11
+ # @return [Symbol] name of the Attribute
4
12
  attr_reader :name
5
13
 
14
+ # Description of method
15
+ #
16
+ # @param [Hash] options = {} describe options = {}
17
+ # @option options [Object] :default default value or method name as a
18
+ # Symbol
19
+ # @option options [#to_sym] :name attribute name
20
+ # @option options [#to_s] :type class or class name that the attribute
21
+ # will be
22
+ # @return [Attribute] description of returned object
23
+ #
24
+ # @see Lean::Attributes::ClassMethods#attribute
6
25
  def initialize(options = {})
7
26
  @default = options[:default]
8
27
  @name = options[:name].to_sym
9
28
  @type = options[:type].to_s.to_sym
10
29
  end
11
30
 
31
+ # Generates a method definition as a String with the name
32
+ # `coerce_<attribute>`.
33
+ #
34
+ # @example
35
+ # class Post
36
+ # include Lean::Attributes
37
+ #
38
+ # attribute :author, String
39
+ # attribute :parent, Post
40
+ #
41
+ # # generated_methods:
42
+ # # def coerce_author(value)
43
+ # # value.to_s unless value.nil?
44
+ # # end
45
+ #
46
+ # # def coerce_parent(value)
47
+ # # Post.new(value) unless value.nil?
48
+ # # end
49
+ # end
50
+ #
51
+ # @return [String] method definition
52
+ #
53
+ # @see #coercion_method_name
54
+ # @see CoercionHelpers
12
55
  def coercion_method
13
56
  <<-EOS
14
- def #{coercion_method_name(name)}(value)
15
- #{coercion_method_name}(value)
57
+ def coerce_#{name}(value)
58
+ #{CoercionHelpers.method_body_for_type(@type)} unless value.nil?
16
59
  end
17
60
  EOS
18
61
  end
19
62
 
20
- def coercion_method_name(from = nil)
21
- ['coerce', from, 'to', @type.to_s.downcase].compact.join('_')
63
+ # Generates a method with a name `coerce_<attribute>`.
64
+ #
65
+ # @return [String] method name
66
+ #
67
+ # @see #coercion_method
68
+ def coercion_method_name
69
+ "coerce_#{name}"
22
70
  end
23
71
 
72
+ # If the configured default is a Symbol but the intended type for this
73
+ # attribute is anything but, interpret the default as a method name to
74
+ # which we will `send`. Otherwise, just use the configured default as a
75
+ # String.
76
+ #
77
+ # @return [Object] configured default
78
+ #
79
+ # @since 0.1.0
80
+ #
81
+ # @see #getter_method_with_default
24
82
  def default
25
83
  if @default.is_a?(Symbol) && @type != :Symbol
26
84
  return "send(:#{@default})"
@@ -29,12 +87,57 @@ module Lean
29
87
  @default.inspect
30
88
  end
31
89
 
90
+ # Generates a getter method definition if the Attribute has a default,
91
+ # or we just use the straightforward `attr_reader :attribute_name`.
92
+ #
93
+ # @example
94
+ # class Post
95
+ # include Lean::Attributes
96
+ #
97
+ # attribute :author, String
98
+ # attribute :replies_count, Integer, default: 0
99
+ #
100
+ # # generated attr_reader:
101
+ # # attr_reader :author
102
+ #
103
+ # # generated getter with default
104
+ # # def replies_count
105
+ # # @replies_count ||= 0
106
+ # # end
107
+ # end
108
+ #
109
+ # @return [String] description of returned object
110
+ #
111
+ # @see #getter_method_with_default
32
112
  def getter_method
33
113
  return getter_method_with_default unless @default.nil?
34
114
 
35
115
  "attr_reader :#{@name}"
36
116
  end
37
117
 
118
+ # Generates a getter method definition that lazily sets a default.
119
+ #
120
+ # @example
121
+ # class Post
122
+ # include Lean::Attributes
123
+ #
124
+ # attribute :created_at, Time, default: :default_created_at
125
+ # attribute :replies_count, Integer, default: 0
126
+ #
127
+ # # generated methods:
128
+ # # def created_at
129
+ # # @first_name ||= send(:default_created_at)
130
+ # # end
131
+ #
132
+ # # def replies_count
133
+ # # @replies_count ||= 0
134
+ # # end
135
+ # end
136
+ #
137
+ # @return [String] method definition that sets default
138
+ #
139
+ # @see #default
140
+ # @see #getter_method
38
141
  def getter_method_with_default
39
142
  <<-EOS
40
143
  def #{name}
@@ -43,10 +146,27 @@ module Lean
43
146
  EOS
44
147
  end
45
148
 
149
+ # Generates a setter method definition that coerces values to the
150
+ # configured type if necessary.
151
+ #
152
+ # @example
153
+ # class Book
154
+ # include Lean::Attributes
155
+ #
156
+ # attribute :pages, Integer
157
+ #
158
+ # # generated method:
159
+ # # def pages=(value)
160
+ # # value = coerce_pages(value)
161
+ # # @pages = value
162
+ # # end
163
+ # end
164
+ #
165
+ # @return [String] method definition
46
166
  def setter_method
47
167
  <<-EOS
48
168
  def #{name}=(value)
49
- value = #{coercion_method_name}(value) unless value.is_a?(#{@type})
169
+ value = #{coercion_method_name}(value)
50
170
  @#{name} = value
51
171
  end
52
172
  EOS
@@ -2,7 +2,19 @@ require 'lean-attributes/attributes/attribute'
2
2
 
3
3
  module Lean
4
4
  module Attributes
5
+ # Methods that extend classes that include {Lean::Attributes}
6
+ #
7
+ # @since 0.0.1
8
+ # @api public
5
9
  module ClassMethods
10
+ # Defines a new attribute. Adds getter and setter methods to the class.
11
+ #
12
+ # @param [Symbol] name describe name
13
+ # @param [Class,String,Symbol] type describe type
14
+ # @param [Hash] options
15
+ # @option options [Object] :default default value or method name to call
16
+ # as a Symbol
17
+ # @return [Attribute] configured Attribute
6
18
  def attribute(name, type, options = {})
7
19
  attribute = Attribute.new(
8
20
  default: options[:default],
@@ -10,12 +22,28 @@ module Lean
10
22
  type: type
11
23
  )
12
24
 
25
+ defined_attributes << attribute.name
26
+
13
27
  class_eval(attribute.coercion_method, __FILE__, __LINE__ + 1)
14
28
  class_eval(attribute.getter_method, __FILE__, __LINE__ + 1)
15
29
  class_eval(attribute.setter_method, __FILE__, __LINE__ + 1)
16
30
 
17
31
  attribute
18
32
  end
33
+
34
+ # @return [Array<Symbol>] names of defined attributes
35
+ #
36
+ # @since 0.2.0
37
+ # @api private
38
+ def defined_attributes
39
+ return @defined_attributes if @defined_attributes
40
+
41
+ @defined_attributes = if superclass.respond_to?(:defined_attributes)
42
+ superclass.defined_attributes.clone
43
+ else
44
+ []
45
+ end
46
+ end
19
47
  end
20
48
  end
21
49
  end
@@ -1,36 +1,40 @@
1
1
  module Lean
2
2
  module Attributes
3
+ # Incredibly straightforward methods that coerce some types into other
4
+ # common types. You may find that you need to override these methods,
5
+ # depending on your use case.
6
+ #
7
+ # @since 0.1.0
8
+ # @api private
3
9
  module CoercionHelpers
4
- def coerce_to_array(value)
5
- Array(value) unless value.nil?
6
- end
7
-
8
- def coerce_to_bigdecimal(value)
9
- BigDecimal.new(value, 0) unless value.nil?
10
- end
11
-
12
- def coerce_to_date(value)
13
- Date.parse(value) unless value.nil?
14
- end
15
-
16
- def coerce_to_datetime(value)
17
- DateTime.parse(value) unless value.nil?
18
- end
19
-
20
- def coerce_to_integer(value)
21
- value.to_i unless value.nil?
22
- end
23
-
24
- def coerce_to_string(value)
25
- value.to_s unless value.nil?
26
- end
27
-
28
- def coerce_to_symbol(value)
29
- value.to_sym unless value.nil?
30
- end
10
+ # Method body strings used to precompile `coerce_<attribute>` methods
11
+ #
12
+ # @since 0.2.0
13
+ #
14
+ # @see .method_body_for_type
15
+ # @see Attribute#coercion_method
16
+ METHOD_BODIES = {
17
+ Array: 'Array(value)',
18
+ BigDecimal: 'BigDecimal.new(value, 0)',
19
+ Date: 'Date.parse(value)',
20
+ DateTime: 'DateTime.parse(value)',
21
+ Float: 'value.to_f',
22
+ Integer: 'value.to_i',
23
+ String: 'value.to_s',
24
+ Symbol: 'value.to_s.to_sym',
25
+ Time: 'Time.parse(value).utc'
26
+ }
31
27
 
32
- def coerce_to_time(value)
33
- Time.new(value) unless value.nil?
28
+ # Fetches or generates the method body for coercing a value to this type
29
+ #
30
+ # @param [Symbol] type based on class name e.g. `:DateTime`
31
+ # @return [String] method body for coercion to type
32
+ #
33
+ # @since 0.2.0
34
+ #
35
+ # @see Attribute#coercion_method
36
+ def self.method_body_for_type(type)
37
+ METHOD_BODIES[type] || "#{type}.new(value)"
34
38
  end
35
39
  end
36
40
  end
@@ -1,6 +1,15 @@
1
1
  module Lean
2
2
  module Attributes
3
+ # Adds an `#initialize` method that sets attribute values based on
4
+ # Hash keys-values
5
+ #
6
+ # @since 0.0.1
7
+ # @api public
3
8
  module Initializer
9
+ # Assigns Hash values to attributes based on key names
10
+ #
11
+ # @param [Hash] attributes
12
+ # @return [Object] instance of inclusive class with attributes set
4
13
  def initialize(attributes = {})
5
14
  attributes.each do |name, value|
6
15
  send(:"#{name}=", value)
@@ -1,5 +1,5 @@
1
1
  module Lean
2
2
  module Attributes
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lean-attributes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - R. Elliott Mason
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-09 00:00:00.000000000 Z
11
+ date: 2015-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -60,6 +60,7 @@ extensions: []
60
60
  extra_rdoc_files:
61
61
  - README.md
62
62
  files:
63
+ - CHANGELOG.md
63
64
  - README.md
64
65
  - lean-attributes.gemspec
65
66
  - lib/lean-attributes.rb