ohm-contrib 1.0.1 → 1.1.0
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/README.markdown +211 -195
- data/ohm-contrib.gemspec +2 -3
- data/test/plugin.rb +2 -2
- metadata +4 -4
data/README.markdown
CHANGED
@@ -1,200 +1,216 @@
|
|
1
|
-
ohm-contrib
|
2
|
-
===========
|
3
|
-
|
4
|
-
A collection of drop-in modules for Ohm. Read the full documentation at
|
5
|
-
[http://cyx.github.com/ohm-contrib](http://cyx.github.com/ohm-contrib).
|
6
|
-
|
7
|
-
List of modules
|
8
|
-
---------------
|
9
|
-
1. [`Ohm::Boundaries`](http://cyx.github.com/ohm-contrib/doc/Ohm/Boundaries.html)
|
10
|
-
2. [`Ohm::Callbacks`](http://cyx.github.com/ohm-contrib/doc/Ohm/Callbacks.html)
|
11
|
-
3. [`Ohm::Timestamping`](http://cyx.github.com/ohm-contrib/doc/Ohm/Timestamping.html)
|
12
|
-
5. [`Ohm::WebValidations`](http://cyx.github.com/ohm-contrib/doc/Ohm/WebValidations.html)
|
13
|
-
6. [`Ohm::NumberValidations`](http://cyx.github.com/ohm-contrib/doc/Ohm/NumberValidations.html)
|
14
|
-
7. [`Ohm::ExtraValidations`](http://cyx.github.com/ohm-contrib/doc/Ohm/ExtraValidations.html)
|
15
|
-
8. [`Ohm::Typecast`](http://cyx.github.com/ohm-contrib/doc/Ohm/Typecast.html)
|
16
|
-
9. [`Ohm::Locking`](http://cyx.github.com/ohm-contrib/doc/Ohm/Locking.html)
|
17
|
-
|
18
|
-
Example usage
|
19
|
-
-------------
|
20
|
-
|
21
|
-
require 'ohm'
|
22
|
-
require 'ohm/contrib'
|
23
|
-
|
24
|
-
class Post < Ohm::Model
|
25
|
-
include Ohm::Timestamping
|
26
|
-
include Ohm::Boundaries
|
27
|
-
include Ohm::WebValidations
|
28
|
-
include Ohm::NumberValidations
|
29
|
-
|
30
|
-
attribute :amount
|
31
|
-
attribute :url
|
32
|
-
attribute :poster_email
|
33
|
-
attribute :slug
|
34
|
-
|
35
|
-
def validate
|
36
|
-
# from NumberValidations
|
37
|
-
assert_decimal :amount
|
38
|
-
|
39
|
-
# or if you want it to be optional
|
40
|
-
assert_decimal :amount unless amount.to_s.empty?
|
41
|
-
|
42
|
-
# from WebValidations
|
43
|
-
assert_slug :slug
|
44
|
-
assert_url :url
|
45
|
-
assert_email :poster_email
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
Post.first
|
50
|
-
Post.last
|
51
|
-
Post.new.to_hash
|
52
|
-
Post.create.to_hash
|
53
|
-
Post.create.created_at
|
54
|
-
Post.create.updated_at
|
55
|
-
|
56
|
-
# Casting example
|
57
|
-
class Product
|
58
|
-
include Ohm::Typecast
|
59
|
-
|
60
|
-
attribute :price, Decimal
|
61
|
-
attribute :start_of_sale, Time
|
62
|
-
attribute :end_of_sale, Time
|
63
|
-
attribute :priority, Integer
|
64
|
-
attribute :rating, Float
|
65
|
-
end
|
66
|
-
|
67
|
-
Typecasting explained
|
68
|
-
---------------------
|
69
|
-
|
70
|
-
I studied various typecasting behaviors implemented by a few ORMs in Ruby.
|
71
|
-
|
72
|
-
### ActiveRecord
|
73
|
-
|
74
|
-
class Post < ActiveRecord::Base
|
75
|
-
# say we have an integer column in the DB named votes
|
76
|
-
end
|
77
|
-
Post.new(:votes => "FooBar").votes == 0
|
78
|
-
# => true
|
79
|
-
|
80
|
-
### DataMapper
|
81
|
-
class Post
|
82
|
-
include DataMapper::Resource
|
83
|
-
|
84
|
-
property :id, Serial
|
85
|
-
property :votes, Integer
|
86
|
-
end
|
87
|
-
|
88
|
-
post = Post.new(:votes => "FooBar")
|
89
|
-
post.votes == "FooBar"
|
90
|
-
# => true
|
91
|
-
|
92
|
-
post.save
|
93
|
-
post.reload
|
94
|
-
|
95
|
-
# Get ready!!!!
|
96
|
-
post.votes == 0
|
97
|
-
# => true
|
98
|
-
|
99
|
-
### Ohm::Typecast approach.
|
100
|
-
|
101
|
-
#### Mindset:
|
102
|
-
|
103
|
-
1. Explosion everytime is too cumbersome.
|
104
|
-
2. Mutation of data is less than ideal (Also similar to MySQL silently allowing you
|
105
|
-
to store more than 255 chars in a VARCHAR and then truncating that data. Yes I know
|
106
|
-
you can configure it to be noisy but the defaults kill).
|
107
|
-
3. We just want to operate on it like it should!
|
108
|
-
|
109
|
-
#### Short Demo:
|
110
|
-
class Post < Ohm::Model
|
111
|
-
include Ohm::Typecast
|
112
|
-
attribute :votes
|
113
|
-
end
|
114
|
-
|
115
|
-
post = Post.new(:votes => "FooBar")
|
116
|
-
post.votes == "FooBar"
|
117
|
-
# => true
|
118
|
-
|
119
|
-
post.save
|
120
|
-
post = Post[post.id]
|
121
|
-
post.votes == "FooBar"
|
122
|
-
# => true
|
123
|
-
|
124
|
-
# Here comes the cool part...
|
125
|
-
post.votes * 1
|
126
|
-
# => ArgumentError: invalid value for Integer: "FooBar"
|
127
|
-
|
128
|
-
post.votes = 50
|
129
|
-
post.votes * 2 == 100
|
130
|
-
# => true
|
131
|
-
|
132
|
-
post.votes.class == Ohm::Types::Integer
|
133
|
-
# => true
|
134
|
-
post.votes.inspect == "50"
|
135
|
-
# => true
|
136
|
-
|
137
|
-
#### More examples just to show the normal case.
|
138
|
-
|
139
|
-
require 'ohm'
|
140
|
-
require 'ohm/contrib'
|
141
|
-
|
142
|
-
class Post < Ohm::Model
|
143
|
-
include Ohm::Typecast
|
144
|
-
|
145
|
-
attribute :price, Decimal
|
146
|
-
attribute :available_at, Time
|
147
|
-
attribute :stock, Integer
|
148
|
-
attribute :address, Hash
|
149
|
-
attribute :tags, Array
|
150
|
-
end
|
151
|
-
|
152
|
-
post = Post.create(:price => "10.20", :stock => "100",
|
153
|
-
:address => { "city" => "Boston", "country" => "US" },
|
154
|
-
:tags => ["redis", "ohm", "typecast"])
|
1
|
+
# ohm-contrib
|
155
2
|
|
156
|
-
|
157
|
-
# => true
|
3
|
+
A collection of drop-in modules for Ohm.
|
158
4
|
|
159
|
-
|
160
|
-
# => true
|
161
|
-
|
162
|
-
post.stock / 10 == 10
|
163
|
-
# => true
|
5
|
+
If you're upgrading from 0.1.x, see [the changes below](#upgrade)
|
164
6
|
|
165
|
-
|
166
|
-
post.tags.map { |tag| tag.upcase }
|
7
|
+
## Quick Overview
|
167
8
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
post.tags << "contrib"
|
172
|
-
post.address["state"] = "MA"
|
173
|
-
post.save
|
174
|
-
post = Post[post.id]
|
175
|
-
|
176
|
-
post.address["state"] == "MA"
|
177
|
-
# => true
|
178
|
-
post.tags.include?("contrib")
|
179
|
-
# => true
|
180
|
-
|
181
|
-
|
182
|
-
Credits
|
183
|
-
-------
|
184
|
-
Thanks to github user gnrfan for the web validations.
|
185
|
-
|
186
|
-
Note on Patches/Pull Requests
|
187
|
-
-----------------------------
|
188
|
-
* Fork the project.
|
189
|
-
* Make your feature addition or bug fix.
|
190
|
-
* Add tests for it. This is important so I don't break it in a
|
191
|
-
future version unintentionally.
|
192
|
-
* Commit, do not mess with rakefile, version, or history.
|
193
|
-
(if you want to have your own version, that is fine but bump version in a
|
194
|
-
commit by itself I can ignore when I pull)
|
195
|
-
* Send me a pull request. Bonus points for topic branches.
|
196
|
-
|
197
|
-
Copyright
|
198
|
-
---------
|
199
|
-
Copyright (c) 2010 Cyril David. See LICENSE for details.
|
9
|
+
```ruby
|
10
|
+
require 'ohm'
|
11
|
+
require 'ohm/contrib'
|
200
12
|
|
13
|
+
class Post < Ohm::Model
|
14
|
+
include Ohm::Timestamps
|
15
|
+
include Ohm::DataTypes
|
16
|
+
|
17
|
+
attribute :amount, Type::Decimal
|
18
|
+
attribute :url
|
19
|
+
attribute :poster_email
|
20
|
+
attribute :slug
|
21
|
+
end
|
22
|
+
|
23
|
+
post = Post.create
|
24
|
+
|
25
|
+
post.created_at.kind_of?(Time)
|
26
|
+
# => true
|
27
|
+
|
28
|
+
post.update_at.kind_of?(Time)
|
29
|
+
# => true
|
30
|
+
```
|
31
|
+
|
32
|
+
It's important to note that `created_at` and `update_at` both store
|
33
|
+
times as a unix timestamp for efficiency.
|
34
|
+
|
35
|
+
## Ohm::Callbacks
|
36
|
+
|
37
|
+
**Note:** Macro-style callbacks have been removed since version 1.0.x.
|
38
|
+
Please use instance style callbacks.
|
39
|
+
|
40
|
+
### On the topic of callbacks
|
41
|
+
|
42
|
+
Since I initially released ohm-contrib, a lot has changed. Initially, I
|
43
|
+
had a bad habit of putting a lot of stuff in callbacks. Nowadays, I
|
44
|
+
prefer having a workflow object or a service layer which coordinates
|
45
|
+
code not really related to the model, a perfect example of which is
|
46
|
+
photo manipulation.
|
47
|
+
|
48
|
+
It's best to keep your models pure, and have domain specific code
|
49
|
+
in a separate object.
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
class Order < Ohm::Model
|
53
|
+
attribute :status
|
54
|
+
|
55
|
+
def before_create
|
56
|
+
self.status = "pending"
|
57
|
+
end
|
58
|
+
|
59
|
+
def after_save
|
60
|
+
# do something here
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
## Ohm::DataTypes
|
66
|
+
|
67
|
+
If you don't already know, Ohm 1.0 already supports typecasting out of
|
68
|
+
the box by taking a `lambda` parameter. An example best explains:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
class Product < Ohm::Model
|
72
|
+
attribute :price, lambda { |x| x.to_f }
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
What `Ohm::DataTypes` does is define all of these lambdas for you,
|
77
|
+
so we don't have to manually define how to cast an Integer, Float,
|
78
|
+
Decimal, Time, Date, etc.
|
79
|
+
|
80
|
+
### DataTypes: Basic example
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class Product < Ohm::Model
|
84
|
+
include Ohm::DataTypes
|
85
|
+
|
86
|
+
attribute :price, Type::Decimal
|
87
|
+
attribute :start_of_sale, Type::Time
|
88
|
+
attribute :end_of_sale, Type::Time
|
89
|
+
attribute :priority, Type::Integer
|
90
|
+
attribute :rating, Type::Float
|
91
|
+
end
|
92
|
+
|
93
|
+
product = Product.create(price: "127.99")
|
94
|
+
product.price.kind_of?(BigDecimal)
|
95
|
+
# => true
|
96
|
+
|
97
|
+
product = Product.create(start_of_sale: Time.now.rfc2822)
|
98
|
+
product.start_of_sale.kind_of?(Time)
|
99
|
+
# => true
|
100
|
+
|
101
|
+
product = Product.create(end_of_sale: Time.now.rfc2822)
|
102
|
+
product.end_of_sale.kind_of?(Time)
|
103
|
+
# => true
|
104
|
+
|
105
|
+
product = Product.create(priority: "100")
|
106
|
+
product.priority.kind_of?(Integer)
|
107
|
+
# => true
|
108
|
+
|
109
|
+
product = Product.create(rating: "5.5")
|
110
|
+
product.rating.kind_of?(Float)
|
111
|
+
# => true
|
112
|
+
```
|
113
|
+
|
114
|
+
### DataTypes: Advanced example
|
115
|
+
|
116
|
+
**IMPORTANT NOTE**: Mutating a Hash and/or Array doesn't work, so you have
|
117
|
+
to re-assign them accordingly.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class Product < Ohm::Model
|
121
|
+
include Ohm::DataTypes
|
122
|
+
|
123
|
+
attribute :meta, Type::Hash
|
124
|
+
attribute :sizes, Type::Array
|
125
|
+
end
|
126
|
+
|
127
|
+
product = Product.create(meta: { resolution: '1280x768', battery: '8 hours' },
|
128
|
+
sizes: ['XS S M L XL'])
|
129
|
+
|
130
|
+
product.meta.kind_of?(Hash)
|
131
|
+
# => true
|
132
|
+
|
133
|
+
product.meta == { resolution: '1280x768', battery: '8 hours' }
|
134
|
+
# => true
|
135
|
+
|
136
|
+
product.meta.kind_of?(Array)
|
137
|
+
# => true
|
138
|
+
|
139
|
+
product.sizes == ['XS S M L XL']
|
140
|
+
# => true
|
141
|
+
```
|
142
|
+
|
143
|
+
## Ohm::Slug
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class Post < Ohm::Model
|
147
|
+
include Ohm::Slug
|
148
|
+
|
149
|
+
attribute :title
|
150
|
+
|
151
|
+
def to_s
|
152
|
+
title
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
post = Post.create(title: "Using Ohm contrib 1.0")
|
157
|
+
post.to_param == "1-using-ohm-contrib-1.0"
|
158
|
+
# => true
|
159
|
+
```
|
160
|
+
|
161
|
+
By default, `Ohm::Slug` tries to load iconv in order to transliterate
|
162
|
+
non-ascii characters.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
post = Post.create(:title => "Décor")
|
166
|
+
post.to_param == "2-decor"
|
167
|
+
```
|
168
|
+
|
169
|
+
## Ohm::Versioned
|
170
|
+
|
171
|
+
For cases where you expect people to be editing long pieces of
|
172
|
+
content concurrently (the most obvious example would be a CMS with multiple
|
173
|
+
moderators), then you need to put some kind of versioning in place.
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
class Article < Ohm::Model
|
177
|
+
include Ohm::Versioned
|
178
|
+
|
179
|
+
attribute :title
|
180
|
+
attribute :content
|
181
|
+
end
|
182
|
+
|
183
|
+
a1 = Article.create(:title => "Foo Bar", :content => "Lorem ipsum")
|
184
|
+
a2 = Article[a1.id]
|
185
|
+
|
186
|
+
# At this point, a2 will be stale.
|
187
|
+
a1.update(title: "Foo Bar Baz")
|
188
|
+
|
189
|
+
begin
|
190
|
+
a2.update(:title => "Bar Baz")
|
191
|
+
rescue Ohm::VersionConflict => ex
|
192
|
+
ex.attributes == { :title => "Bar Baz", :_version => "1", :content => "Lorem ipsum" }
|
193
|
+
# => true
|
194
|
+
end
|
195
|
+
```
|
196
|
+
|
197
|
+
<a name="upgrade" id="upgrade"></a>
|
198
|
+
|
199
|
+
## Important Upgrade notes from 0.1.x
|
200
|
+
|
201
|
+
The following lists the major changes:
|
202
|
+
|
203
|
+
1. `Ohm::Typecast` has been removed in favor of `Ohm::DataTypes`.
|
204
|
+
2. `Ohm::Timestamping` has been renamed to `Ohm::Timestamps`.
|
205
|
+
3. `Ohm::Timestamps` now store times as a UNIX Timestamp.
|
206
|
+
4. `All Ohm validation related plugins have been removed.
|
207
|
+
See [scrivener][scrivener] instead.
|
208
|
+
5. `Ohm::Boundaries` has been removed.
|
209
|
+
6. Ohm::Contrib no longer uses `autoload`. You can either
|
210
|
+
`require 'ohm/contrib', which requires everything, or you
|
211
|
+
can `require ohm/datatypes` for example if you want to cherry
|
212
|
+
pick your requires.
|
213
|
+
7. `Ohm::Callbacks` no longer provides macro style callbacks, i.e.
|
214
|
+
`after :create, :do_something`. Use instance callbacks instead.
|
215
|
+
|
216
|
+
[scrivener]: http://github.com/soveran/scrivener
|
data/ohm-contrib.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "ohm-contrib"
|
3
|
-
s.version = "1.0
|
3
|
+
s.version = "1.1.0"
|
4
4
|
s.summary = %{A collection of decoupled drop-in modules for Ohm.}
|
5
5
|
s.description = %{Includes a couple of core functions such as callbacks, timestamping, typecasting and lots of generic validation routines.}
|
6
6
|
s.author = "Cyril David"
|
@@ -21,8 +21,7 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
s.rubyforge_project = "ohm-contrib"
|
23
23
|
|
24
|
-
s.
|
25
|
-
s.add_dependency "ohm", "~> 1.0"
|
24
|
+
s.add_dependency "ohm", "~> 1.1"
|
26
25
|
|
27
26
|
s.add_development_dependency "cutest"
|
28
27
|
s.add_development_dependency "redis"
|
data/test/plugin.rb
CHANGED
@@ -58,8 +58,8 @@ scope do
|
|
58
58
|
end
|
59
59
|
|
60
60
|
test "slugging" do
|
61
|
-
post = Post.create(:title => "Foo Bar Baz")
|
62
|
-
assert_equal "1-foo-bar-baz", post.to_param
|
61
|
+
post = Post.create(:title => "Foo Bar Baz 1.0")
|
62
|
+
assert_equal "1-foo-bar-baz-1-0", post.to_param
|
63
63
|
|
64
64
|
post = Post.create(:title => "Décor")
|
65
65
|
assert_equal "2-decor", post.to_param
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ohm-contrib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ohm
|
@@ -18,7 +18,7 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '1.
|
21
|
+
version: '1.1'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '1.
|
29
|
+
version: '1.1'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: cutest
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|