plain_record 0.2 → 0.3

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/.travis.yml CHANGED
@@ -3,4 +3,3 @@ rvm:
3
3
  - 1.8.7
4
4
  - ruby-head
5
5
  - jruby-18mode
6
- - rbx-18mode
data/ChangeLog CHANGED
@@ -1,3 +1,14 @@
1
+ == 0.3 (Chlorine)
2
+ * Use modules to use super in filters.
3
+ * Add filter to i18n support.
4
+ * Add filters to get updated and created time from git.
5
+ * Add filters to set field type.
6
+ * Add filter to set default field value.
7
+ * Add alternate hash syntax to define filters.
8
+ * Set deault data root in Rails project.
9
+ * Fix association cache.
10
+ * Allow to set Pathname in data root.
11
+
1
12
  == 0.2 (Smallpox)
2
13
  * Add associations.
3
14
  * Add special syntax for virtual properties.
data/Gemfile CHANGED
@@ -4,3 +4,5 @@ gem 'rake'
4
4
  gem 'yard'
5
5
  gem 'rspec'
6
6
  gem 'redcarpet'
7
+ gem 'r18n-core', :require => nil, :github => 'ai/r18n'
8
+ gem 'i18n', :require => nil
data/Gemfile.lock CHANGED
@@ -1,14 +1,21 @@
1
+ GIT
2
+ remote: git://github.com/ai/r18n.git
3
+ revision: ca6933926eb955344eeb982de35f492c76a38b1a
4
+ specs:
5
+ r18n-core (0.4.14)
6
+
1
7
  GEM
2
8
  remote: http://rubygems.org/
3
9
  specs:
4
10
  diff-lcs (1.1.3)
11
+ i18n (0.6.0)
5
12
  rake (0.9.2.2)
6
13
  redcarpet (2.1.1)
7
14
  rspec (2.10.0)
8
15
  rspec-core (~> 2.10.0)
9
16
  rspec-expectations (~> 2.10.0)
10
17
  rspec-mocks (~> 2.10.0)
11
- rspec-core (2.10.0)
18
+ rspec-core (2.10.1)
12
19
  rspec-expectations (2.10.0)
13
20
  diff-lcs (~> 1.1.3)
14
21
  rspec-mocks (2.10.1)
@@ -18,6 +25,8 @@ PLATFORMS
18
25
  ruby
19
26
 
20
27
  DEPENDENCIES
28
+ i18n
29
+ r18n-core!
21
30
  rake
22
31
  redcarpet
23
32
  rspec
data/README.md CHANGED
@@ -6,6 +6,10 @@ text files. It’s ideal for static generated sites, like blog or homepage.
6
6
  If you want to write another static website generator, you don’t need to write
7
7
  another file parser – you can use Plain Record.
8
8
 
9
+ Sponsored by [Evil Martians].
10
+
11
+ [Evil Martians]: http://evilmartians.com/
12
+
9
13
  ## How To
10
14
 
11
15
  For example we will create simple blog storage with posts and comments.
@@ -23,7 +27,7 @@ For example we will create simple blog storage with posts and comments.
23
27
  ```
24
28
 
25
29
  3. Create Post class, include `Plain::Resource` module, set glob pattern
26
- to posts files and define properties:
30
+ to posts files and define fields:
27
31
 
28
32
  ```ruby
29
33
  class Post
@@ -31,16 +35,17 @@ For example we will create simple blog storage with posts and comments.
31
35
 
32
36
  entry_in '*/post.md'
33
37
 
34
- virtual :name, in_filepath(1)
35
- virtual :comments, many(Comment)
36
- property :title
37
- property :tags
38
- text :summary
39
- text :content
38
+ virtual :name, in_filepath(1)
39
+ virtual :comments, many(Comment)
40
+ field :title default("Untitled")
41
+ field :tags default([])
42
+ field :created type(Time)
43
+ text :summary
44
+ text :content
40
45
  end
41
46
  ```
42
47
 
43
- 4. Create new post file `data/first/post.md`. Properties will be saved as
48
+ 4. Create new post file `data/first/post.md`. Fields will be saved as
44
49
  YAML and text will be placed as plain text, which is separated by 3 dashes:
45
50
 
46
51
  ```
@@ -61,10 +66,10 @@ For example we will create simple blog storage with posts and comments.
61
66
 
62
67
  list_in '*/comments.yml'
63
68
 
64
- virtual :post_name, in_filepath(1)
65
- virtual :post, one(Post)
66
- property :author
67
- property :comment
69
+ virtual :post_name, in_filepath(1)
70
+ virtual :post, one(Post)
71
+ field :author
72
+ field :comment
68
73
  end
69
74
  ```
70
75
  You can’t use text fields in list files.
@@ -90,7 +95,7 @@ For example we will create simple blog storage with posts and comments.
90
95
  ```
91
96
 
92
97
  9. To get one entry use `first` method, which also can take matchers. You can
93
- access for properties and text by methods with same name:
98
+ access for fields and text by methods with same name:
94
99
 
95
100
  ```ruby
96
101
  post = Post.first(title: /first/)
@@ -1,7 +1,8 @@
1
1
  =begin
2
2
  Storage for one-to-many virtual associations.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -19,39 +20,39 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
 
20
21
  module PlainRecord
21
22
  # Storage for one-to-many virtual associations. When new object will pushed,
22
- # proxy change it mapped properties.
23
+ # proxy change it mapped fields.
23
24
  class AssociationProxy < Array
24
25
  # Model with association.
25
26
  attr_accessor :owner
26
27
 
27
- # Associations property name.
28
- attr_accessor :property
28
+ # Associations field name.
29
+ attr_accessor :field
29
30
 
30
- # Create proxy for one-to-many virtual associations +property+ in +owner+
31
+ # Create proxy for one-to-many virtual associations +field+ in +owner+
31
32
  # and put +array+ into it.
32
- def self.link(array, owner, property)
33
- proxy = new(array, owner, property)
33
+ def self.link(array, owner, field)
34
+ proxy = new(array, owner, field)
34
35
  proxy.each { |i| proxy.link(i) }
35
36
  proxy
36
37
  end
37
38
 
38
- # Create proxy for one-to-many virtual associations +property+ in +owner+
39
+ # Create proxy for one-to-many virtual associations +field+ in +owner+
39
40
  # with +array+ in value.
40
- def initialize(array, owner, property)
41
+ def initialize(array, owner, field)
41
42
  @owner = owner
42
- @property = property
43
+ @field = field
43
44
  super(array)
44
45
  end
45
46
 
46
- # Push new item in association and change it property by association map.
47
+ # Push new item in association and change it field by association map.
47
48
  def <<(obj)
48
49
  link(obj)
49
50
  super(obj)
50
51
  end
51
52
 
52
- # Change properties in +obj+ by association map.
53
+ # Change fields in +obj+ by association map.
53
54
  def link(obj)
54
- @owner.class.association_maps[@property].each do |from, to|
55
+ @owner.class.association_maps[@field].each do |from, to|
55
56
  obj.send(from.to_s + '=', @owner.send(to))
56
57
  end
57
58
  end
@@ -1,7 +1,8 @@
1
1
  =begin
2
2
  Extention to store or have link to another model.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -21,30 +22,30 @@ module PlainRecord
21
22
  # Extention for model to store or have link to another model. There is two
22
23
  # type of association.
23
24
  #
24
- # == Virtual property
25
- # In +virtual+ method this definer create only _link_ to another model. When
26
- # you try to use this virtual property, model will find association object by
25
+ # == Virtual field
26
+ # In +virtual+ method this filter create only _link_ to another model. When
27
+ # you try to use this virtual field, model will find association object by
27
28
  # rules in +map+.
28
29
  #
29
- # Rules in +map+ is only Hash with model properties in key and association
30
- # properties in value. For example, if model contain +name+ property and
30
+ # Rules in +map+ is only Hash with model fields in key and association
31
+ # fields in value. For example, if model contain +name+ field and
31
32
  # association must have +post_name+ with same value, +map+ will be
32
33
  # <tt>{ :name => :post_name }</tt>.
33
34
  #
34
- # If you didn’t set +map+ definer will try to find it automatically:
35
- # it will find in model and association class all property pairs, what have
36
- # name like +property+ → <tt>model</tt>_<tt>property</tt>. For example,
37
- # if model +Post+ have property +name+ and +Comment+ have +post_name+, you
38
- # may not set +map+ – definer will find it automatically.
35
+ # If you didn’t set +map+ filter will try to find it automatically:
36
+ # it will find in model and association class all field pairs, what have
37
+ # name like +field+ → <tt>model</tt>_<tt>field</tt>. For example,
38
+ # if model +Post+ have field +name+ and +Comment+ have +post_name+, you
39
+ # may not set +map+ – filter will find it automatically.
39
40
  #
40
41
  # class Review
41
42
  # include PlainRecord::Resource
42
43
  #
43
44
  # entry_in 'reviews/*.md'
44
45
  #
45
- # virtual :author, one(Author)
46
- # property :author_login
47
- # text :review
46
+ # virtual :author, one(Author)
47
+ # field :author_login
48
+ # text :review
48
49
  # end
49
50
  #
50
51
  # class Author
@@ -52,26 +53,26 @@ module PlainRecord
52
53
  #
53
54
  # list_in 'authors.yml'
54
55
  #
55
- # virtual :reviews, many(Review)
56
- # property :login
57
- # property :name
56
+ # virtual :reviews, many(Review)
57
+ # field :login
58
+ # field :name
58
59
  # end
59
60
  #
60
- # == Real property
61
- # If you will use this definer in +property+ method, association object data
61
+ # == Real field
62
+ # If you will use this filter in +field+ method, association object data
62
63
  # will store in you model file. For example model:
63
64
  #
64
65
  # class Movie
65
66
  # include PlainRecord::Resource
66
67
  #
67
- # property :title
68
- # property :genre
69
- # property :release_year
68
+ # field :title
69
+ # field :genre
70
+ # field :release_year
70
71
  # end
71
72
  #
72
73
  # class Tag
73
74
  # include PlainRecord::Resource
74
- # property :name
75
+ # field :name
75
76
  # end
76
77
  #
77
78
  # class Review
@@ -79,10 +80,10 @@ module PlainRecord
79
80
  #
80
81
  # entry_in 'reviews/*.md'
81
82
  #
82
- # property :author
83
- # property :movie, one(Movie)
84
- # property :tags, many(Tag)
85
- # text :review
83
+ # field :author
84
+ # field :movie, one(Movie)
85
+ # field :tags, many(Tag)
86
+ # text :review
86
87
  # end
87
88
  #
88
89
  # will be store as:
@@ -101,54 +102,47 @@ module PlainRecord
101
102
  # Hash with map for virtual associations.
102
103
  attr_accessor :association_maps
103
104
 
104
- # Hash with cached values for virtual associations.
105
- attr_accessor :association_cache
106
-
107
105
  private
108
106
 
109
- # Return definer for one-to-one association with +klass+. Have different
110
- # logic in +property+ and +virtual+ methods.
107
+ # Return filter for one-to-one association with +klass+. Have different
108
+ # logic in +field+ and +virtual+ methods.
111
109
  def one(klass, map = { })
112
- proc do |property, caller|
113
- if :property == caller
114
- Associations.define_real_one(self, property, klass)
115
- :accessor
116
- elsif :virtual == caller
117
- map = Associations.map(self, klass, "#{property}_") if map.empty?
118
- Associations.define_link_one(self, klass, property, map)
119
- nil
110
+ proc do |model, field, type|
111
+ if :field == type
112
+ Associations.define_real_one(model, field, klass)
113
+ elsif :virtual == type
114
+ map = Associations.map(model, klass, "#{field}_") if map.empty?
115
+ Associations.define_link_one(model, klass, field, map)
120
116
  else
121
- raise ArgumentError, "You couldn't create association property" +
122
- " #{property} by text creator"
117
+ raise ArgumentError, "You couldn't create association field" +
118
+ " #{field} by text creator"
123
119
  end
124
120
  end
125
121
  end
126
122
 
127
- # Return definer for one-to-many or many-to-many association with +klass+.
128
- # Have different login in +property+ and +virtual+ methods.
123
+ # Return filter for one-to-many or many-to-many association with +klass+.
124
+ # Have different login in +field+ and +virtual+ methods.
129
125
  def many(klass, prefix = nil, map = { })
130
- proc do |property, caller|
131
- if :property == caller
132
- Associations.define_real_many(self, property, klass)
133
- :accessor
134
- elsif :virtual == caller
126
+ proc do |model, field, type|
127
+ if :field == type
128
+ Associations.define_real_many(model, field, klass)
129
+ elsif :virtual == type
135
130
  unless prefix
136
131
  prefix = self.to_s.gsub!(/[A-Z]/, '_\0')[1..-1].downcase + '_'
137
132
  end
138
- map = Associations.map(klass, self, prefix) if map.empty?
139
- Associations.define_link_many(self, klass, property, map)
140
- nil
133
+ map = Associations.map(klass, model, prefix) if map.empty?
134
+ Associations.define_link_many(model, klass, field, map)
141
135
  else
142
- raise ArgumentError, "You couldn't create association property" +
143
- " #{property} by text creator"
136
+ raise ArgumentError, "You couldn't create association field" +
137
+ " #{field} by text creator"
144
138
  end
145
139
  end
146
140
  end
147
141
 
148
142
  class << self
149
- # Define, that +property+ in +klass+ contain in file data from +model+.
150
- def define_real_one(klass, property, model)
151
- name = property.to_s
143
+ # Define, that +field+ in +klass+ contain in file data from +model+.
144
+ def define_real_one(klass, field, model)
145
+ name = field.to_s
152
146
  klass.after :load do |result, entry|
153
147
  entry.data[name] = model.new(entry.file, entry.data[name])
154
148
  result
@@ -164,10 +158,10 @@ module PlainRecord
164
158
  end
165
159
  end
166
160
 
167
- # Define, that +property+ in +klass+ contain in file array of data from
161
+ # Define, that +field+ in +klass+ contain in file array of data from
168
162
  # +model+ objects.
169
- def define_real_many(klass, property, model)
170
- name = property.to_s
163
+ def define_real_many(klass, field, model)
164
+ name = field.to_s
171
165
  klass.after :load do |result, entry|
172
166
  if entry.data[name].is_a? Enumerable
173
167
  entry.data[name].map! { |i| model.new(entry.file, i) }
@@ -195,17 +189,17 @@ module PlainRecord
195
189
  end
196
190
  end
197
191
 
198
- # Find properties pairs in +from+ and +to+ models, witch is like
192
+ # Find fields pairs in +from+ and +to+ models, witch is like
199
193
  # <tt>prefix</tt>_<tt>from</tt> → +to+.
200
194
  #
201
- # For example, if Comment contain +post_name+ property and Post contain
195
+ # For example, if Comment contain +post_name+ field and Post contain
202
196
  # +name+:
203
197
  #
204
198
  # Associations.map(Comment, Post, :post) #=> { :post_name => :name }
205
199
  def map(from, to, prefix)
206
- from_fields = (from.properties + from.virtuals).map { |i| i.to_s }
200
+ from_fields = (from.fields + from.virtuals).map { |i| i.to_s }
207
201
  mapped = { }
208
- (to.properties + to.virtuals).each do |to_field|
202
+ (to.fields + to.virtuals).each do |to_field|
209
203
  from_field = prefix + to_field.to_s
210
204
  if from_fields.include? from_field
211
205
  mapped[from_field.to_sym] = to_field
@@ -214,54 +208,60 @@ module PlainRecord
214
208
  mapped
215
209
  end
216
210
 
217
- # Define that virtual property +name+ in +klass+ contain link to +model+
211
+ def init_association_cache(klass)
212
+ klass.after :load do |result, entry|
213
+ entry.instance_exec { @association_cache = { } }
214
+ end
215
+ end
216
+
217
+ # Define that virtual field +name+ in +klass+ contain link to +model+
218
218
  # witch is finded by +map+.
219
219
  def define_link_one(klass, model, name, map)
220
- klass.association_cache ||= { }
221
- klass.association_maps ||= { }
220
+ klass.association_maps ||= { }
222
221
  klass.association_maps[name] = map
222
+ init_association_cache(klass)
223
223
 
224
- klass.class_eval <<-EOS, __FILE__, __LINE__
224
+ klass.add_accessors <<-EOS, __FILE__, __LINE__
225
225
  def #{name}
226
- unless self.class.association_cache[:#{name}]
226
+ unless @association_cache.has_key? :#{name}
227
227
  search = Hash[
228
228
  self.class.association_maps[:#{name}].map do |from, to|
229
229
  [to, send(from)]
230
230
  end]
231
- self.class.association_cache[:#{name}] = #{model}.first(search)
231
+ @association_cache[:#{name}] = #{model}.first(search)
232
232
  end
233
- self.class.association_cache[:#{name}]
233
+ @association_cache[:#{name}]
234
234
  end
235
235
  def #{name}=(value)
236
236
  self.class.association_maps[:#{name}].each do |from, to|
237
237
  value.send(to.to_s + '=', send(from))
238
238
  end
239
- self.class.association_cache[:#{name}] = value
239
+ @association_cache[:#{name}] = value
240
240
  end
241
241
  EOS
242
242
  end
243
243
 
244
- # Define that virtual property +name+ in +klass+ contain links to +model+
244
+ # Define that virtual field +name+ in +klass+ contain links to +model+
245
245
  # witch are finded by +map+.
246
246
  def define_link_many(klass, model, name, map)
247
- klass.association_cache ||= { }
248
- klass.association_maps ||= { }
247
+ klass.association_maps ||= { }
249
248
  klass.association_maps[name] = map
249
+ init_association_cache(klass)
250
250
 
251
- klass.class_eval <<-EOS, __FILE__, __LINE__
251
+ klass.add_accessors <<-EOS, __FILE__, __LINE__
252
252
  def #{name}
253
- unless self.class.association_cache[:#{name}]
253
+ unless @association_cache.has_key? :#{name}
254
254
  search = Hash[
255
255
  self.class.association_maps[:#{name}].map do |from, to|
256
256
  [from, send(to)]
257
257
  end]
258
- self.class.association_cache[:#{name}] = AssociationProxy.new(
258
+ @association_cache[:#{name}] = AssociationProxy.new(
259
259
  #{model}.all(search), self, :#{name})
260
260
  end
261
- self.class.association_cache[:#{name}]
261
+ @association_cache[:#{name}]
262
262
  end
263
263
  def #{name}=(values)
264
- self.class.association_cache[:#{name}] = AssociationProxy.link(
264
+ @association_cache[:#{name}] = AssociationProxy.link(
265
265
  values, self, :#{name})
266
266
  end
267
267
  EOS
@@ -1,7 +1,8 @@
1
1
  =begin
2
2
  Module to add before/after hooks.
3
3
 
4
- Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>
4
+ Copyright (C) 2009 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
5
6
 
6
7
  This program is free software: you can redistribute it and/or modify
7
8
  it under the terms of the GNU Lesser General Public License as published by
@@ -21,7 +22,7 @@ module PlainRecord
21
22
  # Callbacks are hooks that allow you to define methods to run before and
22
23
  # after some method, to change it logic.
23
24
  module Callbacks
24
- # Hash of class callbacks with property.
25
+ # Hash of class callbacks with field.
25
26
  attr_accessor :callbacks
26
27
 
27
28
  # Set block as callback before +events+. Callback with less +priority+ will
@@ -0,0 +1,56 @@
1
+ =begin
2
+ Extention to set default value for field.
3
+
4
+ Copyright (C) 2012 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ =end
20
+
21
+ module PlainRecord
22
+ # Extention to set default value for field.
23
+ #
24
+ # class Post
25
+ # include PlainRecord::Resource
26
+ #
27
+ # entry_in '*/*/post.md'
28
+ #
29
+ # virtual :category, default('uncategorized')
30
+ # …
31
+ # end
32
+ #
33
+ # post = Post.new
34
+ # post.category #=> "uncategorized"
35
+ # post.category = "a"
36
+ # post.category #=> "a"
37
+ module Default
38
+ attr_accessor :default_values
39
+
40
+ private
41
+
42
+ # Filter to set default field value.
43
+ def default(value)
44
+ proc do |model, field, type|
45
+ model.default_values ||= { }
46
+ model.default_values[field] = value
47
+
48
+ model.add_accessors <<-EOS, __FILE__, __LINE__
49
+ def #{field}
50
+ super || self.class.default_values[:#{field}]
51
+ end
52
+ EOS
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,97 @@
1
+ =begin
2
+ Extention to get time from git commits of model file.
3
+
4
+ Copyright (C) 2012 Andrey “A.I.” Sitnik <andrey@sitnik.ru>,
5
+ sponsored by Evil Martians.
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ =end
20
+
21
+ module PlainRecord::Extra
22
+ # Extention to get time from git commits of model file.
23
+ #
24
+ # It make sense only with `entry_in` models. You can get created or modified
25
+ # time from first or last git commit time.
26
+ #
27
+ # It is additional extention, so you need to include `PlainRecord::Extra::Git`
28
+ # module to your model.
29
+ #
30
+ # class Post
31
+ # include PlainRecord::Resource
32
+ # include PlainRecord::Extra::Git
33
+ #
34
+ # virtual :created_at, git_created_time
35
+ # virtual :updated_at, git_modify_time
36
+ # end
37
+ module Git
38
+ class << self
39
+ def included(base)
40
+ base.send :extend, Model
41
+ end
42
+ end
43
+
44
+ # Return time of first commit of model file (created time).
45
+ #
46
+ # If file isn’t commited yet, it will return `Time.now`.
47
+ def first_git_commit
48
+ return Time.now unless file
49
+ times = `git log --reverse --date=iso --pretty=format:%cD #{file}`
50
+ time = times.split("\n").first
51
+ time ? Time.parse(time) : Time.now
52
+ end
53
+
54
+ # Return time of last commit of model file (modified time).
55
+ #
56
+ # If file isn’t commited yet, it will return `Time.now`.
57
+ def last_git_commit
58
+ return Time.now if file.nil? or git_uncommitted?
59
+ time = `git log -1 --date=iso --pretty=format:%cD #{file}`
60
+ time ? Time.parse(time) : Time.now
61
+ end
62
+
63
+ # If file have changes, that is not commited yet.
64
+ def git_uncommitted?
65
+ not `git status -s #{file}`.empty?
66
+ end
67
+
68
+ module Model
69
+
70
+ private
71
+
72
+ # Filter to set default value to time of last file git commit.
73
+ # If file is not commited or has changes, filter will return `Time.now`.
74
+ def git_modified_time
75
+ proc do |model, name, type|
76
+ model.add_accessors <<-EOS, __FILE__, __LINE__
77
+ def #{name}
78
+ super || last_git_commit
79
+ end
80
+ EOS
81
+ end
82
+ end
83
+
84
+ # Filter to set default value to time of first file git commit.
85
+ # If file is not commited or has changes, filter will return `Time.now`.
86
+ def git_created_time
87
+ proc do |model, name, type|
88
+ model.add_accessors <<-EOS, __FILE__, __LINE__
89
+ def #{name}
90
+ super || first_git_commit
91
+ end
92
+ EOS
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end