simple_model 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- simple_model (1.0.0)
4
+ simple_model (1.1.0)
5
5
  activemodel (~> 3.0)
6
6
  activesupport (~> 3.0)
7
7
 
data/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  # SimpleModel
2
- A collection of convenience methods for building table-less models. If ActiveModel
3
- gem is installed, SimpleModel::Based will include ActiveModel::Validations,
4
- include ActiveModel::Conversion and extend ActiveModel::Naming. If ActiveModel
5
- gem is not available, SimpleModel::Base defaults to its own built-in Error and Validation modules.
2
+ A collection of convenience methods for building table-less models.
3
+ SimpleModel implements:
4
+
5
+ * ActiveModel::Validations
6
+ * ActiveModel::Conversion
7
+ * ActiveModel::Validations::Callbacks
8
+ * ActiveModel::Dirty
9
+ * ActiveModel::Naming
10
+ * ActiveModel::Callbacks
11
+ * ActiveSupport core extentions (see [SimpleModel::Base](https://github.com/JoshMcKin/simple_model/blob/master/lib/simple_model/base.rb))
12
+
13
+ Additionally SimpleModel implements basic model actions
6
14
 
7
15
  ## Installation
8
16
 
@@ -16,11 +24,20 @@ SimpleModel is available through [Rubygems](http://rubygems.org/gems/simple_mode
16
24
  require 'simple_model'
17
25
 
18
26
  class Item < SimpleModel::Base
27
+ # Model Actions
19
28
  save :save_item, :rollback => :undo_save
29
+
30
+ # Callbacks
31
+ before_validation :add_to_array
32
+
33
+ # Attributes
20
34
  has_booleans :active, :default => true
21
35
  has_booleans :paid
22
- has_currency :price, :default => 10.0
36
+ has_currency :price, :default => 10.0.to_currency
23
37
  has_times :created_at, :default => :now
38
+ has_attribute :my_array, :default => []
39
+
40
+ # Validation
24
41
  validates_inclusion_of :price, :in => 10..25
25
42
 
26
43
  def now
@@ -30,7 +47,13 @@ SimpleModel is available through [Rubygems](http://rubygems.org/gems/simple_mode
30
47
  def file_name
31
48
  @file_name ||= "receipt-#{self.created_at.to_i}.txt"
32
49
  end
33
-
50
+
51
+ private
52
+
53
+ def add_to_array
54
+ my_array << 1
55
+ end
56
+
34
57
  def save_item
35
58
  begin
36
59
  File.open(self.file_name, 'w') do |receipt|
@@ -50,17 +73,25 @@ SimpleModel is available through [Rubygems](http://rubygems.org/gems/simple_mode
50
73
  end
51
74
 
52
75
  item = Item.new
53
- item.created_at # => 2011-10-23 21:56:07 -0500
54
- item.created_at # => 2011-10-23 21:56:08 -0500
55
- item.active # => true
56
- item.paid # => nil
57
- item.paid? # => false
58
- item.price # => 10.0
76
+ item.changed? # => false
77
+ item.created_at # => 2011-10-23 21:56:07 -0500
78
+ item.created_at # => 2011-10-23 21:56:08 -0500
79
+ item.active # => true
80
+ item.paid # => nil
81
+ item.paid? # => false
82
+ item.price # => 10.0
59
83
  item.price = '$1,024.00'
60
- item.price # => #<BigDecimal:100c989d8,'0.1024E4',9(27)>
61
- item.valid? # => false
84
+ item.price # => #<BigDecimal:100c989d8,'0.1024E4',9(27)>
85
+ item.changed? # => true
86
+ item.price_changed? # => true
87
+ item.changes # => {"price"=>[#<BigDecimal:7fc61b250da8,'0.1E2',9(27)>, #<BigDecimal:7fc61b1ba600,'0.1024E4',9(27)>]}
88
+ item.my_array # => []
89
+ item.valid? # => false
90
+ item.my_array # => [1]
62
91
  item.price = 15
63
- item.save # => true
92
+ item.save # => true
93
+ item.changed? # => false
94
+ item.previous_changes # => {"price"=>[#<BigDecimal:7fc61b1ba600,'0.1024E4',9(27)>, #<BigDecimal:7fc61b1730e8,'0.15E2',9(27)>], "saved"=>[nil, true]}
64
95
 
65
96
 
66
97
 
@@ -5,7 +5,8 @@ module SimpleModel
5
5
 
6
6
  module Attributes
7
7
  include ExtendCore
8
-
8
+ extend ActiveSupport::Concern
9
+ include ActiveModel::AttributeMethods
9
10
  #Set attribute values to those supplied at initialization
10
11
  def initialize(*attrs)
11
12
  set_attributes(attrs.extract_options!)
@@ -22,63 +23,99 @@ module SimpleModel
22
23
  self.send("#{attr[0].to_sym}=",attr[1])
23
24
  end
24
25
  end
26
+
27
+ # Hook to run method after attribute is converted but before it is set
28
+ def before_attribute_set(method,val)
29
+ end
30
+
25
31
  alias :update_attributes :set_attributes
26
32
 
27
33
  def self.included(base)
28
34
  base.extend(ClassMethods)
29
35
  end
36
+
37
+ def fetch_default
38
+ end
39
+
30
40
 
31
41
  module ClassMethods
32
-
42
+
43
+ # Hook to call class method after attribute method definitions
44
+ def after_attribute_definition(attr)
45
+ end
46
+
47
+ # Defines a reader method that returns a default value if current value
48
+ # is nil, if :default is present in the options hash
49
+ def define_reader_with_options(attr,options)
50
+ if options.has_key?(:default)
51
+ define_method(attr.to_s) do
52
+ default = (options[:default].is_a?(Symbol) ? self.send(options[:default]) : options[:default])
53
+ val = instance_variable_get("@#{attr.to_s}")
54
+ val = default unless instance_variable_defined?("@#{attr.to_s}")
55
+ val
56
+ end
57
+ else
58
+ attr_reader attr
59
+ end
60
+ end
61
+
62
+ def define_setter(attr,cast_methods)
63
+ define_method("#{attr.to_s}=") do |val|
64
+ val = val.cast_to(cast_methods)
65
+ before_attribute_set(attr,val)
66
+ instance_variable_set("@#{attr}", val)
67
+ attributes[attr] = val
68
+ val
69
+ end
70
+ end
71
+
72
+ # Builder for attribute methods
73
+ def build_attribute_methods(attr,options={},cast_methods=[])
74
+ define_reader_with_options(attr,options)
75
+ define_setter(attr,cast_methods)
76
+ after_attribute_definition attr
77
+ end
78
+
79
+ # Left this use a module eval for reference, saw no noticable improvement
80
+ # in speed, so I would rather use code than strings for now
81
+ # def define_setter_with_eval(attr,cast_methods)
82
+ # module_eval <<-STR, __FILE__, __LINE__
83
+ # def #{attr.to_s}=#{attr.to_s}
84
+ # val = #{attr.to_s}.cast_to(#{cast_methods})
85
+ # before_attribute_set(:#{attr.to_s},val)
86
+ # @#{attr.to_s} = val
87
+ # attributes[:#{attr.to_s}] = val
88
+ # val
89
+ # end
90
+ # STR
91
+ # end
92
+
33
93
  #creates setter and getter datatype special attribute
34
94
  def has_attributes(*attrs)
35
95
  options = attrs.extract_options!
36
96
  attrs.each do |attr|
37
-
38
- attr_reader attr
39
- define_reader_with_options(attr,options)
40
- define_method("#{attr.to_s}=") do |val|
41
- instance_variable_set("@#{attr}", val)
42
- attributes[attr] = val
43
- val
44
-
45
- end
97
+ build_attribute_methods(attr,options)
46
98
  end
47
99
  end
100
+ alias :has_attribute :has_attributes
48
101
 
49
102
  # Creates setter and getter methods for boolean attributes
50
103
  def has_booleans(*attrs)
51
104
  options = attrs.extract_options!
52
- attrs.each do |attr|
53
-
54
- attr_reader attr
55
- define_reader_with_options(attr,options)
56
- define_method("#{attr.to_s}=") do |val|
57
- instance_variable_set("@#{attr}", val.to_s.to_b)
58
- attributes[attr] = val
59
- val
60
- end
61
-
105
+ attrs.each do |attr|
106
+ build_attribute_methods(attr,options,[:to_s,:to_b])
62
107
  define_method ("#{attr.to_s}?") do
63
108
  send("#{attr.to_s}".to_sym).to_s.to_b
64
109
  end
65
110
  end
66
111
  end
67
- alias :has_boolean :has_booleans
112
+ alias :has_boolean :has_booleans
68
113
 
69
114
  # Creates setter and getter methods for integer attributes
70
115
  def has_ints(*attrs)
71
116
  options = attrs.extract_options!
72
117
  attrs.each do |attr|
73
- attr_reader attr
74
- define_reader_with_options(attr,options)
75
-
76
- define_method("#{attr.to_s}=") do |val|
77
- instance_variable_set("@#{attr}", val.to_i)
78
- attributes[attr] = val
79
- val
80
-
81
- end
118
+ build_attribute_methods(attr,options,[:to_i])
82
119
  end
83
120
  end
84
121
  alias :has_int :has_ints
@@ -90,26 +127,16 @@ module SimpleModel
90
127
  def has_currency(*attrs)
91
128
  options = attrs.extract_options!
92
129
  attrs.each do |attr|
93
- attr_reader attr
94
- define_reader_with_options(attr,options)
95
- define_method("#{attr.to_s}=") do |val|
96
- instance_variable_set("@#{attr}", val.to_s.to_currency)
97
- attributes[attr] = val
98
- val
99
- end
130
+ build_attribute_methods(attr,options,[:to_s,:to_currency])
131
+
100
132
  end
101
133
  end
102
134
 
103
135
  def has_decimals(*attrs)
104
136
  options = attrs.extract_options!
105
137
  attrs.each do |attr|
106
- attr_reader attr
107
- define_reader_with_options(attr,options)
108
- define_method("#{attr.to_s}=") do |val|
109
- instance_variable_set("@#{attr}", BigDecimal("#{val}"))
110
- attributes[attr] = val
111
- val
112
- end
138
+ build_attribute_methods(attr,options,[:to_f,:to_d])
139
+
113
140
  end
114
141
  end
115
142
  alias :has_decimal :has_decimals
@@ -118,70 +145,31 @@ module SimpleModel
118
145
  def has_floats(*attrs)
119
146
  options = attrs.extract_options!
120
147
  attrs.each do |attr|
148
+ build_attribute_methods(attr,options,[:to_f])
121
149
 
122
- attr_reader attr
123
- define_reader_with_options(attr,options)
124
-
125
- define_method("#{attr.to_s}=") do |val|
126
- instance_variable_set("@#{attr}", val.to_f)
127
- attributes[attr] = val
128
- val
129
-
130
- end
131
150
  end
132
151
  end
133
152
  alias :has_float :has_floats
153
+
134
154
  # Creates setter and getter methods for date attributes
135
155
  def has_dates(*attrs)
136
156
  options = attrs.extract_options!
137
157
  attrs.each do |attr|
158
+ build_attribute_methods(attr,options,[:to_s,:to_date])
138
159
 
139
- attr_reader attr
140
- define_reader_with_options(attr,options)
141
- define_method("#{attr.to_s}=") do |val|
142
- val = val.to_date unless val.nil?
143
- instance_variable_set("@#{attr}", val )
144
- attributes[attr] = val
145
- val
146
-
147
- end
148
160
  end
149
161
  end
150
162
  alias :has_date :has_dates
163
+
151
164
  # Creates setter and getter methods for time attributes
152
165
  def has_times(*attrs)
153
166
  options = attrs.extract_options!
154
167
  attrs.each do |attr|
155
- attr_reader attr
156
- define_reader_with_options(attr,options)
157
- define_method("#{attr.to_s}=") do |val|
158
- val = val.to_time unless val.nil?
159
- instance_variable_set("@#{attr}", val)
160
- attributes[attr] = val
161
- val
162
- end
163
- end
164
- end
165
-
166
- def fetch_alias_name(attr)
167
- alias_name = (attr.to_s << "_old=").to_sym
168
- self.module_eval("alias #{alias_name} #{attr}")
169
-
170
- alias_name
171
- end
168
+ build_attribute_methods(attr,options,[:to_s,:to_time])
172
169
 
173
- # Defines a reader method that returns a default value if current value
174
- # is nil, if :default is present in the options hash
175
- def define_reader_with_options(attr,options)
176
- unless options[:default].blank?
177
- define_method (attr.to_s) do
178
- default = options[:default].is_a?(Symbol) ? self.send(options[:default]) : options[:default]
179
- val = instance_variable_get("@#{attr.to_s}")
180
- val = default if val.nil?
181
- val
182
- end
183
170
  end
184
- end
171
+ end
172
+ alias :has_time :has_times
185
173
  end
186
174
  end
187
175
  end
@@ -20,8 +20,8 @@ module SimpleModel
20
20
  #
21
21
  # Provides an interface for any class to build tabless models.
22
22
  #
23
- # Implements Validations and Callsbacks from ActiveModel and attribute datatype specific
24
- # attribute definition with default options
23
+ # Implements Validations, Callbacks and Dirty from ActiveModel, and datatype specific
24
+ # attribute definitions with default options
25
25
  #
26
26
  # == SimpleModel Actions:
27
27
  #
@@ -82,38 +82,51 @@ module SimpleModel
82
82
  extend ActiveModel::Naming
83
83
  extend ActiveModel::Callbacks
84
84
  include ActiveModel::Validations::Callbacks
85
+ include ActiveModel::Dirty
86
+
85
87
  define_model_callbacks :save, :update, :create, :destroy
86
-
88
+
89
+ class << self
90
+
91
+ # Collect methods as they are defined, then add to define_attribute_methods
92
+ def after_attribute_definition(method)
93
+ @defined_attribute_methods ||= []
94
+ @defined_attribute_methods << method
95
+ define_attribute_methods @defined_attribute_methods
96
+ end
97
+
98
+ def save(*methods)
99
+ define_model_action(methods,:save)
100
+ end
101
+
102
+ def create(*methods)
103
+ define_model_action(methods,:create)
104
+ end
105
+
106
+ def update(*methods)
107
+ define_model_action(methods,:update)
108
+ end
109
+
110
+ #Destroy does not run normal validation by default.
111
+ def destroy(*methods)
112
+ define_model_action(methods,:destroy, {:validate => false})
113
+ end
114
+ end
115
+
87
116
  has_boolean :saved
88
117
  has_boolean :new_record, :default => true
89
-
90
118
  attr_accessor :id
91
-
119
+
92
120
  def persisted?
93
121
  saved?
94
122
  end
95
-
96
123
 
97
- def self.save(*methods)
98
- define_model_action(methods,:save)
99
- end
100
-
101
- def self.create(*methods)
102
- define_model_action(methods,:create)
103
- end
104
-
105
- def self.update(*methods)
106
- define_model_action(methods,:update)
107
- end
108
-
109
- #Destroy does not run normal validation by default.
110
- def self.destroy(*methods)
111
- define_model_action(methods,:destroy, {:validate => false})
112
- end
124
+ def before_attribute_set(method,val)
125
+ send("#{method.to_s}_will_change!") unless val == instance_variable_get("@#{method.to_s}")
126
+ end
113
127
 
114
128
  private
115
-
116
-
129
+
117
130
  # Skeleton for action instance methods
118
131
  def run_model_action(methods,options)
119
132
  completed = true
@@ -125,8 +138,11 @@ module SimpleModel
125
138
  ran = self.send(method)
126
139
  completed = ran unless ran
127
140
  end
141
+
128
142
  if completed
129
143
  self.saved = true
144
+ @previously_changed = changes
145
+ @changed_attributes.clear
130
146
  else
131
147
  self.send(options[:rollback]) unless options[:rollback].blank?
132
148
  end
@@ -5,6 +5,19 @@ module SimpleModel
5
5
  require 'bigdecimal'
6
6
  require 'bigdecimal/util'
7
7
 
8
+ Object.class_eval do
9
+
10
+ # Expects an array of symboles representing methods for casting the object
11
+ # EX: "1".cast_to(:float) # => 1.0
12
+ def cast_to(methods=[])
13
+ val = self
14
+ methods.each do |method|
15
+ val = val.send(method)
16
+ end
17
+ val
18
+ end
19
+ end
20
+
8
21
  Float.class_eval do
9
22
 
10
23
  # any value greater than 0.0 is true
@@ -1,11 +1,13 @@
1
1
  module SimpleModel
2
2
  class SimpleModelRailtie < ::Rails::Railtie
3
3
  initializer "simple_model.extend_core" do
4
- extend SimpleModel::ExtendCore
4
+ extend SimpleModel::ExtendCore
5
5
  end
6
- initializer "simple_model.ar_error_helper" do
7
- class ActiveRecord::Base
8
- include SimpleModel::ErrorHelpers
6
+ if Object.const_defined?(:ActiveRecord)
7
+ initializer "simple_model.ar_error_helper" do
8
+ class ActiveRecord::Base
9
+ include SimpleModel::ErrorHelpers
10
+ end
9
11
  end
10
12
  end
11
13
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleModel
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -75,3 +75,18 @@ describe SimpleModel::Attributes, 'has_booleans' do
75
75
  end
76
76
 
77
77
  end
78
+ describe SimpleModel::Attributes, 'has_attributes' do
79
+ before(:all) do
80
+ class TestBoolean
81
+ include SimpleModel::Attributes
82
+ has_attributes :my_array, :default => []
83
+ end
84
+ end
85
+
86
+ it "should allow default value to be an empty array" do
87
+ test = TestBoolean.new
88
+ test.my_array.should eql([])
89
+ end
90
+
91
+ end
92
+
@@ -147,6 +147,25 @@ describe SimpleModel do
147
147
  t.foo.should eql('foo')
148
148
  t.bar.should eql('bar')
149
149
  end
150
+
151
+ it 'Should implement ActiveModel::Dirty' do
152
+ class TestStuff < SimpleModel::Base
153
+ save :my_save_method
154
+ has_attributes :foo,:bar, :default => "def"
155
+ def my_save_method
156
+ true
157
+ end
158
+ end
159
+
160
+ t = TestStuff.new
161
+ t.foo = "bar"
162
+ t.foo_changed?.should be_true
163
+ t.foo_change.should eql(["def","bar"])
164
+ t.changed?.should be_true
165
+ t.save
166
+ t.changed?.should be_false
167
+ end
168
+
150
169
  end
151
170
 
152
171
  #describe SimpleModel::Errors do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_model
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-24 00:00:00.000000000Z
12
+ date: 2011-11-16 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70355814091020 !ruby/object:Gem::Requirement
16
+ requirement: &70101481150080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70355814091020
24
+ version_requirements: *70101481150080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activemodel
27
- requirement: &70355814090520 !ruby/object:Gem::Requirement
27
+ requirement: &70101481149580 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '3.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70355814090520
35
+ version_requirements: *70101481149580
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70355814090140 !ruby/object:Gem::Requirement
38
+ requirement: &70101481149200 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70355814090140
46
+ version_requirements: *70101481149200
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: autotest
49
- requirement: &70355814089680 !ruby/object:Gem::Requirement
49
+ requirement: &70101481148740 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70355814089680
57
+ version_requirements: *70101481148740
58
58
  description: Simpifies building tableless models or models backed by webservices.
59
59
  Create data type specific attributes with default if values.
60
60
  email: