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.
@@ -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,13 @@
1
+ class CsvRecord::PresenceValidation
2
+ attr_accessor :field
3
+
4
+ def initialize(field)
5
+ self.field = field
6
+ end
7
+
8
+ def run_on(obj)
9
+ if obj.public_send(self.field).nil?
10
+ obj.errors.add self.field
11
+ end
12
+ end
13
+ 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
@@ -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 CsvRecord
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
- # This is the base module for all domain objects that need to be persisted to
15
- # the database.
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
- receiver.const_set 'DATABASE_LOCATION',"db/#{klass.underscore.pluralize}.csv"
21
- receiver.const_set 'DATABASE_LOCATION_TMP',"db/#{klass.underscore.pluralize}_tmp.csv"
22
-
23
- receiver.extend CsvRecord::Connector
24
- receiver.extend CsvRecord::Writer::ClassMethods
25
- receiver.extend CsvRecord::Reader::ClassMethods
26
- receiver.extend CsvRecord::Associations
27
- receiver.extend CsvRecord::Validations::ClassMethods
28
- receiver.send :include, CsvRecord::Writer::InstanceMethods
29
- receiver.send :include, CsvRecord::Reader::InstanceMethods
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
@@ -1,121 +1,100 @@
1
- module CsvRecord
2
- module Reader
3
- module ClassMethods
4
- DYNAMIC_FINDER_PATTERN = /^find_by_(.+)$/
5
-
6
- def build(params={})
7
- inst = new
8
- params.each do |key, value|
9
- inst.public_send("#{key}=", value)
10
- end if params
11
- inst
12
- end
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
- def last
29
- all.last
30
- end
15
+ def __fields__
16
+ @relevant_instance_variables
17
+ end
31
18
 
32
- def __count__
33
- open_database_file do |csv|
34
- csv.entries.size
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
- def __find__(condition)
39
- (__where__ id: condition.to_param).first
40
- end
25
+ def first
26
+ all.first
27
+ end
41
28
 
42
- def __where__(params)
43
- open_database_file do |csv|
44
- rows = search_for csv, params
45
- rows.map { |row| self.build row.to_hash }
46
- end
47
- end
29
+ def last
30
+ all.last
31
+ end
48
32
 
49
- def method_missing(meth, *args, &block)
50
- if meth.to_s =~ DYNAMIC_FINDER_PATTERN
51
- dynamic_finder $1, *args, &block
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
- def respond_to?(meth)
60
- (meth.to_s =~ DYNAMIC_FINDER_PATTERN) || super
61
- end
39
+ def __find__(condition)
40
+ (__where__ id: condition.to_param).first
41
+ end
62
42
 
63
- protected
43
+ def __where__(params)
44
+ CsvRecord::Query.new self, params
45
+ end
64
46
 
65
- def search_for(csv, params)
66
- conditions = handle_params params
67
- csv.entries.select do |attributes|
68
- eval conditions
69
- end
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
- def handle_params(params)
73
- conditions = ''
74
- index = 0
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
- def dynamic_finder(meth, *args, &block)
84
- properties = meth.split '_and_'
85
- conditions = Hash[properties.zip args]
86
- __where__ conditions
87
- end
61
+ protected
88
62
 
89
- alias :fields :__fields__
90
- alias :find :__find__
91
- alias :count :__count__
92
- alias :where :__where__
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
- module InstanceMethods
96
- def __values__
97
- self.class.fields.map { |attribute| self.public_send(attribute) }
98
- end
69
+ alias :fields :__fields__
70
+ alias :find :__find__
71
+ alias :count :__count__
72
+ alias :where :__where__
73
+ end
99
74
 
100
- def __attributes__
101
- Hash[self.class.fields.zip self.values]
102
- end
75
+ module InstanceMethods
76
+ def __values__
77
+ self.class.fields.map { |attribute| self.public_send(attribute) }
78
+ end
103
79
 
104
- def __to_param__
105
- self.id.to_s
106
- end
80
+ def __attributes__
81
+ Hash[self.class.fields.zip self.values]
82
+ end
107
83
 
108
- def ==(obj)
109
- self.class == obj.class && self.to_param == obj.to_param
110
- end
84
+ def __to_param__
85
+ self.id.to_s
86
+ end
111
87
 
112
- def !=(obj)
113
- self.class != obj.class || self.to_param != obj.to_param
114
- end
88
+ def ==(obj)
89
+ self.class == obj.class and self.to_param == obj.to_param
90
+ end
115
91
 
116
- alias :attributes :__attributes__
117
- alias :values :__values__
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 CsvRecord
2
- # This module handles the behaviour for setting up document created at
3
- # timestamp.
4
- module Timestamps
5
- def self.included(receiver)
6
- receiver.send :attr_accessor, :created_at
7
- receiver.send :attr_accessor, :updated_at
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
- # Update the created_at field on the Document to the current time. This is
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
- # Update the created_at field on the Document to the current time. This is
24
- # called on each save.
25
- #
26
- # @example Set the updated at time.
27
- # person.set_updated_at
28
- def __set_updated_at__
29
- @updated_at = Time.now.utc
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
- alias :set_created_at :__set_created_at__
33
- alias :set_updated_at :__set_updated_at__
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
@@ -1,3 +1,3 @@
1
1
  module CsvRecord
2
- VERSION = "1.9.0"
2
+ VERSION = '2.0.0'
3
3
  end