model-x 0.0.1

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: 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: