ohm-contrib 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|