taxjar-model_attribute 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a82866463930e1da3898b2cd7c511dd20b8663d8
4
+ data.tar.gz: 3e0eb679c8ccf1a38565c3a2743de1fc012bf6d7
5
+ SHA512:
6
+ metadata.gz: 48ead77c8ce88fe459c39962205bf3c3262e69328f52d1e28463711f64cc191f78aeecfdac19e9d0d065aa8dd957b9bb72c9c36efdaba10840fd03414493dbcc
7
+ data.tar.gz: d3ff747cad98fe25f477d8d76186556f7e1cb953309ae965374c1d568c592fd3abf777e897d291b0165cee296f2dd84e1aba834afd21baa3e2583f48feadc9d5
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.1.7"
5
+ - "2.2.3"
6
+ - "jruby-9.0.0.0"
@@ -0,0 +1,53 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## 3.0.0
6
+
7
+ - **Breaking change**: All casting errors raise `ArgumentError`. Previously some
8
+ errors during casting would raise `RuntimeError`.
9
+ Thanks to [@gotascii](https://github.com/gotascii) for the report.
10
+
11
+ ## 2.1.0
12
+
13
+ - **New feature**: default values. Allows you to specify a default value like
14
+ so:
15
+ ```
16
+ class User
17
+ attribute :name, :string, default: 'Michelle'
18
+ end
19
+
20
+ User.new.name
21
+ # => 'Michelle'
22
+ ```
23
+
24
+ ## 2.0.0
25
+
26
+ - **Breaking change**: Rename to `ModelAttribute` (no trailing 's') to avoid name
27
+ clash with another gem.
28
+
29
+ ## 1.4.0
30
+
31
+ - **New method**: #changes_for_json Returns a hash from attribute name to its
32
+ new value, suitable for serialization to a JSON string. Easily generate the
33
+ payload to send in an HTTP PUT to a web service.
34
+
35
+ - **New attribute type: json** Store an array/hash/etc. built using the basic
36
+ JSON data types: nil, numeric, string, boolean, hash and array.
37
+
38
+ ## 1.3.0
39
+
40
+ - **Breaking change**: Parsing an integer to a time attribute, the integer is
41
+ treated as the number of milliseconds since the epoch (not the number of
42
+ seconds). `attributes_as_json` emits integers for time attributes.
43
+
44
+ ## 1.2.0
45
+
46
+ - **Breaking change**: `attributes_as_json` removed; replaced with
47
+ `attributes_for_json`. You will have to serialize this yourself:
48
+ `Oj.dump(attributes_for_json, mode: :strict)`. This allows you to modify the
49
+ returned hash before serializing it.
50
+
51
+ ## 1.1.0
52
+
53
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in model_attribute.gemspec
4
+ gemspec
@@ -0,0 +1,47 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec feature)
6
+
7
+ ## Uncomment to clear the screen before every task
8
+ # clearing :on
9
+
10
+ ## Make Guard exit when config is changed so it can be restarted
11
+ #
12
+ ## Note: if you want Guard to automatically start up again, run guard in a
13
+ ## shell loop, e.g.:
14
+ #
15
+ # $ while bundle exec guard; do echo "Restarting Guard..."; done
16
+ #
17
+ ## Note: if you are using the `directories` clause above and you are not
18
+ ## watching the project directory ('.'), the you will want to move the Guardfile
19
+ ## to a watched dir and symlink it back, e.g.
20
+ #
21
+ # $ mkdir config
22
+ # $ mv Guardfile config/
23
+ # $ ln -s config/Guardfile .
24
+ #
25
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
26
+ #
27
+ watch ("Guardfile") do
28
+ UI.info "Exiting because Guard must be restarted for changes to take effect"
29
+ exit 0
30
+ end
31
+
32
+ guard :rspec, cmd: "bundle exec rspec --format=Nc --format=documentation", all_on_start: true do
33
+ require "guard/rspec/dsl"
34
+ dsl = Guard::RSpec::Dsl.new(self)
35
+
36
+ # RSpec files
37
+ rspec = dsl.rspec
38
+ watch(rspec.spec_helper) { rspec.spec_dir }
39
+ watch(rspec.spec_support) { rspec.spec_dir }
40
+ watch(rspec.spec_files)
41
+
42
+ # Ruby files
43
+ ruby = dsl.ruby
44
+ dsl.watch_spec_files_for(ruby.lib_files)
45
+
46
+ watch(%r{lib/*}) { 'spec' }
47
+ end
@@ -0,0 +1,26 @@
1
+ ModelAttribute
2
+
3
+ Copyright (c) 2015 Microsoft Corporation
4
+
5
+ All rights reserved.
6
+
7
+ MIT License
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining
10
+ a copy of this software and associated documentation files (the
11
+ "Software"), to deal in the Software without restriction, including
12
+ without limitation the rights to use, copy, modify, merge, publish,
13
+ distribute, sublicense, and/or sell copies of the Software, and to
14
+ permit persons to whom the Software is furnished to do so, subject to
15
+ the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be
18
+ included in all copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,223 @@
1
+ # ModelAttribute [![Gem Version](https://badge.fury.io/rb/model_attribute.svg)](http://badge.fury.io/rb/model_attribute) [![Build Status](https://travis-ci.org/yammer/model_attribute.svg?branch=master)](https://travis-ci.org/yammer/model_attribute)
2
+
3
+ Simple attributes for a non-ActiveRecord model.
4
+
5
+ - Stores attributes in instance variables.
6
+ - Type casting and checking.
7
+ - Dirty tracking.
8
+ - List attribute names and values.
9
+ - Default values for attributes
10
+ - Handles integers, booleans, strings and times - a set of types that are very
11
+ easy to persist to and parse from JSON.
12
+ - Supports efficient serialization of attributes to JSON.
13
+ - Mass assignment - handy for initializers.
14
+
15
+ Why not [Virtus][virtus-gem]? Virtus doesn't provide dirty tracking, and
16
+ doesn't integrate with [ActiveModel::Dirty][am-dirty]. So if you're not using
17
+ ActiveRecord, but you need attributes with dirty tracking, ModelAttribute may be
18
+ what you're after. For example, it works very well for a model that fronts an
19
+ HTTP web service, and you want dirty tracking so you can PATCH appropriately.
20
+
21
+ Also in favor of ModelAttribute:
22
+
23
+ - It's simple - less than [200 lines of code][source].
24
+ - It supports efficient serialization and deserialization to/from JSON.
25
+
26
+ [virtus-gem]:https://github.com/solnic/virtus
27
+ [am-dirty]:https://github.com/rails/rails/blob/master/activemodel/lib/active_model/dirty.rb
28
+ [source]:https://github.com/yammer/model_attribute/blob/master/lib/model_attribute.rb
29
+
30
+ ## Usage
31
+
32
+ ```ruby
33
+ require 'model_attribute'
34
+ class User
35
+ extend ModelAttribute
36
+ attribute :id, :integer
37
+ attribute :paid, :boolean
38
+ attribute :name, :string
39
+ attribute :created_at, :time
40
+ attribute :grades, :json
41
+
42
+ def initialize(attributes = {})
43
+ set_attributes(attributes)
44
+ end
45
+ end
46
+
47
+ User.attributes # => [:id, :paid, :name, :created_at, :grades]
48
+ user = User.new
49
+
50
+ user.attributes # => {:id=>nil, :paid=>nil, :name=>nil, :created_at=>nil, :grades=>nil}
51
+
52
+ # An integer attribute
53
+ user.id # => nil
54
+
55
+ user.id = 3
56
+ user.id # => 3
57
+
58
+ # Stores values that convert cleanly to an integer
59
+ user.id = '5'
60
+ user.id # => 5
61
+
62
+ # Protects you against nonsense assignment
63
+ user.id = '5error'
64
+ ArgumentError: invalid value for Integer(): "5error"
65
+
66
+ # A boolean attribute
67
+ user.paid # => nil
68
+ user.paid = true
69
+
70
+ # Booleans also define a predicate method (ending in '?')
71
+ user.paid? # => true
72
+
73
+ # Conversion from strings used by databases.
74
+ user.paid = 'f'
75
+ user.paid # => false
76
+ user.paid = 't'
77
+ user.paid # => true
78
+
79
+ # A :time attribute
80
+ user.created_at = Time.now
81
+ user.created_at # => 2015-01-08 15:57:05 +0000
82
+
83
+ # Also converts from other reasonable time formats
84
+ user.created_at = "2014-12-25 14:00:00 +0100"
85
+ user.created_at # => 2014-12-25 13:00:00 +0000
86
+ user.created_at = Date.parse('2014-01-08')
87
+ user.created_at # => 2014-01-08 00:00:00 +0000
88
+ user.created_at = DateTime.parse("2014-12-25 13:00:45")
89
+ user.created_at # => 2014-12-25 13:00:45 +0000
90
+ # Convert from seconds since the epoch
91
+ user.created_at = Time.now.to_f
92
+ user.created_at # => 2015-01-08 16:23:02 +0000
93
+ # Or milliseconds since the epoch
94
+ user.created_at = 1420734182000
95
+ user.created_at # => 2015-01-08 16:23:02 +0000
96
+
97
+ # A :json attribute is schemaless and accepts the basic JSON types - hash,
98
+ # array, nil, numeric, string and boolean.
99
+ user.grades = {'maths' => 'A', 'history' => 'C'}
100
+ user.grades # => {"maths"=>"A", "history"=>"C"}
101
+ user.grades = ['A', 'A*', 'C']
102
+ user.grades # => ["A", "A*", "C"]
103
+ user.grades = 'AAB'
104
+ user.grades # => "AAB"
105
+ user.grades = Time.now
106
+ # => ArgumentError: JSON only supports nil, numeric, string, boolean and arrays and hashes of those.
107
+
108
+ # read_attribute and write_attribute methods
109
+ user.read_attribute(:created_at)
110
+ user.write_attribute(:name, 'Fred')
111
+
112
+ # View attributes
113
+ user.attributes # => {:id=>5, :paid=>true, :name=>"Fred", :created_at=>2015-01-08 15:57:05 +0000, :grades=>{"maths"=>"A", "history"=>"C"}}
114
+ user.inspect # => "#<User id: 5, paid: true, name: \"Fred\", created_at: 2015-01-08 15:57:05 +0000, grades: {\"maths\"=>\"A\", \"history\"=>\"C\"}>"
115
+
116
+ # Mass assignment
117
+ user.set_attributes(name: "Sally", paid: false)
118
+ user.attributes # => {:id=>5, :paid=>false, :name=>"Sally", :created_at=>2015-01-08 15:57:05 +0000}
119
+
120
+ # Efficient JSON serialization and deserialization.
121
+ # Attributes with nil values are omitted.
122
+ user.attributes_for_json
123
+ # => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
124
+ require 'oj'
125
+ Oj.dump(user.attributes_for_json, mode: :strict)
126
+ # => "{\"id\":5,\"paid\":true,\"name\":\"Fred\",\"created_at\":1421171317762}"
127
+ user2 = User.new(Oj.load(json, strict: true))
128
+
129
+ # Change tracking. A much smaller set of functions than that provided by
130
+ # ActiveModel::Dirty.
131
+ user.changes # => {:id=>[nil, 5], :paid=>[nil, true], :created_at=>[nil, 2015-01-08 15:57:05 +0000], :name=>[nil, "Fred"]}
132
+ user.name_changed? # => true
133
+ # If you need the new values to send as a PUT to a web service
134
+ user.changes_for_json # => {"id"=>5, "paid"=>true, "name"=>"Fred", "created_at"=>1421171317762}
135
+ # If you're imitating ActiveRecord behaviour, changes are cleared after
136
+ # after_save callbacks, but before after_commit callbacks.
137
+ user.changes.clear
138
+ user.changes # => {}
139
+
140
+ # Equality if all the attribute values match
141
+ another = User.new
142
+ another.id = 5
143
+ another.paid = true
144
+ another.created_at = user.created_at
145
+ another.name = 'Fred'
146
+
147
+ user == another # => true
148
+ user === another # => true
149
+ user.eql? another # => true
150
+
151
+ # Making some attributes private
152
+
153
+ class User
154
+ extend ModelAttribute
155
+ attribute :events, :string
156
+ private :events=
157
+
158
+ def initialize(attributes)
159
+ # Pass flag to set_attributes to allow setting attributes with private writers
160
+ set_attributes(attributes, true)
161
+ end
162
+
163
+ def add_event(new_event)
164
+ events ||= ""
165
+ events += new_event
166
+ end
167
+ end
168
+
169
+ # Supporting default attributes
170
+
171
+ class UserWithDefaults
172
+ extend ModelAttribute
173
+
174
+ attribute :name, :string, default: 'Charlie'
175
+ end
176
+
177
+ UserWithDefaults.attribute_defaults # => {:name=>"Charlie"}
178
+
179
+ user = UserWithDefaults.new
180
+ user.name # => "Charlie"
181
+ user.read_attribute(:name) # => "Charlie"
182
+ user.attributes # => {:name=>"Charlie"}
183
+ # attributes_for_json omits defaults to keep the JSON compact
184
+ user.attributes_for_json # => {}
185
+ # You can add them back in if you need them
186
+ user.attributes_for_json.merge(user.class.attribute_defaults) # => {:name=>"Charlie"}
187
+ # A default isn't a change
188
+ user.changes # => {}
189
+ user.changes_for_json # => {}
190
+
191
+ user.name = 'Bob'
192
+ user.attributes # => {:name=>"Bob"}
193
+ ```
194
+
195
+ ## Installation
196
+
197
+ Add this line to your application's Gemfile:
198
+
199
+ ```ruby
200
+ gem 'model_attribute'
201
+ ```
202
+
203
+ And then execute:
204
+
205
+ $ bundle
206
+
207
+ Or install it yourself as:
208
+
209
+ $ gem install model_attribute
210
+
211
+ ## Testing
212
+
213
+ Running specs:
214
+
215
+ $ TZ=UTC rspec
216
+
217
+ ## Contributing
218
+
219
+ 1. [Fork it](https://github.com/yammer/model_attribute/fork)
220
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
221
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
222
+ 4. Push to the branch (`git push origin my-new-feature`)
223
+ 5. Create a new Pull Request
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec) do |t|
4
+ t.pattern = Dir.glob('spec/**/*_spec.rb')
5
+ t.rspec_opts = '--format documentation'
6
+ end
7
+
8
+ task :default => :spec
@@ -0,0 +1,163 @@
1
+ require "model_attribute/version"
2
+ require "model_attribute/casts"
3
+ require "model_attribute/errors"
4
+ require "time"
5
+
6
+ module ModelAttribute
7
+ SUPPORTED_TYPES = [:integer, :float, :boolean, :string, :time, :json]
8
+
9
+ def self.extended(base)
10
+ base.send(:include, InstanceMethods)
11
+ base.instance_variable_set('@attribute_names', [])
12
+ base.instance_variable_set('@attribute_types', {})
13
+ base.instance_variable_set('@attribute_defaults', {})
14
+ end
15
+
16
+ def attribute(name, type, opts = {})
17
+ name = name.to_sym
18
+ type = type.to_sym
19
+ raise UnsupportedTypeError.new(type) unless SUPPORTED_TYPES.include?(type)
20
+
21
+ @attribute_names << name
22
+ @attribute_types[name] = type
23
+ @attribute_defaults[name] = opts[:default] if opts.key?(:default)
24
+
25
+ self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
26
+ def #{name}=(value)
27
+ write_attribute(#{name.inspect}, value, #{type.inspect})
28
+ end
29
+
30
+ def #{name}
31
+ read_attribute(#{name.inspect})
32
+ end
33
+
34
+ def #{name}_changed?
35
+ !!changes[#{name.inspect}]
36
+ end
37
+ CODE
38
+
39
+ if type == :boolean
40
+ self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
41
+ def #{name}?
42
+ !!read_attribute(#{name.inspect})
43
+ end
44
+ CODE
45
+ end
46
+ end
47
+
48
+ def attributes
49
+ @attribute_names
50
+ end
51
+
52
+ def attribute_defaults
53
+ @attribute_defaults
54
+ end
55
+
56
+ module InstanceMethods
57
+ def write_attribute(name, value, type = nil)
58
+ name = name.to_sym
59
+
60
+ # Don't want to expose attribute types as a method on the class, so access
61
+ # via a back door.
62
+ type ||= self.class.instance_variable_get('@attribute_types')[name]
63
+ raise InvalidAttributeNameError.new(name) unless type
64
+
65
+ value = Casts.cast(value, type)
66
+ return if value == read_attribute(name)
67
+
68
+ if changes.has_key? name
69
+ original = changes[name].first
70
+ else
71
+ original = read_attribute(name)
72
+ end
73
+
74
+ if original == value
75
+ changes.delete(name)
76
+ else
77
+ changes[name] = [original, value]
78
+ end
79
+
80
+ instance_variable_set("@#{name}", value)
81
+ end
82
+
83
+ def read_attribute(name)
84
+ ivar_name = "@#{name}"
85
+ if instance_variable_defined?(ivar_name)
86
+ instance_variable_get(ivar_name)
87
+ elsif !self.class.attributes.include?(name.to_sym)
88
+ raise InvalidAttributeNameError.new(name)
89
+ else
90
+ self.class.attribute_defaults[name.to_sym]
91
+ end
92
+ end
93
+
94
+ def attributes
95
+ self.class.attributes.each_with_object({}) do |name, attributes|
96
+ attributes[name] = read_attribute(name)
97
+ end
98
+ end
99
+
100
+ def set_attributes(attributes, can_set_private_attrs = false)
101
+ attributes.each do |key, value|
102
+ send("#{key}=", value) if respond_to?("#{key}=", can_set_private_attrs)
103
+ end
104
+ end
105
+
106
+ def ==(other)
107
+ return true if equal?(other)
108
+ if respond_to?(:id)
109
+ other.kind_of?(self.class) && id == other.id
110
+ else
111
+ other.kind_of?(self.class) && attributes == other.attributes
112
+ end
113
+ end
114
+ alias_method :eql?, :==
115
+
116
+ def changes
117
+ @changes ||= {}
118
+ end
119
+
120
+ # Attributes suitable for serializing to a JSON string.
121
+ #
122
+ # - Attribute keys are strings (for 'strict' JSON dumping).
123
+ # - Attributes with a default or nil value are omitted to speed serialization.
124
+ # - :time attributes are serialized as an Integer giving the number of
125
+ # milliseconds since the epoch.
126
+ def attributes_for_json
127
+ self.class.attributes.each_with_object({}) do |name, attributes|
128
+ value = read_attribute(name)
129
+ if value != self.class.attribute_defaults[name.to_sym]
130
+ value = (value.to_f * 1000).to_i if value.is_a? Time
131
+ attributes[name.to_s] = value
132
+ end
133
+ end
134
+ end
135
+
136
+ # Changed attributes suitable for serializing to a JSON string. Returns a
137
+ # hash from attribute name (as a string) to the new value of that attribute,
138
+ # for attributes that have changed.
139
+ #
140
+ # - :time attributes are serialized as an Integer giving the number of
141
+ # milliseconds since the epoch.
142
+ # - Unlike attributes_for_json, attributes that have changed to a nil value
143
+ # *are* included.
144
+ def changes_for_json
145
+ hash = {}
146
+ changes.each do |attr_name, (_old_value, new_value)|
147
+ new_value = (new_value.to_f * 1000).to_i if new_value.is_a? Time
148
+ hash[attr_name.to_s] = new_value
149
+ end
150
+
151
+ hash
152
+ end
153
+
154
+ # Includes the class name and all the attributes and their values. e.g.
155
+ # "#<User id: 1, paid: true, name: \"Fred\", created_at: 2014-12-25 08:00:00 +0000>"
156
+ def inspect
157
+ attribute_string = self.class.attributes.map do |key|
158
+ "#{key}: #{read_attribute(key).inspect}"
159
+ end.join(', ')
160
+ "#<#{self.class} #{attribute_string}>"
161
+ end
162
+ end
163
+ end