openteam-modest_model 0.1.1.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.
- data/MIT-LICENSE +20 -0
- data/README.md +72 -0
- data/Rakefile +35 -0
- data/lib/modest_model.rb +12 -0
- data/lib/modest_model/base.rb +69 -0
- data/lib/modest_model/callbacks.rb +42 -0
- data/lib/modest_model/combined_attr.rb +9 -0
- data/lib/modest_model/resource.rb +9 -0
- data/lib/modest_model/tenacity.rb +181 -0
- data/lib/modest_model/validators.rb +9 -0
- data/test/compliance_test.rb +26 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +9 -0
- data/test/dummy/app/assets/stylesheets/application.css +7 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +42 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +10 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +27 -0
- data/test/dummy/config/environments/production.rb +51 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +10 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +12 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +26 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/fixtures/sample_model.rb +4 -0
- data/test/fixtures/sample_resource.rb +45 -0
- data/test/modest_model_test.rb +49 -0
- data/test/sample_resource_test.rb +153 -0
- data/test/test_helper.rb +10 -0
- metadata +160 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2011 Mike Fulcher
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# ModestModel [](https://secure.travis-ci.org/6twenty/modest_model.png])
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Inspired by [Crafting Rails Applications](http://pragprog.com/book/jvrails/crafting-rails-applications), ModestModel provides an ActiveModel-compliant class that allows you to quickly create simple, table-less models. The intended use is to back interactions with external APIs with Ruby-friendly models rather than raw structured data (such as hashes).
|
6
|
+
|
7
|
+
**Notice:** we're extending ModestModel's features significantly to give developers more flexibility and freedom when working with table-less models. Some of the upcoming features include:
|
8
|
+
|
9
|
+
- **ActiveRecord support:** including simple delegation of methods like `save` and `destroy`, associations (`has_many`, `belongs_to` and friends) and other commonly used ActiveRecord features
|
10
|
+
- **Basic objects:** to make working with simple key/value data sets easier and friendlier (think of a blend of a Hash and a Struct bundled with a collection of helpful methods)
|
11
|
+
|
12
|
+
## Example
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
json = MyExternalApi.call('/some/path.json')
|
16
|
+
attributes_hash = JSON.decode(json)
|
17
|
+
|
18
|
+
# => {'name' => 'Michael', 'email' => 'michael@example.com'}
|
19
|
+
|
20
|
+
class SampleModel < ModestModel::Base
|
21
|
+
attributes :name, :email
|
22
|
+
end
|
23
|
+
|
24
|
+
SampleModel.new(attributes_hash)
|
25
|
+
|
26
|
+
# => #<SampleModel @name="Michael"...
|
27
|
+
```
|
28
|
+
|
29
|
+
## Installation
|
30
|
+
|
31
|
+
ModestModel has been tested and works on MRI 1.8.7 and 1.9.2.
|
32
|
+
|
33
|
+
### Rubygems
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem install modest_model
|
37
|
+
```
|
38
|
+
|
39
|
+
### Bundler
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
gem 'modest_model'
|
43
|
+
```
|
44
|
+
|
45
|
+
### Usage
|
46
|
+
|
47
|
+
Similar to ActiveRecord models, simply create a class which inherits from `ModestModel::Base`. You'll need to define some attributes - this is achieved by calling the `attributes` method passing in the attribute names you require:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class SampleModel < ModestModel::Base
|
51
|
+
attributes :name, :email
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
## Features
|
56
|
+
|
57
|
+
ModestModel includes the following ActiveModel modules:
|
58
|
+
|
59
|
+
* `ActiveModel::Conversion`
|
60
|
+
* `ActiveModel::Naming`
|
61
|
+
* `ActiveModel::Translation`
|
62
|
+
* `ActiveModel::Serialization`
|
63
|
+
* `ActiveModel::Validations`
|
64
|
+
* `ActiveModel::AttributeMethods`
|
65
|
+
|
66
|
+
These allow your ModestModel models to act almost the same as an ActiveRecord model, but without the database. You can mass-assign attributes, add validations, add translations, and call familiar methods like `model_name.human` and `to_json`.
|
67
|
+
|
68
|
+
ModestModel was extracted from the Mail Form gem described in chapter 2, "Building Models with Active Model", of [Crafting Rails Applications](http://pragprog.com/book/jvrails/crafting-rails-applications) by Jose Valim. This chapter therefore provides an excellent in-depth explanation of the inner workings of ModestModel.
|
69
|
+
|
70
|
+
## Credits
|
71
|
+
|
72
|
+
This code has been derived from [Crafting Rails Applications](http://pragprog.com/book/jvrails/crafting-rails-applications) by Jose Valim, so all credit goes to Jose and [Plataforma](http://blog.plataformatec.com.br/) for granting permission to release this gem.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
rescue LoadError
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
+
end
|
8
|
+
begin
|
9
|
+
require 'rdoc/task'
|
10
|
+
rescue LoadError
|
11
|
+
require 'rdoc/rdoc'
|
12
|
+
require 'rake/rdoctask'
|
13
|
+
RDoc::Task = Rake::RDocTask
|
14
|
+
end
|
15
|
+
|
16
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'ModestModel'
|
19
|
+
rdoc.options << '--line-numbers'
|
20
|
+
rdoc.rdoc_files.include('README.md')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
Bundler::GemHelper.install_tasks
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
|
28
|
+
Rake::TestTask.new(:test) do |t|
|
29
|
+
t.libs << 'lib'
|
30
|
+
t.libs << 'test'
|
31
|
+
t.pattern = 'test/**/*_test.rb'
|
32
|
+
t.verbose = false
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :test
|
data/lib/modest_model.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_model'
|
3
|
+
rescue LoadError => e
|
4
|
+
retry if require('rubygems')
|
5
|
+
end
|
6
|
+
|
7
|
+
module ModestModel
|
8
|
+
autoload :Base, File.expand_path('../modest_model/base', __FILE__)
|
9
|
+
autoload :Validators, File.expand_path('../modest_model/validators', __FILE__)
|
10
|
+
autoload :CombinedAttr, File.expand_path('../modest_model/combined_attr', __FILE__)
|
11
|
+
autoload :Resource, File.expand_path('../modest_model/resource', __FILE__)
|
12
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module ModestModel
|
2
|
+
class Base
|
3
|
+
include ActiveModel::Conversion
|
4
|
+
extend ActiveModel::Naming
|
5
|
+
extend ActiveModel::Translation
|
6
|
+
include ActiveModel::Serialization
|
7
|
+
include ActiveModel::Validations
|
8
|
+
include ActiveModel::AttributeMethods
|
9
|
+
|
10
|
+
include ModestModel::Validators
|
11
|
+
extend ModestModel::CombinedAttr
|
12
|
+
|
13
|
+
def initialize(attributes = {}, options={})
|
14
|
+
self.assign_attributes(attributes, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
class_attribute :_attributes
|
18
|
+
self._attributes = []
|
19
|
+
|
20
|
+
attribute_method_prefix 'clear_'
|
21
|
+
attribute_method_suffix '?'
|
22
|
+
|
23
|
+
def self.attributes(*names)
|
24
|
+
attr_accessor *names
|
25
|
+
define_attribute_methods names
|
26
|
+
|
27
|
+
self._attributes += names
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes
|
31
|
+
self._attributes.inject({}) do |hash, attr|
|
32
|
+
hash[attr.to_s] = send(attr)
|
33
|
+
hash
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def attributes= attributes, options = {}
|
38
|
+
assign_attributes attributes, options = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def [] attr
|
42
|
+
attributes[attr.to_s]
|
43
|
+
end
|
44
|
+
|
45
|
+
def []= attr, val
|
46
|
+
self.send "#{attr}=", val
|
47
|
+
end
|
48
|
+
|
49
|
+
def persisted?
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def assign_attributes attributes, options = {}
|
56
|
+
attributes.each do |attr, value|
|
57
|
+
self[attr] = value
|
58
|
+
end unless attributes.blank?
|
59
|
+
end
|
60
|
+
|
61
|
+
def clear_attribute(attribute)
|
62
|
+
send("#{attribute}=", nil)
|
63
|
+
end
|
64
|
+
|
65
|
+
def attribute?(attribute)
|
66
|
+
send(attribute).present?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ModestModel
|
2
|
+
module Callbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
CALLBACKS = [
|
6
|
+
:after_initialize, :after_find, :before_validation, :after_validation,
|
7
|
+
:before_save, :around_save, :after_save, :before_create, :around_create,
|
8
|
+
:after_create, :before_update, :around_update, :after_update,
|
9
|
+
:before_destroy, :around_destroy, :after_destroy
|
10
|
+
]
|
11
|
+
|
12
|
+
included do
|
13
|
+
extend ActiveModel::Callbacks
|
14
|
+
include ActiveModel::Validations::Callbacks
|
15
|
+
|
16
|
+
define_model_callbacks :initialize, :find, :only => :after
|
17
|
+
define_model_callbacks :save, :create, :update, :destroy
|
18
|
+
end
|
19
|
+
|
20
|
+
def found #:nodoc:
|
21
|
+
run_callbacks(:find) { super }
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy #:nodoc:
|
25
|
+
run_callbacks(:destroy) { super }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def create_or_update #:nodoc:
|
31
|
+
run_callbacks(:save) { super }
|
32
|
+
end
|
33
|
+
|
34
|
+
def create #:nodoc:
|
35
|
+
run_callbacks(:create) { super }
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(*) #:nodoc:
|
39
|
+
run_callbacks(:update) { super }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module ModestModel
|
2
|
+
|
3
|
+
class ResourceNotFound < ::StandardError; end
|
4
|
+
class InvalidResource < ::StandardError; end
|
5
|
+
|
6
|
+
module Tenacity
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :_find
|
11
|
+
self._find = Proc.new {}
|
12
|
+
|
13
|
+
class_attribute :_save
|
14
|
+
self._save = Proc.new {}
|
15
|
+
|
16
|
+
class_attribute :_destroy
|
17
|
+
self._destroy = Proc.new {}
|
18
|
+
|
19
|
+
class_attribute :primary_key
|
20
|
+
set_primary_key :id
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
|
25
|
+
def undefine_attribute_method attr # :nodoc:
|
26
|
+
self._attributes = self._attributes - [attr]
|
27
|
+
undef_method attr if method_defined? attr
|
28
|
+
undef_method "#{attr}=" if method_defined? "#{attr}="
|
29
|
+
end
|
30
|
+
|
31
|
+
# Sets the +primary_key+ and its associated attribute
|
32
|
+
def set_primary_key attr
|
33
|
+
undefine_attribute_method :id if attr == :id # Remove id, always
|
34
|
+
if self.primary_key
|
35
|
+
undefine_attribute_method self.primary_key
|
36
|
+
# TODO: remove any validations on the current PK
|
37
|
+
# self._validators.except!(self.primary_key.to_sym)
|
38
|
+
end
|
39
|
+
self.primary_key = attr
|
40
|
+
attributes attr
|
41
|
+
# TODO: add a presence validation on the new PK
|
42
|
+
# validates attr, :presence => true
|
43
|
+
end
|
44
|
+
|
45
|
+
# When a block is passed, that block is set to +_find+.
|
46
|
+
# When +id_+ is passed (or no +block+ is passed) a new record is instantiated with the passed +id_+ and the +_find+ block is called.
|
47
|
+
# If the return from the +_find+ block is +nil+ or +false+, a ResourceNotFound error is raised.
|
48
|
+
def find id_=nil, &block
|
49
|
+
if block_given?
|
50
|
+
self._find = block
|
51
|
+
else
|
52
|
+
resource = new(primary_key.to_sym => id_)
|
53
|
+
resource.send(:call_find!) || raise(ModestModel::ResourceNotFound)
|
54
|
+
resource.found
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# TODO - docs
|
59
|
+
def create attributes = nil, options = {}, &block
|
60
|
+
if attributes.is_a?(Array)
|
61
|
+
attributes.collect { |attr| create(attr, options, &block) }
|
62
|
+
else
|
63
|
+
object = new(attributes, options)
|
64
|
+
yield(object) if block_given?
|
65
|
+
object.save
|
66
|
+
object
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# The passed +block+ is set to +_save+ and called on save
|
71
|
+
def save &block
|
72
|
+
self._save = block if block_given?
|
73
|
+
end
|
74
|
+
|
75
|
+
# The passed +block+ is set to +_destroy+ and called on destroyed
|
76
|
+
def destroy &block
|
77
|
+
self._destroy = block if block_given?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module InstanceMethods
|
82
|
+
|
83
|
+
def initialize attributes = {}, options={} #:nodoc:
|
84
|
+
super attributes, options
|
85
|
+
@new_resource = true
|
86
|
+
@destroyed = false
|
87
|
+
end
|
88
|
+
|
89
|
+
def found #:nodoc:
|
90
|
+
@new_resource = false
|
91
|
+
return self
|
92
|
+
end
|
93
|
+
|
94
|
+
def to_param #:nodoc:
|
95
|
+
send(self.class.primary_key)
|
96
|
+
end
|
97
|
+
|
98
|
+
def new_resource? #:nodoc:
|
99
|
+
@new_resource
|
100
|
+
end
|
101
|
+
alias :new_record? :new_resource?
|
102
|
+
|
103
|
+
def destroyed? #:nodoc:
|
104
|
+
@destroyed
|
105
|
+
end
|
106
|
+
|
107
|
+
def persisted? #:nodoc:
|
108
|
+
false
|
109
|
+
end
|
110
|
+
|
111
|
+
# Runs the save block and returns true if the operation was successful, false if not
|
112
|
+
def save(*)
|
113
|
+
create_or_update
|
114
|
+
end
|
115
|
+
|
116
|
+
# Runs the save block and returns true if the operation was successful or raises an InvalidResource error if not
|
117
|
+
def save!(*)
|
118
|
+
create_or_update || raise(ModestModel::InvalidResource)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Runs the destroy block and sets the resource as destroyed
|
122
|
+
def destroy
|
123
|
+
call_destroy!
|
124
|
+
@destroyed = true
|
125
|
+
return self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Updates a single attribute and calls save.
|
129
|
+
# This is especially useful for boolean flags on existing records. Also note that
|
130
|
+
#
|
131
|
+
# * Validation is skipped.
|
132
|
+
# * Callbacks are invoked.
|
133
|
+
#
|
134
|
+
def update_attribute(name, value)
|
135
|
+
send("#{name.to_s}=", value)
|
136
|
+
save(:validate => false)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Updates the attributes of the model from the passed-in hash and calls save the
|
140
|
+
# will fail and false will be returned.
|
141
|
+
def update_attributes(attributes, options = {})
|
142
|
+
self.assign_attributes(attributes, options)
|
143
|
+
save
|
144
|
+
end
|
145
|
+
|
146
|
+
# Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
|
147
|
+
# of +save+, so an exception is raised if the resource is invalid.
|
148
|
+
def update_attributes!(attributes, options = {})
|
149
|
+
self.assign_attributes(attributes, options)
|
150
|
+
save!
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def create_or_update #:nodoc:
|
156
|
+
valid? ? (new_resource? ? create : update) : false
|
157
|
+
end
|
158
|
+
|
159
|
+
def create #:nodoc:
|
160
|
+
@new_resource = false
|
161
|
+
call_save!
|
162
|
+
end
|
163
|
+
|
164
|
+
def update #:nodoc:
|
165
|
+
call_save!
|
166
|
+
end
|
167
|
+
|
168
|
+
def call_save! #:nodoc:
|
169
|
+
instance_exec &self.class._save
|
170
|
+
end
|
171
|
+
|
172
|
+
def call_find! #:nodoc:
|
173
|
+
instance_exec &self.class._find
|
174
|
+
end
|
175
|
+
|
176
|
+
def call_destroy! #:nodoc:
|
177
|
+
instance_exec &self.class._destroy
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|