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.
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