csv_record 2.1.2 → 3.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.
- checksums.yaml +7 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -1
- data/README.md +51 -39
- data/Rakefile +1 -1
- data/csv_record.gemspec +8 -6
- data/lib/csv_record.rb +0 -23
- data/lib/csv_record/associations.rb +1 -1
- data/lib/csv_record/callback.rb +4 -2
- data/lib/csv_record/callbacks.rb +21 -19
- data/lib/csv_record/{csv_queries/condition.rb → condition.rb} +4 -2
- data/lib/csv_record/connector.rb +9 -7
- data/lib/csv_record/csv_validations/custom_validation.rb +8 -5
- data/lib/csv_record/csv_validations/presence_validation.rb +6 -4
- data/lib/csv_record/csv_validations/uniqueness_validation.rb +8 -5
- data/lib/csv_record/csv_validations/validations.rb +23 -28
- data/lib/csv_record/document.rb +12 -12
- data/lib/csv_record/field.rb +6 -4
- data/lib/csv_record/fields.rb +45 -0
- data/lib/csv_record/helpers.rb +10 -6
- data/lib/csv_record/{csv_queries/query.rb → query.rb} +8 -15
- data/lib/csv_record/reader.rb +120 -0
- data/lib/csv_record/timestamps.rb +4 -2
- data/lib/csv_record/version.rb +3 -3
- data/lib/csv_record/writer.rb +143 -0
- data/test/csv_record/associations_test.rb +1 -1
- data/test/csv_record/connector_test.rb +3 -3
- data/test/csv_record/reader_test.rb +6 -6
- data/test/csv_record/validation_test.rb +1 -1
- data/test/models/callback_test_class.rb +1 -2
- data/test/models/custom_errors_class.rb +2 -1
- data/test/models/customized_class.rb +1 -1
- data/test/models/jedi.rb +4 -3
- data/test/models/jedi_order.rb +2 -2
- data/test/models/padawan.rb +1 -1
- data/test/test_helper.rb +3 -3
- data/test/{helpers.rb → test_helpers.rb} +2 -2
- metadata +80 -52
- data/lib/csv_record/csv_fields.rb +0 -45
- data/lib/csv_record/csv_readers/class_reader.rb +0 -82
- data/lib/csv_record/csv_readers/instance_reader.rb +0 -29
- data/lib/csv_record/csv_readers/reader.rb +0 -9
- data/lib/csv_record/csv_writers/class_writer.rb +0 -52
- data/lib/csv_record/csv_writers/instance_writer.rb +0 -86
- data/lib/csv_record/csv_writers/writer.rb +0 -9
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class CsvRecord::CustomValidation
|
2
4
|
attr_accessor :message
|
3
5
|
|
@@ -6,15 +8,16 @@ class CsvRecord::CustomValidation
|
|
6
8
|
end
|
7
9
|
|
8
10
|
def run_on(obj)
|
9
|
-
if
|
10
|
-
obj.instance_eval
|
11
|
+
if message.is_a?(Proc)
|
12
|
+
obj.instance_eval(&self.message)
|
11
13
|
else
|
12
|
-
obj.send
|
14
|
+
obj.send message
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
private
|
18
|
+
private
|
19
|
+
|
17
20
|
def get_correct_block_type
|
18
21
|
self.class.send "#{self.type}_block"
|
19
22
|
end
|
20
|
-
end
|
23
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class CsvRecord::PresenceValidation
|
2
4
|
attr_accessor :field
|
3
5
|
|
@@ -6,8 +8,8 @@ class CsvRecord::PresenceValidation
|
|
6
8
|
end
|
7
9
|
|
8
10
|
def run_on(obj)
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
return unless obj.public_send(field).nil?
|
12
|
+
|
13
|
+
obj.errors.add field
|
12
14
|
end
|
13
|
-
end
|
15
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class CsvRecord::UniquenessValidation
|
2
4
|
attr_accessor :field
|
3
5
|
|
@@ -7,10 +9,11 @@ class CsvRecord::UniquenessValidation
|
|
7
9
|
|
8
10
|
def run_on(obj)
|
9
11
|
condition = {}
|
10
|
-
condition[
|
12
|
+
condition[field] = obj.public_send field
|
11
13
|
records = obj.class.__where__ condition
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
|
15
|
+
return unless records.any? { |record| record != obj }
|
16
|
+
|
17
|
+
obj.errors.add field
|
15
18
|
end
|
16
|
-
end
|
19
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'custom_validation'
|
4
|
+
require_relative 'uniqueness_validation'
|
5
|
+
require_relative 'presence_validation'
|
4
6
|
|
5
7
|
module CsvRecord::Validations
|
6
8
|
module ClassMethods
|
@@ -20,7 +22,7 @@ module CsvRecord::Validations
|
|
20
22
|
define_method "__#{class_macro}__" do |*field_names|
|
21
23
|
field_names.each do |field|
|
22
24
|
validation_obj = validation_class_name.new field
|
23
|
-
|
25
|
+
public_send "add_to_#{validator_collection}", validation_obj
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -32,11 +34,10 @@ module CsvRecord::Validations
|
|
32
34
|
end
|
33
35
|
|
34
36
|
def validate(*methods, &block)
|
35
|
-
@custom_validators ||= []
|
36
37
|
methods.each do |method|
|
37
|
-
|
38
|
+
custom_validators << CsvRecord::CustomValidation.new(method)
|
38
39
|
end
|
39
|
-
|
40
|
+
custom_validators << CsvRecord::CustomValidation.new(block) if block_given?
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -49,39 +50,33 @@ module CsvRecord::Validations
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def invalid?
|
52
|
-
not
|
53
|
+
not __valid__?
|
53
54
|
end
|
54
55
|
|
55
56
|
def errors
|
56
|
-
|
57
|
-
@errors = []
|
58
|
-
def @errors.add attribute
|
59
|
-
self << attribute
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
@errors
|
57
|
+
@errors ||= Errors.new
|
64
58
|
end
|
65
59
|
|
66
60
|
alias :valid? :__valid__?
|
67
61
|
|
68
62
|
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
63
|
|
75
|
-
|
76
|
-
|
77
|
-
|
64
|
+
[:presence, :uniqueness].each do |type|
|
65
|
+
define_method "trigger_#{type}_validations" do
|
66
|
+
fields_to_validate = self.class.public_send "fields_to_validate_#{type}"
|
67
|
+
fields_to_validate.each do |validator|
|
68
|
+
validator.run_on self
|
69
|
+
end
|
78
70
|
end
|
79
71
|
end
|
80
72
|
|
81
73
|
def trigger_custom_validations
|
82
|
-
self.class.custom_validators
|
83
|
-
validator.run_on self
|
84
|
-
end
|
74
|
+
self.class.custom_validators
|
75
|
+
.each { |validator| validator.run_on self }
|
85
76
|
end
|
86
77
|
end
|
87
|
-
|
78
|
+
|
79
|
+
class Errors < Array
|
80
|
+
alias :add :<<
|
81
|
+
end
|
82
|
+
end
|
data/lib/csv_record/document.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'active_support/core_ext/string/inflections.rb'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
require_relative 'connector'
|
4
|
+
require_relative 'writer'
|
5
|
+
require_relative 'reader'
|
6
|
+
require_relative 'timestamps'
|
7
|
+
require_relative 'callbacks'
|
8
|
+
require_relative 'helpers'
|
9
|
+
require_relative 'associations'
|
10
|
+
require_relative 'csv_validations/validations'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
require_relative 'fields'
|
13
|
+
require_relative 'field'
|
14
|
+
require_relative 'exceptions'
|
15
15
|
|
16
16
|
# This is the base module for all domain objects that need to be persisted to
|
17
17
|
# the database.
|
@@ -28,4 +28,4 @@ module CsvRecord::Document
|
|
28
28
|
|
29
29
|
receiver.store_as receiver.name
|
30
30
|
end
|
31
|
-
end
|
31
|
+
end
|
data/lib/csv_record/field.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class CsvRecord::Field
|
2
4
|
attr_reader :name, :doppelganger
|
3
5
|
|
@@ -11,11 +13,11 @@ class CsvRecord::Field
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def to_s
|
14
|
-
|
16
|
+
name.to_s
|
15
17
|
end
|
16
18
|
|
17
19
|
def is?(value)
|
18
|
-
|
19
|
-
|
20
|
+
doppelganger.to_sym == value.to_sym ||
|
21
|
+
name.to_sym == value.to_sym
|
20
22
|
end
|
21
|
-
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CsvRecord::Fields
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def fields
|
7
|
+
@fields ||= []
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(field)
|
11
|
+
fields << field
|
12
|
+
end
|
13
|
+
|
14
|
+
def include?(field)
|
15
|
+
has_doppelganger? field
|
16
|
+
end
|
17
|
+
|
18
|
+
[:name, :doppelganger].each do |attribute|
|
19
|
+
define_method "has_#{attribute}?" do |field|
|
20
|
+
fields.any? do |field_model|
|
21
|
+
field_model.public_send(attribute) == field
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_with_doppelganger(doppelganger)
|
27
|
+
fields
|
28
|
+
.select { |field| field.is? doppelganger }
|
29
|
+
.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def each(&block)
|
33
|
+
fields.each(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(meth, *args, &block)
|
37
|
+
if to_a.respond_to?(meth)
|
38
|
+
to_a.public_send(meth, *args, &block)
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
alias :add :<<
|
45
|
+
end
|
data/lib/csv_record/helpers.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Object
|
2
4
|
def integer?
|
3
|
-
/(?<number>^\d+$)/ =~
|
5
|
+
/(?<number>^\d+$)/ =~ to_s
|
4
6
|
not number.nil?
|
5
7
|
end
|
6
8
|
|
7
9
|
def float?
|
8
|
-
/(?<number>^\d+\.\d+)$/ =~
|
10
|
+
/(?<number>^\d+\.\d+)$/ =~ to_s
|
9
11
|
not number.nil?
|
10
12
|
end
|
11
13
|
|
@@ -20,16 +22,18 @@ end
|
|
20
22
|
|
21
23
|
class String
|
22
24
|
def constantize
|
23
|
-
|
25
|
+
split('_').map { |w| w.capitalize }.join
|
24
26
|
end
|
27
|
+
|
25
28
|
def to_class
|
26
|
-
Object.const_get
|
29
|
+
Object.const_get constantize.singularize
|
27
30
|
end
|
31
|
+
|
28
32
|
def underscore
|
29
|
-
|
33
|
+
gsub(/::/, '/').
|
30
34
|
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
31
35
|
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
32
36
|
tr("-", "_").
|
33
37
|
downcase
|
34
38
|
end
|
35
|
-
end
|
39
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'condition'
|
2
4
|
|
3
5
|
class CsvRecord::Query
|
@@ -9,7 +11,7 @@ class CsvRecord::Query
|
|
9
11
|
@klass = klass
|
10
12
|
|
11
13
|
@conditions = conditions.map do |condition|
|
12
|
-
CsvRecord::Condition.new
|
14
|
+
CsvRecord::Condition.new(*condition)
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -21,7 +23,7 @@ class CsvRecord::Query
|
|
21
23
|
|
22
24
|
def __trigger__
|
23
25
|
klass.open_database_file do |csv|
|
24
|
-
rows = search_for csv,
|
26
|
+
rows = search_for csv, conditions
|
25
27
|
rows.map { |row| klass.build row.to_hash }
|
26
28
|
end
|
27
29
|
end
|
@@ -39,7 +41,7 @@ class CsvRecord::Query
|
|
39
41
|
end
|
40
42
|
|
41
43
|
def each(&block)
|
42
|
-
to_a.each
|
44
|
+
to_a.each(&block)
|
43
45
|
end
|
44
46
|
|
45
47
|
def empty?
|
@@ -50,7 +52,7 @@ class CsvRecord::Query
|
|
50
52
|
alias :trigger :__trigger__
|
51
53
|
alias :to_a :__to_a__
|
52
54
|
|
53
|
-
|
55
|
+
private
|
54
56
|
|
55
57
|
def search_for(csv, params)
|
56
58
|
csv.entries.select do |attributes|
|
@@ -59,15 +61,6 @@ protected
|
|
59
61
|
end
|
60
62
|
|
61
63
|
def conditions_as_string
|
62
|
-
|
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)
|
64
|
+
@_conditions_as_string ||= conditions.map(&:to_code).join ' and '
|
72
65
|
end
|
73
|
-
end
|
66
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv_record/query'
|
4
|
+
|
5
|
+
module CsvRecord::Reader
|
6
|
+
module ClassMethods
|
7
|
+
DYNAMIC_FINDER_PATTERN = /^find_by_(.+)$/
|
8
|
+
|
9
|
+
def build(params={})
|
10
|
+
new.tap do |inst|
|
11
|
+
break unless params
|
12
|
+
|
13
|
+
params.each do |key, value|
|
14
|
+
attribute = fields.find_with_doppelganger(key)
|
15
|
+
attr_name = attribute ? attribute.name : key
|
16
|
+
inst.public_send "#{attr_name}=", value
|
17
|
+
end
|
18
|
+
|
19
|
+
yield inst if block_given?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def __fields__
|
24
|
+
@fields ||= ::CsvRecord::Fields.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def __doppelganger_fields__
|
28
|
+
__fields__.map(&:doppelganger)
|
29
|
+
end
|
30
|
+
|
31
|
+
def all
|
32
|
+
open_database_file do |csv|
|
33
|
+
csv.entries.map(&method(:build))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def first
|
38
|
+
all.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def last
|
42
|
+
all.last
|
43
|
+
end
|
44
|
+
|
45
|
+
def __count__
|
46
|
+
open_database_file do |csv|
|
47
|
+
csv.entries.size
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def __find__(condition)
|
52
|
+
(__where__ id: condition.to_param).first
|
53
|
+
end
|
54
|
+
|
55
|
+
def __where__(params)
|
56
|
+
::CsvRecord::Query.new self, params
|
57
|
+
end
|
58
|
+
|
59
|
+
def method_missing(meth, *args, &block)
|
60
|
+
if meth.to_s =~ DYNAMIC_FINDER_PATTERN
|
61
|
+
dynamic_finder $1, *args, &block
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def respond_to?(meth)
|
68
|
+
(meth.to_s =~ DYNAMIC_FINDER_PATTERN) || super
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def dynamic_finder(meth, *args, &block)
|
74
|
+
properties = meth.split '_and_'
|
75
|
+
conditions = Hash[properties.zip args]
|
76
|
+
__where__(conditions).first
|
77
|
+
end
|
78
|
+
|
79
|
+
alias :fields :__fields__
|
80
|
+
alias :find :__find__
|
81
|
+
alias :count :__count__
|
82
|
+
alias :where :__where__
|
83
|
+
alias :doppelganger_fields :__doppelganger_fields__
|
84
|
+
end
|
85
|
+
|
86
|
+
module InstanceMethods
|
87
|
+
def __values__
|
88
|
+
self.class.fields.map do |attribute|
|
89
|
+
public_send attribute.name
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def __attributes__
|
94
|
+
Hash[self.class.fields.zip(values)]
|
95
|
+
end
|
96
|
+
|
97
|
+
def __to_param__
|
98
|
+
id.to_s
|
99
|
+
end
|
100
|
+
|
101
|
+
def ==(obj)
|
102
|
+
self.class == obj.class and
|
103
|
+
to_param == obj.to_param
|
104
|
+
end
|
105
|
+
|
106
|
+
def !=(obj)
|
107
|
+
self.class != obj.class ||
|
108
|
+
to_param != obj.to_param
|
109
|
+
end
|
110
|
+
|
111
|
+
alias :attributes :__attributes__
|
112
|
+
alias :values :__values__
|
113
|
+
alias :to_param :__to_param__
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.included(receiver)
|
117
|
+
receiver.extend ClassMethods
|
118
|
+
receiver.send :include, InstanceMethods
|
119
|
+
end
|
120
|
+
end
|