plain_record 0.2 → 0.3

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