object_state 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b954a8472c703394a859740677330050ca02f5af
4
+ data.tar.gz: a501a9e2030d067cc173c1b9cbd93cd2e41fc0ef
5
+ SHA512:
6
+ metadata.gz: 3b6ec2625a2c7edec5cf64e22470827ef9f868ae0e823466ec099dfb8b9459d1c5ee793da5654c2ca0da7cbaad51e87a182903bfaf875bf31e09644e89a43bb5
7
+ data.tar.gz: 2f37413a9e6f788bf0364c70cfe1d186f6d80b51df9b88baad7099ecb59520b985b6f30acfb637891f14cb276d883e1f712d63275c512312d5434ab9909dccbe
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ sudo: false
2
+ language: ruby
3
+ script: 'bundle exec rake'
4
+ rvm:
5
+ - 2.2.5
6
+ before_install:
7
+ - gem install bundler -v 1.12.5
8
+ notifications:
9
+ email:
10
+ recipients:
11
+ - tomas.celizna@gmail.com
12
+ on_failure: change
13
+ on_success: never
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in object_state.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :minitest do
2
+ watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
3
+ watch(%r{^test/.+_test\.rb$})
4
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
5
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Tomas Celizna
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # Object State
2
+
3
+ [![Build Status](https://travis-ci.org/tomasc/object_state.svg)](https://travis-ci.org/tomasc/object_state) [![Gem Version](https://badge.fury.io/rb/object_state.svg)](http://badge.fury.io/rb/object_state) [![Coverage Status](https://img.shields.io/coveralls/tomasc/object_state.svg)](https://coveralls.io/r/tomasc/object_state)
4
+
5
+ This gem helps to encapsulate state of objects of (typically) Ruby on Rails applications.
6
+
7
+ * The state becomes easy to identify in the source code.
8
+ * There is a standard method for converting the state to and from query params.
9
+ * The state can be expressed in the form of a `cache_key`.
10
+ * It is possible to additionally typecast and/or validate its attributes.
11
+
12
+ Useful for pagination, filtering etc.
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'object_state'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ ```
25
+ $ bundle
26
+ ```
27
+
28
+ Or install it yourself as:
29
+
30
+ ```
31
+ $ gem install object_state
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Encapsulate object's state
37
+
38
+ ```ruby
39
+ class MyObj
40
+ include ObjectState::Owner
41
+
42
+ object_state do
43
+ attr_accessor :current_date
44
+ end
45
+ end
46
+ ```
47
+
48
+ ### Export state hash
49
+
50
+ ```ruby
51
+ my_obj.to_object_state_hash # => { my_obj => { id: "123", current_date: "2016-08-27" } }
52
+ ```
53
+
54
+ This hash can be easily used as query params, for example:
55
+
56
+ ```ruby
57
+ my_obj_path(my_obj, my_obj.to_object_state_hash)
58
+ ```
59
+
60
+ An attribute can be easily overridden:
61
+
62
+ ```ruby
63
+ my_obj_path(my_obj, my_obj.to_object_state_hash(current_date: Date.tomorow))
64
+ ```
65
+
66
+ ### Assign values from state hash
67
+
68
+ ```ruby
69
+ my_obj.assign_attributes_from_object_state_hash(…)
70
+ ```
71
+
72
+ The attributes will be assigned only if the id in the state hash matches the id of your object. This is helpful for example when used in controllers.
73
+
74
+ ## Supported attribute definitions
75
+
76
+ * PoRo (`attr_accessor`)
77
+ * Mongoid fields
78
+ * Virtus attributes
79
+
80
+ ## Advanced usage
81
+
82
+ Optionally the state can be processed by a custom class. This is useful when the values need to be typecast, validated, or transformed. The `ObjectState::State` includes `ActiveModel::Model` and `Virtus` so you can use [Virtus' attribute definition](https://github.com/solnic/virtus) and [ActiveModel validations](http://api.rubyonrails.org/classes/ActiveModel/Validations.html). For example:
83
+
84
+ ```ruby
85
+ class MyObj::State < ObjectState::State
86
+ attribute :current_date, Date
87
+
88
+ validates :current_date, inclusion: { in: Date.today..Date.tomorrow }, presence: true
89
+ end
90
+ ```
91
+
92
+ Only valid values will be assigned to your object.
93
+
94
+ ```ruby
95
+ class MyObj
96
+ include ObjectState::Owner
97
+
98
+ object_state class_name: 'MyObj::State' do
99
+ attr_accessor :current_date
100
+ end
101
+ end
102
+ ```
103
+
104
+ ## Cache
105
+
106
+ Often values or views associated with the object need to be cached (and the cache expired) depending on its state. The `:object_state_cache_key` generates a cache key based on the state's values. For example the `MyObj` from the above example:
107
+
108
+ ```ruby
109
+ my_obj.object_state_cache_key # => '2016-08-27'
110
+ ```
111
+
112
+ In fact the `object_state` method automatically merges the state object's cache key with the object's cache_key:
113
+
114
+ ```ruby
115
+ my_obj.cache_key # => '<object-cache-key>/2016-08-27'
116
+ ```
117
+
118
+ This can be disabled as follows:
119
+
120
+ ```ruby
121
+ class MyObj
122
+ include ObjectState::Owner
123
+
124
+ object_state merge_cache_key: false do
125
+ attr_accessor :current_date
126
+ end
127
+ end
128
+ ```
129
+
130
+ ## Development
131
+
132
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
133
+
134
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
135
+
136
+ ## Contributing
137
+
138
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/tomasc/object_state>.
139
+
140
+ ## License
141
+
142
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "object_state"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,45 @@
1
+ require 'active_support/concern'
2
+
3
+ module ObjectState
4
+ module Owner
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def object_state_class
9
+ @object_state_class ||= Class.new(ObjectState::State)
10
+ end
11
+
12
+ def object_state(options = {}, &block)
13
+ cls = self
14
+ class_name = options.fetch(:class_name, nil)
15
+ @object_state_class ||= class_name ? class_name.constantize : Class.new(ObjectState::State)
16
+ @object_state_class.class_eval do
17
+ setup_attributes cls, &block
18
+ end
19
+
20
+ if instance_methods.include?(:cache_key) && options.fetch(:merge_cache_key, true)
21
+ define_method :cache_key do |*args|
22
+ return super(*args) unless object_state
23
+ [super(*args), object_state.cache_key].reject(&:blank?).join('/')
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ def object_state(attrs = {})
30
+ self.class.object_state_class.new(self, attrs)
31
+ end
32
+
33
+ def to_object_state_hash(attrs = {})
34
+ object_state(attrs).try(:to_hash)
35
+ end
36
+
37
+ def assign_attributes_from_state_hash(attrs)
38
+ key = respond_to?(:model_name) ? model_name.singular : self.class.to_s.underscore
39
+ return unless model_attrs = attrs.stringify_keys[key]
40
+ model_id = model_attrs.stringify_keys['id'] || model_attrs.stringify_keys['_id']
41
+ return if respond_to?(:id) && model_id.to_s != id.to_s
42
+ object_state(model_attrs.except(*%i(_id id))).try(:update_model!)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,104 @@
1
+ require 'active_model'
2
+ require 'virtus'
3
+
4
+ module ObjectState
5
+ class State
6
+ class UnsupportedAttributeType < StandardError; end
7
+
8
+ include ActiveModel::Model
9
+ include ActiveSupport::Rescuable
10
+ include Virtus.model(nullify_blank: true)
11
+
12
+ attr_accessor :model
13
+
14
+ validates :model, presence: true
15
+
16
+ def self.attribute_names
17
+ attribute_set.map(&:name)
18
+ end
19
+
20
+ def self.setup_attributes(owner_class, &block)
21
+ temp_class = Class.new(Object)
22
+
23
+ # override attr_accessor to collect attr_accessor :names
24
+ temp_class.define_singleton_method :attr_accessor do |*names|
25
+ @attr_accessor_names ||= []
26
+ @attr_accessor_names.concat Array(names)
27
+ super(*names)
28
+ end
29
+
30
+ temp_class.define_singleton_method :attr_accessor_names do
31
+ Array(@attr_accessor_names)
32
+ end
33
+
34
+ is_mongoid_document = !!defined?(::Mongoid::Document) && owner_class.ancestors.include?(::Mongoid::Document)
35
+ is_virtus_model = owner_class.respond_to?(:attribute_set) # FIXME: is there a better way to check for virtus?
36
+
37
+ temp_class.class_eval do
38
+ include ::Mongoid::Document if is_mongoid_document
39
+ include Virtus.model if is_virtus_model
40
+ instance_eval(&block)
41
+ end
42
+
43
+ # setup attributes for :attr_accessor names
44
+ excluded_attr_accessor_names = %i(validation_context _index before_callback_halted)
45
+ (temp_class.attr_accessor_names - excluded_attr_accessor_names).each do |name|
46
+ next if attribute_set.map(&:name).include?(name.to_sym)
47
+ attribute name
48
+ end
49
+
50
+ # setup attributes for Mongoid fields
51
+ if is_mongoid_document
52
+ temp_class.fields.except(*%w(_id)).each do |name, field|
53
+ next if attribute_set.map(&:name).include?(name.to_sym)
54
+ attribute name, field.type
55
+ end
56
+ end
57
+
58
+ # setup attributes for Virtus attributes
59
+ if is_virtus_model
60
+ temp_class.attribute_set.each do |att|
61
+ next if attribute_set.map(&:name).include?(att.name.to_sym)
62
+ attribute att.name, att.type
63
+ end
64
+ end
65
+
66
+ owner_class.class_eval(&block)
67
+ end
68
+
69
+ def initialize(model, attrs = {})
70
+ self.model = model
71
+ super attributes_from_model.merge(attrs)
72
+ end
73
+
74
+ def update_model!(options = {})
75
+ return unless model.present?
76
+ return unless valid? || options[:skip_validations] == true
77
+ attributes.except(:id).each do |name, value|
78
+ model.send("#{name}=", value) if model.respond_to?(name)
79
+ end
80
+ end
81
+
82
+ def to_hash(attrs = {})
83
+ return unless model.present?
84
+ key = model.respond_to?(:model_name) ? model.model_name.singular : model.class.to_s.underscore
85
+ value = super()
86
+ value = value.merge(id: model.id) if model.respond_to?(:id)
87
+ value = value.merge(attrs)
88
+ { key => value }
89
+ end
90
+
91
+ def cache_key
92
+ attributes.values.flatten.map(&:to_s).reject(&:blank?).join('/')
93
+ end
94
+
95
+ private
96
+
97
+ def attributes_from_model
98
+ self.class.attribute_names.each_with_object({}) do |name, res|
99
+ res[name] = model.send(name) if model.respond_to?(name)
100
+ res
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module ObjectState
2
+ VERSION = '0.1.0'.freeze
3
+ end
@@ -0,0 +1,4 @@
1
+ require 'object_state/owner'
2
+ require 'object_state/state'
3
+
4
+ require 'object_state/version'
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'object_state/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'object_state'
8
+ spec.version = ObjectState::VERSION
9
+ spec.authors = ['Tomas Celizna']
10
+ spec.email = ['tomas.celizna@gmail.com']
11
+
12
+ spec.summary = "Encapsulates object's state, converts from/to params and to cache_key."
13
+ spec.homepage = 'https://github.com/tomasc/object_state'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activemodel', '>= 4', '<= 5'
22
+ spec.add_dependency 'activesupport', '>= 4', '<= 5'
23
+ spec.add_dependency 'virtus'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.12'
26
+ spec.add_development_dependency 'guard'
27
+ spec.add_development_dependency 'guard-minitest'
28
+ spec.add_development_dependency 'minitest', '~> 5.0'
29
+ spec.add_development_dependency 'mongoid', '>= 5.0', '< 6'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ end
metadata ADDED
@@ -0,0 +1,202 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: object_state
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tomas Celizna
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-09-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4'
20
+ - - "<="
21
+ - !ruby/object:Gem::Version
22
+ version: '5'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4'
30
+ - - "<="
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '4'
40
+ - - "<="
41
+ - !ruby/object:Gem::Version
42
+ version: '5'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '4'
50
+ - - "<="
51
+ - !ruby/object:Gem::Version
52
+ version: '5'
53
+ - !ruby/object:Gem::Dependency
54
+ name: virtus
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: bundler
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.12'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.12'
81
+ - !ruby/object:Gem::Dependency
82
+ name: guard
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: guard-minitest
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: minitest
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '5.0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '5.0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: mongoid
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '5.0'
130
+ - - "<"
131
+ - !ruby/object:Gem::Version
132
+ version: '6'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '5.0'
140
+ - - "<"
141
+ - !ruby/object:Gem::Version
142
+ version: '6'
143
+ - !ruby/object:Gem::Dependency
144
+ name: rake
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: '10.0'
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - "~>"
155
+ - !ruby/object:Gem::Version
156
+ version: '10.0'
157
+ description:
158
+ email:
159
+ - tomas.celizna@gmail.com
160
+ executables: []
161
+ extensions: []
162
+ extra_rdoc_files: []
163
+ files:
164
+ - ".gitignore"
165
+ - ".travis.yml"
166
+ - Gemfile
167
+ - Guardfile
168
+ - LICENSE.txt
169
+ - README.md
170
+ - Rakefile
171
+ - bin/console
172
+ - bin/setup
173
+ - lib/object_state.rb
174
+ - lib/object_state/owner.rb
175
+ - lib/object_state/state.rb
176
+ - lib/object_state/version.rb
177
+ - object_state.gemspec
178
+ homepage: https://github.com/tomasc/object_state
179
+ licenses:
180
+ - MIT
181
+ metadata: {}
182
+ post_install_message:
183
+ rdoc_options: []
184
+ require_paths:
185
+ - lib
186
+ required_ruby_version: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ required_rubygems_version: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ requirements: []
197
+ rubyforge_project:
198
+ rubygems_version: 2.4.5.1
199
+ signing_key:
200
+ specification_version: 4
201
+ summary: Encapsulates object's state, converts from/to params and to cache_key.
202
+ test_files: []