model-x 0.0.1
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 +7 -0
- data/.gitignore +29 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +45 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/model-x.rb +2 -0
- data/lib/model_x.rb +18 -0
- data/lib/model_x/associations.rb +104 -0
- data/lib/model_x/attributes.rb +158 -0
- data/lib/model_x/base.rb +19 -0
- data/lib/model_x/boolean.rb +68 -0
- data/lib/model_x/mixin.rb +24 -0
- data/lib/model_x/percentage.rb +102 -0
- data/lib/model_x/version.rb +3 -0
- data/model-x.gemspec +26 -0
- data/spec/model_x/associations_spec.rb +134 -0
- data/spec/model_x/attributes_spec.rb +62 -0
- data/spec/model_x/boolean_spec.rb +36 -0
- data/spec/model_x/percentage_spec.rb +102 -0
- data/spec/spec_helper.rb +21 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cef24d01b17c87ee5b6410ec90b970cb312b2fba
|
4
|
+
data.tar.gz: 2c254c395e2ef7148d051ad449038c92cf2f1c2b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f7b9fcf347aaca60c5fa0a04469be335ed32bacb86535db13bfb066d2b55ae4ec1f10fcf1c5fe860a8b3c23ec42fe1f43a34eafda5f6a6e0420abfe8d674fb12
|
7
|
+
data.tar.gz: 744b333045ddacd09bc565ff41c6417ef8976d5c299a34074ac3c6f99d3cbdae445c6bfbe241d4ca47de0e5b630157d647bda624be29723ab90cadc715e29bb1
|
data/.gitignore
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile ~/.gitignore_global
|
6
|
+
|
7
|
+
# Ignore .DS_Store
|
8
|
+
.DS_Store
|
9
|
+
|
10
|
+
# Ignore the output files.
|
11
|
+
/pkg
|
12
|
+
|
13
|
+
# Ignore bundler and database config and precompiled assets
|
14
|
+
/.bundle
|
15
|
+
/.env
|
16
|
+
|
17
|
+
# Ignore all logfiles and tempfiles.
|
18
|
+
/tmp
|
19
|
+
|
20
|
+
# Ignore TextMate projects
|
21
|
+
*.tmproj
|
22
|
+
*.sublime-project
|
23
|
+
*.sublime-workspace
|
24
|
+
|
25
|
+
# Documentation files and other stuff
|
26
|
+
.yardoc
|
27
|
+
/doc
|
28
|
+
/nbproject
|
29
|
+
/coverage
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
flux
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p247
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
model-x (0.0.1)
|
5
|
+
activemodel
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.0.3)
|
11
|
+
activesupport (= 4.0.3)
|
12
|
+
builder (~> 3.1.0)
|
13
|
+
activesupport (4.0.3)
|
14
|
+
i18n (~> 0.6, >= 0.6.4)
|
15
|
+
minitest (~> 4.2)
|
16
|
+
multi_json (~> 1.3)
|
17
|
+
thread_safe (~> 0.1)
|
18
|
+
tzinfo (~> 0.3.37)
|
19
|
+
atomic (1.1.15)
|
20
|
+
builder (3.1.4)
|
21
|
+
diff-lcs (1.2.4)
|
22
|
+
i18n (0.6.9)
|
23
|
+
minitest (4.7.5)
|
24
|
+
multi_json (1.9.0)
|
25
|
+
rake (10.1.0)
|
26
|
+
rspec (2.14.1)
|
27
|
+
rspec-core (~> 2.14.0)
|
28
|
+
rspec-expectations (~> 2.14.0)
|
29
|
+
rspec-mocks (~> 2.14.0)
|
30
|
+
rspec-core (2.14.5)
|
31
|
+
rspec-expectations (2.14.2)
|
32
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
33
|
+
rspec-mocks (2.14.3)
|
34
|
+
thread_safe (0.2.0)
|
35
|
+
atomic (>= 1.1.7, < 2)
|
36
|
+
tzinfo (0.3.38)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
ruby
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
bundler (~> 1.3)
|
43
|
+
model-x!
|
44
|
+
rake
|
45
|
+
rspec (~> 2.14)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Joost Lubach
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Model::X
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'model-x'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install model-x
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/model-x.rb
ADDED
data/lib/model_x.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'active_model'
|
3
|
+
|
4
|
+
module ModelX
|
5
|
+
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
class AttributeAlreadyDefined < Exception; end
|
9
|
+
class AttributeNotFound < RuntimeError; end
|
10
|
+
|
11
|
+
autoload :Base
|
12
|
+
autoload :Mixin
|
13
|
+
autoload :Attributes
|
14
|
+
autoload :Associations
|
15
|
+
autoload :Boolean
|
16
|
+
autoload :Percentage
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module ModelX
|
2
|
+
|
3
|
+
# Adds rudimentary 'associations' support to simple models. As there is no real database connection to
|
4
|
+
# another object, it is merely simulated by creating two attributes: +<name>+ and +<name>_id+. They work
|
5
|
+
# together so that a database association is simulated.
|
6
|
+
module Associations
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
######
|
12
|
+
# Association definition methods
|
13
|
+
|
14
|
+
# Define a belongs_to association.
|
15
|
+
#
|
16
|
+
# @param [Symbol|String] association The name of the association.
|
17
|
+
# @option options [String] :class_name The class name of the associated object.
|
18
|
+
# @option options [#to_s] :foreign_key The foreign key to use.
|
19
|
+
def belongs_to(association, options = {})
|
20
|
+
class_name = options[:class_name] || association.to_s.camelize
|
21
|
+
foreign_key = options[:foreign_key] || "#{association}_id"
|
22
|
+
|
23
|
+
attribute foreign_key
|
24
|
+
|
25
|
+
# Define attribute readers and writers for the ID attribute.
|
26
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
27
|
+
|
28
|
+
def #{foreign_key}
|
29
|
+
read_attribute(:#{foreign_key}) || @#{association}.try(:id)
|
30
|
+
end
|
31
|
+
|
32
|
+
def #{foreign_key}=(value)
|
33
|
+
value = nil if value.blank?
|
34
|
+
write_attribute :#{foreign_key}, value
|
35
|
+
@#{association} = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def #{association}(reload = false)
|
39
|
+
id = #{foreign_key}
|
40
|
+
if reload
|
41
|
+
@#{association} = ::#{class_name}.find_by_id(id) if id
|
42
|
+
else
|
43
|
+
@#{association} ||= ::#{class_name}.find_by_id(id) if id
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def #{association}=(value)
|
48
|
+
value = nil if value.blank?
|
49
|
+
@#{association} = value
|
50
|
+
write_attribute :#{foreign_key}, nil
|
51
|
+
end
|
52
|
+
|
53
|
+
RUBY
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
# Defines a has_many association.
|
58
|
+
def has_many(association, options = {})
|
59
|
+
attribute association
|
60
|
+
|
61
|
+
class_name = options[:class_name] || association.to_s.singularize.camelize
|
62
|
+
foreign_key = options[:foreign_key] || "#{association.to_s.singularize}_id"
|
63
|
+
ids_attribute = foreign_key.pluralize
|
64
|
+
|
65
|
+
attribute ids_attribute
|
66
|
+
|
67
|
+
# Define attribute readers and writers for the ID attribute.
|
68
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
69
|
+
|
70
|
+
def #{ids_attribute}
|
71
|
+
read_attribute(:#{ids_attribute}) || read_attribute(:#{association}).try(:id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def #{ids_attribute}=(value)
|
75
|
+
value = nil if value.blank?
|
76
|
+
write_attribute :#{ids_attribute}, value
|
77
|
+
@#{association} = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def #{association}(reload = false)
|
81
|
+
ids = #{ids_attribute}
|
82
|
+
if reload
|
83
|
+
@#{association} = ::#{class_name}.where(:id => ids) if ids
|
84
|
+
else
|
85
|
+
@#{association} ||= ::#{class_name}.where(:id => ids) if ids
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def #{association}=(value)
|
90
|
+
value = nil if value.blank?
|
91
|
+
@#{association} = value
|
92
|
+
write_attribute :#{ids_attribute}, nil
|
93
|
+
end
|
94
|
+
|
95
|
+
RUBY
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module ModelX
|
2
|
+
|
3
|
+
# Provides attribute DSL methods.
|
4
|
+
#
|
5
|
+
# == Defaults && types
|
6
|
+
#
|
7
|
+
# For each attribute, you can set a default which is returned if the virtual attribute would otherwise
|
8
|
+
# be +nil+.
|
9
|
+
#
|
10
|
+
# You may also specify a 'type' for the virtual attribute. This does nothing more than evaluate the line
|
11
|
+
# <tt><type> :<attribute></tt> into the model, e.g.
|
12
|
+
#
|
13
|
+
# attribute :has_price, :type => :boolean
|
14
|
+
#
|
15
|
+
# is equivalent to
|
16
|
+
#
|
17
|
+
# attribute :has_price
|
18
|
+
# boolean :has_price
|
19
|
+
module Attributes
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
# Attributes hash. Builds an attributes hash from current instance variables.
|
23
|
+
def attributes
|
24
|
+
@attributes ||= self.class.attributes.inject({}) do |attrs, name|
|
25
|
+
next attrs if name == :'attributes'
|
26
|
+
attrs[name.to_sym] = send(name)
|
27
|
+
attrs
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Assigns the attributes hash by setting all given values through attribute writers.
|
32
|
+
#
|
33
|
+
# @raise [AttributeNotFound] if a certain attribute is not found.
|
34
|
+
def attributes=(values)
|
35
|
+
assign_attributes values
|
36
|
+
end
|
37
|
+
|
38
|
+
# Assigns the model's attributes with the values from the given hash.
|
39
|
+
#
|
40
|
+
# @option options [Symbol] :missing (:raise)
|
41
|
+
# Specify the behavior for missing attributes. +:raise+ raises an exception, +:ignore+
|
42
|
+
# ignores the missing attributes.
|
43
|
+
def assign_attributes(values, options = {})
|
44
|
+
raise ArgumentError, "hash required" unless values.is_a?(Hash)
|
45
|
+
|
46
|
+
options = options.symbolize_keys
|
47
|
+
options.assert_valid_keys :missing
|
48
|
+
|
49
|
+
values.each do |key, value|
|
50
|
+
if respond_to?(:"#{key}=")
|
51
|
+
send :"#{key}=", value
|
52
|
+
elsif options[:missing] == :raise
|
53
|
+
raise AttributeNotFound, "attribute :#{key} not found"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Reads an attribute value.
|
60
|
+
def read_attribute(attribute)
|
61
|
+
instance_variable_get("@#{attribute}")
|
62
|
+
end
|
63
|
+
protected :read_attribute
|
64
|
+
|
65
|
+
# Reads an attribute value.
|
66
|
+
def [](attribute)
|
67
|
+
read_attribute attribute
|
68
|
+
end
|
69
|
+
|
70
|
+
# Writes an attribute value
|
71
|
+
def write_attribute(attribute, value)
|
72
|
+
@attributes = nil
|
73
|
+
instance_variable_set "@#{attribute}", value
|
74
|
+
end
|
75
|
+
protected :write_attribute
|
76
|
+
|
77
|
+
module ClassMethods
|
78
|
+
|
79
|
+
# @!attribute [r] attributes
|
80
|
+
# @return [Array] An array of defined attribute names.
|
81
|
+
def attributes
|
82
|
+
@attributes ||= []
|
83
|
+
end
|
84
|
+
|
85
|
+
# @!method attribute(*attributes, options = {})
|
86
|
+
# DSL method to define attributes.
|
87
|
+
#
|
88
|
+
# @option options :default
|
89
|
+
# A default value for the attribute.
|
90
|
+
# @option options [Symbol] :type
|
91
|
+
# A type for the attribute.
|
92
|
+
def attribute(*attributes)
|
93
|
+
options = attributes.extract_options!
|
94
|
+
|
95
|
+
@_model_x_defaults ||= {}
|
96
|
+
@_model_x_types ||= {}
|
97
|
+
|
98
|
+
attributes.each do |attribute|
|
99
|
+
attribute = attribute.to_sym
|
100
|
+
|
101
|
+
if instance_methods.include?(attribute)
|
102
|
+
raise AttributeAlreadyDefined, "attribute :#{attribute} is already defined on #{self.name}"
|
103
|
+
end
|
104
|
+
self.attributes << attribute
|
105
|
+
|
106
|
+
@_model_x_defaults[attribute] = options[:default] if options.key?(:default)
|
107
|
+
@_model_x_types[attribute] = options[:type]
|
108
|
+
|
109
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
110
|
+
|
111
|
+
def #{attribute}
|
112
|
+
value = read_attribute(:#{attribute})
|
113
|
+
value = self.class.send(:_model_x_default, :#{attribute}) if value.nil?
|
114
|
+
value
|
115
|
+
end
|
116
|
+
|
117
|
+
def #{attribute}=(value)
|
118
|
+
write_attribute :#{attribute}, value
|
119
|
+
end
|
120
|
+
|
121
|
+
RUBY
|
122
|
+
|
123
|
+
if options[:type]
|
124
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
125
|
+
#{options[:type]} :#{attribute}
|
126
|
+
RUBY
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def _model_x_default(attribute)
|
135
|
+
if @_model_x_defaults && @_model_x_defaults.key?(attribute)
|
136
|
+
@_model_x_defaults[attribute]
|
137
|
+
elsif superclass.private_methods.include?(:_model_x_default)
|
138
|
+
superclass.send :_model_x_default, attribute
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def _model_x_convert(attribute, value)
|
143
|
+
if @_model_x_types[attribute]
|
144
|
+
if ModelX.const_defined?(@_model_x_types[attribute].to_s.camelize)
|
145
|
+
ModelX.const_get(@_model_x_types[attribute].to_s.camelize).convert(value)
|
146
|
+
else
|
147
|
+
raise "no converter found for type #{@_model_x_types[attribute]}"
|
148
|
+
end
|
149
|
+
else
|
150
|
+
value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
data/lib/model_x/base.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module ModelX
|
2
|
+
|
3
|
+
# Base ModelX class. Does nothing more than include {ModelX::Mixin} and define
|
4
|
+
# a constructor.
|
5
|
+
class Base
|
6
|
+
|
7
|
+
include Mixin
|
8
|
+
|
9
|
+
# Initializes the ModelX class by assigning all specified attributes.
|
10
|
+
#
|
11
|
+
# @yield [self] Yields the new model if a block is given
|
12
|
+
def initialize(attributes = {})
|
13
|
+
self.attributes = attributes if attributes.present?
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ModelX
|
2
|
+
|
3
|
+
# Adds boolean attribute accessors to any object, allowing boolean-ish values to be set as well.
|
4
|
+
#
|
5
|
+
# == Usage
|
6
|
+
#
|
7
|
+
# class MyObject
|
8
|
+
# include ModelX::Boolean
|
9
|
+
#
|
10
|
+
# attr_accessor :my_attribute
|
11
|
+
# boolean :my_attribute
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Now, the following can be used:
|
15
|
+
#
|
16
|
+
# object = MyObject.new
|
17
|
+
# object.my_attribute = false
|
18
|
+
# object.my_attribute? # => false
|
19
|
+
#
|
20
|
+
# object.my_attribute = '0'
|
21
|
+
# object.my_attribute? # => false
|
22
|
+
# object.my_attribute = '1'
|
23
|
+
# object.my_attribute? # => true
|
24
|
+
# object.my_attribute = 'false'
|
25
|
+
# object.my_attribute? # => false
|
26
|
+
#
|
27
|
+
# Note that an existing attribute writer *must* exist.
|
28
|
+
#
|
29
|
+
# The values '0', 0, 'off', 'no' and 'false', and all values that Ruby considers false are deemed to be false.
|
30
|
+
# All other values are true.
|
31
|
+
module Boolean
|
32
|
+
extend ActiveSupport::Concern
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
|
36
|
+
def boolean(*attributes)
|
37
|
+
attributes.each do |attribute|
|
38
|
+
|
39
|
+
# An attribute must already exist.
|
40
|
+
unless instance_methods.include?(:"#{attribute}=")
|
41
|
+
raise ArgumentError, "cannot add boolean attribute #{attribute} - no existing attribute exists"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Override the writer and add a ? version.
|
45
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
46
|
+
|
47
|
+
def #{attribute}_with_model_x_boolean=(value)
|
48
|
+
self.#{attribute}_without_model_x_boolean = ModelX::Boolean.convert(value)
|
49
|
+
end
|
50
|
+
alias_method_chain :#{attribute}=, :model_x_boolean
|
51
|
+
alias_method :#{attribute}?, :#{attribute}
|
52
|
+
|
53
|
+
RUBY
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Converts a boolean attribute. This is used mostly for toggle buttons that
|
61
|
+
# enable or disable an input section.
|
62
|
+
def self.convert(value)
|
63
|
+
value.present? && value != '0' && value != 0 && value != 'off' && value != 'no' && value != 'false'
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ModelX
|
2
|
+
module Mixin
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# Include standard activemodel stuff.
|
6
|
+
include ActiveModel::Naming
|
7
|
+
include ActiveModel::Validations
|
8
|
+
include ActiveModel::Serialization
|
9
|
+
include ActiveModel::Serializers::JSON
|
10
|
+
include ActiveModel::Translation
|
11
|
+
|
12
|
+
included { extend ActiveModel::Callbacks }
|
13
|
+
include ActiveModel::Validations::Callbacks
|
14
|
+
|
15
|
+
include ModelX::Attributes
|
16
|
+
include ModelX::Associations
|
17
|
+
include ModelX::Boolean
|
18
|
+
include ModelX::Percentage
|
19
|
+
|
20
|
+
def persisted?() false end
|
21
|
+
def to_key() false end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module ModelX
|
2
|
+
|
3
|
+
# Adds percentage attribute accessors to any object. These are attribute accessors with a '_percentage' suffix
|
4
|
+
# which simply accept a number multiplied by 100.
|
5
|
+
module Percentage
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Adds percentage attributes for the given attribute. Per specified attribute, the following are added:
|
11
|
+
#
|
12
|
+
# +:<attribute>_percentage+::
|
13
|
+
# Retrieves or accepts the value of the original times 100.
|
14
|
+
#
|
15
|
+
# == Usage
|
16
|
+
#
|
17
|
+
# class MyObject
|
18
|
+
# include ModelX::Percentage
|
19
|
+
#
|
20
|
+
# attr_accessor :my_attribute
|
21
|
+
# percentage :my_attribute
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# Now, the following holds true:
|
25
|
+
#
|
26
|
+
# object = MyObject.new
|
27
|
+
#
|
28
|
+
# object.my_attribute = 0.5
|
29
|
+
# object.my_attribute_percentage # => 50
|
30
|
+
#
|
31
|
+
# object.my_attribute_percentage = 10
|
32
|
+
# object.my_attribute # => 0.1
|
33
|
+
#
|
34
|
+
# Note that an existing attribute reader *must* exist. For the writer to be defined, an existing attribute
|
35
|
+
# writer must exist.
|
36
|
+
def percentage(*attributes)
|
37
|
+
attributes.each do |attribute|
|
38
|
+
|
39
|
+
# An attribute must already exist.
|
40
|
+
unless instance_methods.include?(:"#{attribute}") || instance_methods.include?(:read_attribute)
|
41
|
+
raise ArgumentError, "cannot add percentage attribute #{attribute} - no existing attribute exists"
|
42
|
+
end
|
43
|
+
|
44
|
+
define_writer = instance_methods.include?(:"#{attribute}=") || private_instance_methods.include?(:write_attribute)
|
45
|
+
|
46
|
+
# Create a *_percentage reader and writer.
|
47
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
48
|
+
|
49
|
+
def #{attribute}_multiplier
|
50
|
+
1 - (#{attribute}.to_f || 0)
|
51
|
+
end
|
52
|
+
|
53
|
+
def #{attribute}_increase_multiplier
|
54
|
+
1 + (#{attribute}.to_f || 0)
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :#{attribute}_decrease_multiplier, :#{attribute}_multiplier
|
58
|
+
|
59
|
+
def #{attribute}_percentage
|
60
|
+
@#{attribute}_percentage ||= if #{attribute}.present?
|
61
|
+
#{attribute}.to_f * 100
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.human_attribute_name(attribute, options = {})
|
68
|
+
if attribute =~ /_percentage$/
|
69
|
+
super $`, options
|
70
|
+
else
|
71
|
+
super
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
RUBY
|
76
|
+
|
77
|
+
if define_writer
|
78
|
+
validates_numericality_of :"#{attribute}_percentage", :allow_blank => true
|
79
|
+
|
80
|
+
class_eval <<-RUBY, __FILE__, __LINE__+1
|
81
|
+
|
82
|
+
def #{attribute}_percentage=(value)
|
83
|
+
if value.present?
|
84
|
+
@#{attribute}_percentage = value
|
85
|
+
self.#{attribute} = value.to_f / 100
|
86
|
+
else
|
87
|
+
@#{attribute}_percentage = nil
|
88
|
+
self.#{attribute} = nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
RUBY
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
data/model-x.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'model_x/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "model-x"
|
8
|
+
spec.version = ModelX::VERSION
|
9
|
+
spec.authors = ["Joost Lubach"]
|
10
|
+
spec.email = ["joost@yoazt.com"]
|
11
|
+
spec.description = %q[ Base class that includes many ActiveModel mixins as well as attribute behavior. ]
|
12
|
+
spec.summary = %q[ Base class that includes many ActiveModel mixins as well as attribute behavior. ]
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activemodel"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2.14"
|
26
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Define this class top-level, because it will be accessed as a constant.
|
4
|
+
class ModelXTestRecord
|
5
|
+
attr_reader :id
|
6
|
+
def initialize(id)
|
7
|
+
@id = id
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ModelX::Associations do
|
12
|
+
|
13
|
+
let(:model_x_test_model_class) do
|
14
|
+
Class.new(ModelX::Base) do
|
15
|
+
belongs_to :model_x_test_record
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:model) { model_x_test_model_class.new }
|
20
|
+
let(:records) { [ ModelXTestRecord.new(0), ModelXTestRecord.new(1) ] }
|
21
|
+
|
22
|
+
before do
|
23
|
+
allow(ModelXTestRecord).to receive(:find_by_id) { |id| records[id] }
|
24
|
+
end
|
25
|
+
|
26
|
+
context "trying to access a non-existent association" do
|
27
|
+
specify { expect{ model.something }.to raise_error(NoMethodError) }
|
28
|
+
specify { expect{ model.something_id }.to raise_error(NoMethodError) }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "trying to access an existent association" do
|
32
|
+
specify { expect{ model.model_x_test_record }.not_to raise_error }
|
33
|
+
specify { expect{ model.model_x_test_record_id }.not_to raise_error }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'foreign key' do
|
37
|
+
context "specifying nil" do
|
38
|
+
before { model.model_x_test_record_id = nil }
|
39
|
+
specify { expect(model.model_x_test_record_id).to be_nil }
|
40
|
+
end
|
41
|
+
context "specifying a non-existing ID" do
|
42
|
+
before { model.model_x_test_record_id = 2 }
|
43
|
+
specify { expect(model.model_x_test_record_id).to eql(2) }
|
44
|
+
end
|
45
|
+
context "specifying an existing ID" do
|
46
|
+
before { model.model_x_test_record_id = 1 }
|
47
|
+
specify { expect(model.model_x_test_record_id).to eql(1) }
|
48
|
+
end
|
49
|
+
context "specifying a related object" do
|
50
|
+
before { model.model_x_test_record = records[0] }
|
51
|
+
specify { expect(model.model_x_test_record_id).to eql(0) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'association' do
|
56
|
+
context "without an ID" do
|
57
|
+
specify { expect(model.model_x_test_record).to be_nil }
|
58
|
+
end
|
59
|
+
context "with a nonexisting ID" do
|
60
|
+
before { model.model_x_test_record_id = 2 }
|
61
|
+
specify { expect(model.model_x_test_record).to be_nil }
|
62
|
+
end
|
63
|
+
context "with an existing ID" do
|
64
|
+
before { model.model_x_test_record_id = 1 }
|
65
|
+
specify { expect(model.model_x_test_record).to be(records[1]) }
|
66
|
+
end
|
67
|
+
context "with an existing object" do
|
68
|
+
before { model.model_x_test_record = records[1] }
|
69
|
+
specify { expect(model.model_x_test_record).to be(records[1]) }
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should convert to nil if any blank value is passed" do
|
73
|
+
model.model_x_test_record = ""
|
74
|
+
expect(model.model_x_test_record).to be_nil
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should invalidate a cached record if the id changes" do
|
78
|
+
model.model_x_test_record = records[1]
|
79
|
+
model.model_x_test_record_id = 0
|
80
|
+
expect(model.model_x_test_record).to be(records[0])
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should honour a reload parameter" do
|
84
|
+
model.model_x_test_record = records[1]
|
85
|
+
model.instance_variable_set '@model_x_test_record_id', 0
|
86
|
+
|
87
|
+
expect(model.model_x_test_record).to be(records[1])
|
88
|
+
expect(model.model_x_test_record(true)).to be(records[0])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "option overrides" do
|
93
|
+
context "with another foreign key" do
|
94
|
+
before do
|
95
|
+
class ::ModelXTestRecord2; end
|
96
|
+
|
97
|
+
allow(ModelXTestRecord2).to receive(:find_by_id) { ModelXTestRecord2.new }
|
98
|
+
model_x_test_model_class.class_eval { belongs_to :model_x_test_record2, :foreign_key => :related_object_id }
|
99
|
+
end
|
100
|
+
|
101
|
+
specify { expect(model).to respond_to(:model_x_test_record2) }
|
102
|
+
specify { expect(model).to respond_to(:model_x_test_record2=) }
|
103
|
+
specify { expect(model).to_not respond_to(:model_x_test_record2_id) }
|
104
|
+
specify { expect(model).to_not respond_to(:model_x_test_record2_id=) }
|
105
|
+
specify { expect(model).to respond_to(:related_object_id) }
|
106
|
+
specify { expect(model).to respond_to(:related_object_id=) }
|
107
|
+
|
108
|
+
it "should use the corresponding foreign key" do
|
109
|
+
expect(ModelXTestRecord2).to receive(:find_by_id).with(1)
|
110
|
+
model.related_object_id = 1
|
111
|
+
model.model_x_test_record2
|
112
|
+
end
|
113
|
+
it "should use the corresponding association name" do
|
114
|
+
related_object = ModelXTestRecord2.new
|
115
|
+
expect(related_object).to receive(:id).and_return(5)
|
116
|
+
model.model_x_test_record2 = related_object
|
117
|
+
expect(model.related_object_id).to eql(5)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "with another class name" do
|
122
|
+
it "should use the correct class" do
|
123
|
+
expect(ModelXTestRecord).to receive(:find_by_id).with(1)
|
124
|
+
model_x_test_model_class.class_eval { belongs_to :again_model_x_test_record, :class_name => 'ModelXTestRecord' }
|
125
|
+
model.again_model_x_test_record_id = 1
|
126
|
+
model.again_model_x_test_record
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# TODO: has_many
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ModelX
|
4
|
+
class TestModel < ModelX::Base
|
5
|
+
attribute :status
|
6
|
+
end
|
7
|
+
class DerivedModel < TestModel
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ModelX::Attributes do
|
12
|
+
|
13
|
+
let(:model) { ModelX::TestModel.new }
|
14
|
+
|
15
|
+
context "trying to access a non-existent attribute" do
|
16
|
+
specify { expect{ model.some_attribute = 'test' }.to raise_error(NoMethodError) }
|
17
|
+
specify { expect{ model.some_attribute }.to raise_error(NoMethodError) }
|
18
|
+
specify { expect(model[:some_attribute]).to be_nil }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "trying to access an existent attribute" do
|
22
|
+
specify { expect{ model.status = 'test' }.not_to raise_error }
|
23
|
+
specify { expect{ model.status }.not_to raise_error }
|
24
|
+
specify { model.status = 'test'; expect(model.status).to eql('test') }
|
25
|
+
specify { model.status = 'test'; expect(model[:status]).to eql('test') }
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should not allow an attribute to be defined more than once" do
|
29
|
+
expect{ ModelX::TestModel.class_eval{ attribute :status } }.to raise_error(ModelX::AttributeAlreadyDefined)
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#attributes' do
|
33
|
+
specify { expect(model.attributes).to eql(:status => nil) }
|
34
|
+
context "with a value" do
|
35
|
+
before { model.status = :one }
|
36
|
+
specify { expect(model.attributes).to eql(:status => :one) }
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should stay updated" do
|
40
|
+
model.status = :one
|
41
|
+
model.attributes
|
42
|
+
model.status = :two
|
43
|
+
expect(model.attributes).to eql(:status => :two)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#attributes=' do
|
48
|
+
before { model.attributes = { :status => :one } }
|
49
|
+
specify { expect(model.status).to be(:one) }
|
50
|
+
end
|
51
|
+
|
52
|
+
context "default value" do
|
53
|
+
before(:all) do
|
54
|
+
ModelX::TestModel.class_eval { attribute :type, :default => :post }
|
55
|
+
end
|
56
|
+
|
57
|
+
specify { expect(model.type).to be(:post) }
|
58
|
+
specify { model.type = :attachment; expect(model.type).to be(:attachment) }
|
59
|
+
specify { expect(ModelX::DerivedModel.new.type).to be(:post) }
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ModelX::Boolean
|
4
|
+
class TestModel < ModelX::Base
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ModelX::Boolean do
|
9
|
+
|
10
|
+
let(:model) { ModelX::Boolean::TestModel.new }
|
11
|
+
before(:all) do
|
12
|
+
ModelX::Boolean::TestModel.class_eval { attribute :archived, :type => :boolean }
|
13
|
+
end
|
14
|
+
|
15
|
+
specify { expect(model).to respond_to(:archived?) }
|
16
|
+
specify { model.archived = true; expect(model.archived).to be_true }
|
17
|
+
specify { model.archived = true; expect(model.archived?).to be_true }
|
18
|
+
specify { model.archived = false; expect(model.archived).to be_false }
|
19
|
+
specify { model.archived = false; expect(model.archived?).to be_false }
|
20
|
+
|
21
|
+
specify { model.archived = '0'; expect(model.archived).to be_false }
|
22
|
+
specify { model.archived = 0; expect(model.archived).to be_false }
|
23
|
+
specify { model.archived = ''; expect(model.archived).to be_false }
|
24
|
+
specify { model.archived = 'false'; expect(model.archived).to be_false }
|
25
|
+
specify { model.archived = 'off'; expect(model.archived).to be_false }
|
26
|
+
specify { model.archived = 'no'; expect(model.archived).to be_false }
|
27
|
+
|
28
|
+
specify { model.archived = '1'; expect(model.archived).to be_true }
|
29
|
+
specify { model.archived = 1; expect(model.archived).to be_true }
|
30
|
+
specify { model.archived = '1'; expect(model.archived).to be_true }
|
31
|
+
specify { model.archived = 'true'; expect(model.archived).to be_true }
|
32
|
+
specify { model.archived = 'on'; expect(model.archived).to be_true }
|
33
|
+
specify { model.archived = 'yes'; expect(model.archived).to be_true }
|
34
|
+
specify { model.archived = 'something'; expect(model.archived).to be_true }
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ModelX::Percentage do
|
4
|
+
|
5
|
+
let(:klass) do
|
6
|
+
Class.new(ModelX::Base) do
|
7
|
+
def self.name
|
8
|
+
'PercentageExample'
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :discount
|
12
|
+
percentage :discount
|
13
|
+
|
14
|
+
attr_reader :other
|
15
|
+
percentage :other
|
16
|
+
end
|
17
|
+
end
|
18
|
+
let(:object) { klass.new }
|
19
|
+
|
20
|
+
it "should add a _percentage reader and writer" do
|
21
|
+
expect(object).to respond_to(:discount_percentage)
|
22
|
+
expect(object).to respond_to(:discount_percentage=)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not add a writer on an object that doesn't have an existing writer" do
|
26
|
+
expect(object).to_not respond_to(:other_percentage=)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "percentage writer" do
|
30
|
+
|
31
|
+
it "should set the value / 100" do
|
32
|
+
object.discount_percentage = 80
|
33
|
+
expect(object.discount).to eql(0.8)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should set any non-present value to nil" do
|
37
|
+
object.discount_percentage = false
|
38
|
+
expect(object.discount).to be_nil
|
39
|
+
|
40
|
+
object.discount_percentage = nil
|
41
|
+
expect(object.discount).to be_nil
|
42
|
+
|
43
|
+
object.discount_percentage = ''
|
44
|
+
expect(object.discount).to be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'validation' do
|
50
|
+
|
51
|
+
it "should accept any number" do
|
52
|
+
object.discount_percentage = 10
|
53
|
+
expect(object).to be_valid
|
54
|
+
object.discount_percentage = 110
|
55
|
+
expect(object).to be_valid
|
56
|
+
object.discount_percentage = -10
|
57
|
+
expect(object).to be_valid
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not accept anything else" do
|
61
|
+
object.discount_percentage = 'abcd'
|
62
|
+
expect(object).to_not be_valid
|
63
|
+
expect(object.errors[:discount_percentage]).to_not be_nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should allow blank values" do
|
67
|
+
object.discount_percentage = ''
|
68
|
+
expect(object).to be_valid
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "percentage reader" do
|
74
|
+
|
75
|
+
it "should report the value * 100" do
|
76
|
+
object.discount = 0.5
|
77
|
+
expect(object.discount_percentage).to eql(50.0)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should report nil upon any non-present attribute value" do
|
81
|
+
object.discount = false
|
82
|
+
expect(object.discount_percentage).to be_nil
|
83
|
+
|
84
|
+
object.discount = nil
|
85
|
+
expect(object.discount_percentage).to be_nil
|
86
|
+
|
87
|
+
object.discount = ''
|
88
|
+
expect(object.discount_percentage).to be_nil
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#human_attribute_name override" do
|
94
|
+
|
95
|
+
it "should translate the name of the attribute without _percentage" do
|
96
|
+
expect(klass.human_attribute_name(:discount_percentage)).to eql(klass.human_attribute_name(:discount))
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'model-x'
|
2
|
+
require 'rspec/autorun'
|
3
|
+
|
4
|
+
# require 'simplecov'
|
5
|
+
# SimpleCov.start do
|
6
|
+
# add_filter "_spec.rb"
|
7
|
+
# add_filter "spec/"
|
8
|
+
# add_group "Libs", "../libs/"
|
9
|
+
# end
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
|
13
|
+
config.mock_with :rspec do |config|
|
14
|
+
config.syntax = :expect
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
20
|
+
# in spec/support/ and its subdirectories.
|
21
|
+
Dir[File.expand_path("../support/**/*.rb", __FILE__)].each {|f| require f}
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: model-x
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joost Lubach
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-07 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: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.14'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.14'
|
69
|
+
description: ' Base class that includes many ActiveModel mixins as well as attribute
|
70
|
+
behavior. '
|
71
|
+
email:
|
72
|
+
- joost@yoazt.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- .ruby-gemset
|
79
|
+
- .ruby-version
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- lib/model-x.rb
|
86
|
+
- lib/model_x.rb
|
87
|
+
- lib/model_x/associations.rb
|
88
|
+
- lib/model_x/attributes.rb
|
89
|
+
- lib/model_x/base.rb
|
90
|
+
- lib/model_x/boolean.rb
|
91
|
+
- lib/model_x/mixin.rb
|
92
|
+
- lib/model_x/percentage.rb
|
93
|
+
- lib/model_x/version.rb
|
94
|
+
- model-x.gemspec
|
95
|
+
- spec/model_x/associations_spec.rb
|
96
|
+
- spec/model_x/attributes_spec.rb
|
97
|
+
- spec/model_x/boolean_spec.rb
|
98
|
+
- spec/model_x/percentage_spec.rb
|
99
|
+
- spec/spec_helper.rb
|
100
|
+
homepage: ''
|
101
|
+
licenses:
|
102
|
+
- MIT
|
103
|
+
metadata: {}
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
requirements:
|
110
|
+
- - '>='
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: '0'
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
requirements: []
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 2.1.4
|
121
|
+
signing_key:
|
122
|
+
specification_version: 4
|
123
|
+
summary: Base class that includes many ActiveModel mixins as well as attribute behavior.
|
124
|
+
test_files:
|
125
|
+
- spec/model_x/associations_spec.rb
|
126
|
+
- spec/model_x/attributes_spec.rb
|
127
|
+
- spec/model_x/boolean_spec.rb
|
128
|
+
- spec/model_x/percentage_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
has_rdoc:
|