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 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in model-x.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ # File added so users can require 'model-x'
2
+ require 'model_x'
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>&lt;type&gt; :&lt;attribute&gt;</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
@@ -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
@@ -0,0 +1,3 @@
1
+ module ModelX
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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: