csv_record 1.9.0 → 2.0.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.
- data/README.md +13 -221
- data/csv_record.gemspec +1 -0
- data/lib/csv_record/associations.rb +27 -29
- data/lib/csv_record/callback.rb +12 -0
- data/lib/csv_record/callbacks.rb +79 -77
- data/lib/csv_record/connector.rb +52 -46
- data/lib/csv_record/csv_queries/condition.rb +18 -0
- data/lib/csv_record/csv_queries/query.rb +73 -0
- data/lib/csv_record/csv_validations/custom_validation.rb +20 -0
- data/lib/csv_record/csv_validations/presence_validation.rb +13 -0
- data/lib/csv_record/csv_validations/uniqueness_validation.rb +16 -0
- data/lib/csv_record/csv_validations/validations.rb +87 -0
- data/lib/csv_record/document.rb +18 -21
- data/lib/csv_record/reader.rb +76 -97
- data/lib/csv_record/timestamps.rb +29 -29
- data/lib/csv_record/version.rb +1 -1
- data/lib/csv_record/writer.rb +78 -80
- data/test/csv_record/callbacks_test.rb +1 -1
- data/test/csv_record/condition_test.rb +35 -0
- data/test/csv_record/connector_test.rb +11 -11
- data/test/csv_record/query_test.rb +58 -0
- data/test/csv_record/reader_test.rb +64 -75
- data/test/csv_record/timestamps_test.rb +9 -16
- data/test/csv_record/validation_test.rb +8 -29
- data/test/csv_record/writer_test.rb +51 -65
- data/test/models/jedi.rb +1 -2
- data/test/test_helper.rb +1 -11
- metadata +30 -10
- data/lib/csv_record/validations.rb +0 -83
- data/test/csv_record/csv_record_test.rb +0 -8
- data/test/csv_record/document_test.rb +0 -6
- data/test/models/car.rb +0 -11
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative 'condition'
|
2
|
+
|
3
|
+
class CsvRecord::Query
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :klass, :conditions
|
7
|
+
|
8
|
+
def initialize(klass, conditions)
|
9
|
+
@klass = klass
|
10
|
+
|
11
|
+
@conditions = conditions.map do |condition|
|
12
|
+
CsvRecord::Condition.new *condition
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def __where__(params)
|
17
|
+
new_conditions = (CsvRecord::Condition.create_from_hashes params)
|
18
|
+
@conditions = (@conditions << new_conditions).flatten # figure out a way to solve this later
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def __trigger__
|
23
|
+
klass.open_database_file do |csv|
|
24
|
+
rows = search_for csv, self.conditions
|
25
|
+
rows.map { |row| klass.build row.to_hash }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
to_a.inspect
|
31
|
+
end
|
32
|
+
|
33
|
+
def __to_a__
|
34
|
+
trigger
|
35
|
+
end
|
36
|
+
|
37
|
+
def last
|
38
|
+
to_a.last
|
39
|
+
end
|
40
|
+
|
41
|
+
def each(&block)
|
42
|
+
to_a.each(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def empty?
|
46
|
+
to_a.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
alias :where :__where__
|
50
|
+
alias :trigger :__trigger__
|
51
|
+
alias :to_a :__to_a__
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def search_for(csv, params)
|
56
|
+
csv.entries.select do |attributes|
|
57
|
+
eval conditions_as_string
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def conditions_as_string
|
62
|
+
conditions_string = ''
|
63
|
+
self.conditions.each_with_index do |condition, index|
|
64
|
+
conditions_string << condition.to_code
|
65
|
+
conditions_string << ' and ' if first_and_not_last? index
|
66
|
+
end
|
67
|
+
conditions_string
|
68
|
+
end
|
69
|
+
|
70
|
+
def first_and_not_last?(index)
|
71
|
+
(self.conditions.size > 1) and (index != self.conditions.size - 1)
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CsvRecord::CustomValidation
|
2
|
+
attr_accessor :message
|
3
|
+
|
4
|
+
def initialize(message)
|
5
|
+
self.message = message
|
6
|
+
end
|
7
|
+
|
8
|
+
def run_on(obj)
|
9
|
+
if self.message.is_a? Proc
|
10
|
+
obj.instance_eval &(self.message)
|
11
|
+
else
|
12
|
+
obj.send self.message
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def get_correct_block_type
|
18
|
+
self.class.send "#{self.type}_block"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CsvRecord::UniquenessValidation
|
2
|
+
attr_accessor :field
|
3
|
+
|
4
|
+
def initialize(field)
|
5
|
+
self.field = field
|
6
|
+
end
|
7
|
+
|
8
|
+
def run_on(obj)
|
9
|
+
condition = {}
|
10
|
+
condition[self.field] = obj.public_send field
|
11
|
+
records = obj.class.__where__ condition
|
12
|
+
if records.any? { |record| record != obj }
|
13
|
+
obj.errors.add field
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'csv_record/csv_validations/custom_validation'
|
2
|
+
require 'csv_record/csv_validations/uniqueness_validation'
|
3
|
+
require 'csv_record/csv_validations/presence_validation'
|
4
|
+
|
5
|
+
module CsvRecord::Validations
|
6
|
+
module ClassMethods
|
7
|
+
[:presence, :uniqueness].each do |kind|
|
8
|
+
validator_collection = "fields_to_validate_#{kind}"
|
9
|
+
class_macro = "validates_#{kind}_of"
|
10
|
+
validation_class_name = eval "CsvRecord::#{kind.to_s.capitalize}Validation"
|
11
|
+
|
12
|
+
define_method validator_collection do
|
13
|
+
eval "@#{validator_collection} ||= []"
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method "add_to_#{validator_collection}" do |value|
|
17
|
+
(eval "@#{validator_collection} ||= []") << value
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method "__#{class_macro}__" do |*field_names|
|
21
|
+
field_names.each do |field|
|
22
|
+
validation_obj = validation_class_name.new field
|
23
|
+
self.public_send("add_to_#{validator_collection}", validation_obj)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
eval "alias :#{class_macro} :__#{class_macro}__"
|
28
|
+
end
|
29
|
+
|
30
|
+
def custom_validators
|
31
|
+
@custom_validators ||= []
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate(*methods, &block)
|
35
|
+
@custom_validators ||= []
|
36
|
+
methods.each do |method|
|
37
|
+
@custom_validators << (CsvRecord::CustomValidation.new method)
|
38
|
+
end
|
39
|
+
@custom_validators << (CsvRecord::CustomValidation.new block) if block_given?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module InstanceMethods
|
44
|
+
def __valid__?
|
45
|
+
trigger_presence_validations
|
46
|
+
trigger_uniqueness_validations
|
47
|
+
trigger_custom_validations
|
48
|
+
errors.empty?
|
49
|
+
end
|
50
|
+
|
51
|
+
def invalid?
|
52
|
+
not self.__valid__?
|
53
|
+
end
|
54
|
+
|
55
|
+
def errors
|
56
|
+
unless @errors
|
57
|
+
@errors = []
|
58
|
+
def @errors.add(attribute)
|
59
|
+
self << attribute
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
@errors
|
64
|
+
end
|
65
|
+
|
66
|
+
alias :valid? :__valid__?
|
67
|
+
|
68
|
+
private
|
69
|
+
def trigger_presence_validations
|
70
|
+
self.class.fields_to_validate_presence.each do |validator|
|
71
|
+
validator.run_on self
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def trigger_uniqueness_validations
|
76
|
+
self.class.fields_to_validate_uniqueness.each do |validator|
|
77
|
+
validator.run_on self
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def trigger_custom_validations
|
82
|
+
self.class.custom_validators.each do |validator|
|
83
|
+
validator.run_on self
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/csv_record/document.rb
CHANGED
@@ -7,29 +7,26 @@ require 'csv_record/timestamps'
|
|
7
7
|
require 'csv_record/callbacks'
|
8
8
|
require 'csv_record/helpers'
|
9
9
|
require 'csv_record/associations'
|
10
|
-
require 'csv_record/validations'
|
10
|
+
require 'csv_record/csv_validations/validations'
|
11
11
|
|
12
|
-
module
|
12
|
+
# This is the base module for all domain objects that need to be persisted to
|
13
|
+
# the database.
|
14
|
+
module CsvRecord::Document
|
15
|
+
def self.included(receiver)
|
16
|
+
klass = receiver.name
|
13
17
|
|
14
|
-
|
15
|
-
|
16
|
-
module Document
|
17
|
-
def self.included(receiver)
|
18
|
-
klass = receiver.name
|
18
|
+
receiver.const_set 'DATABASE_LOCATION',"db/#{klass.underscore.pluralize}.csv"
|
19
|
+
receiver.const_set 'DATABASE_LOCATION_TMP',"db/#{klass.underscore.pluralize}_tmp.csv"
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
receiver.send :include, CsvRecord::Timestamps
|
31
|
-
receiver.send :include, CsvRecord::Validations::InstanceMethods
|
32
|
-
receiver.send :include, CsvRecord::Callbacks
|
33
|
-
end
|
21
|
+
receiver.extend CsvRecord::Connector
|
22
|
+
receiver.extend CsvRecord::Writer::ClassMethods
|
23
|
+
receiver.extend CsvRecord::Reader::ClassMethods
|
24
|
+
receiver.extend CsvRecord::Associations
|
25
|
+
receiver.extend CsvRecord::Validations::ClassMethods
|
26
|
+
receiver.send :include, CsvRecord::Writer::InstanceMethods
|
27
|
+
receiver.send :include, CsvRecord::Reader::InstanceMethods
|
28
|
+
receiver.send :include, CsvRecord::Timestamps
|
29
|
+
receiver.send :include, CsvRecord::Validations::InstanceMethods
|
30
|
+
receiver.send :include, CsvRecord::Callbacks
|
34
31
|
end
|
35
32
|
end
|
data/lib/csv_record/reader.rb
CHANGED
@@ -1,121 +1,100 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def __fields__
|
15
|
-
@relevant_instance_variables
|
16
|
-
end
|
17
|
-
|
18
|
-
def all
|
19
|
-
open_database_file do |csv|
|
20
|
-
csv.entries.map { |attributes| self.build attributes }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def first
|
25
|
-
all.first
|
26
|
-
end
|
1
|
+
require_relative 'csv_queries/query'
|
2
|
+
|
3
|
+
module CsvRecord::Reader
|
4
|
+
module ClassMethods
|
5
|
+
DYNAMIC_FINDER_PATTERN = /^find_by_(.+)$/
|
6
|
+
|
7
|
+
def build(params={})
|
8
|
+
inst = new
|
9
|
+
params.each do |key, value|
|
10
|
+
inst.public_send("#{key}=", value)
|
11
|
+
end if params
|
12
|
+
inst
|
13
|
+
end
|
27
14
|
|
28
|
-
|
29
|
-
|
30
|
-
|
15
|
+
def __fields__
|
16
|
+
@relevant_instance_variables
|
17
|
+
end
|
31
18
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
19
|
+
def all
|
20
|
+
open_database_file do |csv|
|
21
|
+
csv.entries.map { |attributes| self.build attributes }
|
36
22
|
end
|
23
|
+
end
|
37
24
|
|
38
|
-
|
39
|
-
|
40
|
-
|
25
|
+
def first
|
26
|
+
all.first
|
27
|
+
end
|
41
28
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
rows.map { |row| self.build row.to_hash }
|
46
|
-
end
|
47
|
-
end
|
29
|
+
def last
|
30
|
+
all.last
|
31
|
+
end
|
48
32
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
else
|
53
|
-
super # You *must* call super if you don't handle the
|
54
|
-
# method, otherwise you'll mess up Ruby's method
|
55
|
-
# lookup.
|
56
|
-
end
|
33
|
+
def __count__
|
34
|
+
open_database_file do |csv|
|
35
|
+
csv.entries.size
|
57
36
|
end
|
37
|
+
end
|
58
38
|
|
59
|
-
|
60
|
-
|
61
|
-
|
39
|
+
def __find__(condition)
|
40
|
+
(__where__ id: condition.to_param).first
|
41
|
+
end
|
62
42
|
|
63
|
-
|
43
|
+
def __where__(params)
|
44
|
+
CsvRecord::Query.new self, params
|
45
|
+
end
|
64
46
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
47
|
+
def method_missing(meth, *args, &block)
|
48
|
+
if meth.to_s =~ DYNAMIC_FINDER_PATTERN
|
49
|
+
dynamic_finder $1, *args, &block
|
50
|
+
else
|
51
|
+
super # You *must* call super if you don't handle the
|
52
|
+
# method, otherwise you'll mess up Ruby's method
|
53
|
+
# lookup.
|
70
54
|
end
|
55
|
+
end
|
71
56
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
params.each_pair do |property, value|
|
76
|
-
conditions << "attributes['#{property}'] == '#{value}'"
|
77
|
-
conditions << ' && ' if (params.size > 1) && (index != params.size - 1)
|
78
|
-
index += 1
|
79
|
-
end
|
80
|
-
conditions
|
81
|
-
end
|
57
|
+
def respond_to?(meth)
|
58
|
+
(meth.to_s =~ DYNAMIC_FINDER_PATTERN) || super
|
59
|
+
end
|
82
60
|
|
83
|
-
|
84
|
-
properties = meth.split '_and_'
|
85
|
-
conditions = Hash[properties.zip args]
|
86
|
-
__where__ conditions
|
87
|
-
end
|
61
|
+
protected
|
88
62
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
63
|
+
def dynamic_finder(meth, *args, &block)
|
64
|
+
properties = meth.split '_and_'
|
65
|
+
conditions = Hash[properties.zip args]
|
66
|
+
__where__ conditions
|
93
67
|
end
|
94
68
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
69
|
+
alias :fields :__fields__
|
70
|
+
alias :find :__find__
|
71
|
+
alias :count :__count__
|
72
|
+
alias :where :__where__
|
73
|
+
end
|
99
74
|
|
100
|
-
|
101
|
-
|
102
|
-
|
75
|
+
module InstanceMethods
|
76
|
+
def __values__
|
77
|
+
self.class.fields.map { |attribute| self.public_send(attribute) }
|
78
|
+
end
|
103
79
|
|
104
|
-
|
105
|
-
|
106
|
-
|
80
|
+
def __attributes__
|
81
|
+
Hash[self.class.fields.zip self.values]
|
82
|
+
end
|
107
83
|
|
108
|
-
|
109
|
-
|
110
|
-
|
84
|
+
def __to_param__
|
85
|
+
self.id.to_s
|
86
|
+
end
|
111
87
|
|
112
|
-
|
113
|
-
|
114
|
-
|
88
|
+
def ==(obj)
|
89
|
+
self.class == obj.class and self.to_param == obj.to_param
|
90
|
+
end
|
115
91
|
|
116
|
-
|
117
|
-
|
118
|
-
alias :to_param :__to_param__
|
92
|
+
def !=(obj)
|
93
|
+
self.class != obj.class || self.to_param != obj.to_param
|
119
94
|
end
|
95
|
+
|
96
|
+
alias :attributes :__attributes__
|
97
|
+
alias :values :__values__
|
98
|
+
alias :to_param :__to_param__
|
120
99
|
end
|
121
100
|
end
|
@@ -1,35 +1,35 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
1
|
+
# This module handles the behaviour for setting up document created at
|
2
|
+
# timestamp.
|
3
|
+
module CsvRecord::Timestamps
|
4
|
+
def self.included(receiver)
|
5
|
+
receiver.send :attr_accessor, :created_at
|
6
|
+
receiver.send :attr_accessor, :updated_at
|
7
|
+
end
|
9
8
|
|
10
|
-
|
11
|
-
# only called on create.
|
12
|
-
#
|
13
|
-
# @example Set the created at time.
|
14
|
-
# person.set_created_at
|
15
|
-
def __set_created_at__
|
16
|
-
if !created_at
|
17
|
-
time = Time.now.utc
|
18
|
-
@created_at = time
|
19
|
-
@updated_at = time
|
20
|
-
end
|
21
|
-
end
|
9
|
+
private
|
22
10
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
11
|
+
# Update the created_at field on the Document to the current time. This is
|
12
|
+
# only called on create.
|
13
|
+
#
|
14
|
+
# @example Set the created at time.
|
15
|
+
# person.set_created_at
|
16
|
+
def __set_created_at__
|
17
|
+
if !created_at
|
18
|
+
time = Time.now.utc
|
19
|
+
@created_at = time
|
20
|
+
@updated_at = time
|
30
21
|
end
|
22
|
+
end
|
31
23
|
|
32
|
-
|
33
|
-
|
24
|
+
# Update the created_at field on the Document to the current time. This is
|
25
|
+
# called on each save.
|
26
|
+
#
|
27
|
+
# @example Set the updated at time.
|
28
|
+
# person.set_updated_at
|
29
|
+
def __set_updated_at__
|
30
|
+
@updated_at = Time.now.utc
|
34
31
|
end
|
32
|
+
|
33
|
+
alias :set_created_at :__set_created_at__
|
34
|
+
alias :set_updated_at :__set_updated_at__
|
35
35
|
end
|
data/lib/csv_record/version.rb
CHANGED