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.
Files changed (4) hide show
  1. data/README.markdown +211 -195
  2. data/ohm-contrib.gemspec +2 -3
  3. data/test/plugin.rb +2 -2
  4. metadata +4 -4
@@ -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
- post.price.to_s == "10.20"
157
- # => true
3
+ A collection of drop-in modules for Ohm.
158
4
 
159
- post.price * 2 == 20.40
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
- post.address["city"] == "Boston"
166
- post.tags.map { |tag| tag.upcase }
7
+ ## Quick Overview
167
8
 
168
- # of course mutation works for both cases
169
- post.price += 5
170
- post.stock -= 1
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
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ohm-contrib"
3
- s.version = "1.0.1"
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.has_rdoc = false
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"
@@ -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.1
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-05-14 00:00:00.000000000 Z
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.0'
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.0'
29
+ version: '1.1'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: cutest
32
32
  requirement: !ruby/object:Gem::Requirement