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.
Files changed (44) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +72 -0
  3. data/Rakefile +35 -0
  4. data/lib/modest_model.rb +12 -0
  5. data/lib/modest_model/base.rb +69 -0
  6. data/lib/modest_model/callbacks.rb +42 -0
  7. data/lib/modest_model/combined_attr.rb +9 -0
  8. data/lib/modest_model/resource.rb +9 -0
  9. data/lib/modest_model/tenacity.rb +181 -0
  10. data/lib/modest_model/validators.rb +9 -0
  11. data/test/compliance_test.rb +26 -0
  12. data/test/dummy/Rakefile +7 -0
  13. data/test/dummy/app/assets/javascripts/application.js +9 -0
  14. data/test/dummy/app/assets/stylesheets/application.css +7 -0
  15. data/test/dummy/app/controllers/application_controller.rb +3 -0
  16. data/test/dummy/app/helpers/application_helper.rb +2 -0
  17. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  18. data/test/dummy/config.ru +4 -0
  19. data/test/dummy/config/application.rb +42 -0
  20. data/test/dummy/config/boot.rb +10 -0
  21. data/test/dummy/config/database.yml +10 -0
  22. data/test/dummy/config/environment.rb +5 -0
  23. data/test/dummy/config/environments/development.rb +27 -0
  24. data/test/dummy/config/environments/production.rb +51 -0
  25. data/test/dummy/config/environments/test.rb +39 -0
  26. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  27. data/test/dummy/config/initializers/inflections.rb +10 -0
  28. data/test/dummy/config/initializers/mime_types.rb +5 -0
  29. data/test/dummy/config/initializers/secret_token.rb +7 -0
  30. data/test/dummy/config/initializers/session_store.rb +8 -0
  31. data/test/dummy/config/initializers/wrap_parameters.rb +12 -0
  32. data/test/dummy/config/locales/en.yml +5 -0
  33. data/test/dummy/config/routes.rb +58 -0
  34. data/test/dummy/public/404.html +26 -0
  35. data/test/dummy/public/422.html +26 -0
  36. data/test/dummy/public/500.html +26 -0
  37. data/test/dummy/public/favicon.ico +0 -0
  38. data/test/dummy/script/rails +6 -0
  39. data/test/fixtures/sample_model.rb +4 -0
  40. data/test/fixtures/sample_resource.rb +45 -0
  41. data/test/modest_model_test.rb +49 -0
  42. data/test/sample_resource_test.rb +153 -0
  43. data/test/test_helper.rb +10 -0
  44. metadata +160 -0
@@ -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.
@@ -0,0 +1,72 @@
1
+ # ModestModel [![Build Status](https://secure.travis-ci.org/6twenty/modest_model.png)](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.
@@ -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
@@ -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,9 @@
1
+ module ModestModel
2
+ module CombinedAttr
3
+ def attribute *attrs
4
+ validations = attrs.extract_options!
5
+ attributes *attrs
6
+ validates *(attrs +[validations]) if validations.any?
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ModestModel
2
+ autoload :Tenacity, File.expand_path('../tenacity', __FILE__)
3
+ autoload :Callbacks, File.expand_path('../callbacks', __FILE__)
4
+
5
+ class Resource < Base
6
+ include ModestModel::Tenacity
7
+ include ModestModel::Callbacks
8
+ end
9
+ 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