csv_record 2.0.0 → 2.1.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.
Files changed (37) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +2 -0
  3. data/README.md +3 -3
  4. data/Rakefile +1 -1
  5. data/csv_record.gemspec +6 -7
  6. data/lib/csv_record/callbacks.rb +3 -3
  7. data/lib/csv_record/connector.rb +7 -7
  8. data/lib/csv_record/csv_fields.rb +45 -0
  9. data/lib/csv_record/csv_queries/query.rb +1 -1
  10. data/lib/csv_record/{reader.rb → csv_readers/class_reader.rb} +11 -29
  11. data/lib/csv_record/csv_readers/instance_reader.rb +29 -0
  12. data/lib/csv_record/csv_readers/reader.rb +9 -0
  13. data/lib/csv_record/csv_validations/custom_validation.rb +1 -1
  14. data/lib/csv_record/csv_validations/validations.rb +2 -2
  15. data/lib/csv_record/csv_writers/class_writer.rb +52 -0
  16. data/lib/csv_record/{writer.rb → csv_writers/instance_writer.rb} +4 -21
  17. data/lib/csv_record/csv_writers/writer.rb +9 -0
  18. data/lib/csv_record/document.rb +11 -12
  19. data/lib/csv_record/exceptions.rb +1 -0
  20. data/lib/csv_record/field.rb +21 -0
  21. data/lib/csv_record/helpers.rb +1 -1
  22. data/lib/csv_record/timestamps.rb +11 -2
  23. data/lib/csv_record/version.rb +4 -1
  24. data/test/csv_record/associations_test.rb +0 -3
  25. data/test/csv_record/callbacks_test.rb +0 -1
  26. data/test/csv_record/connector_test.rb +0 -2
  27. data/test/csv_record/helpers_test.rb +0 -1
  28. data/test/csv_record/query_test.rb +0 -1
  29. data/test/csv_record/reader_test.rb +122 -116
  30. data/test/csv_record/timestamps_test.rb +2 -5
  31. data/test/csv_record/validation_test.rb +13 -14
  32. data/test/csv_record/writer_test.rb +60 -18
  33. data/test/helpers.rb +7 -0
  34. data/test/models/customized_class.rb +11 -0
  35. data/test/monkey_patches/object_test.rb +2 -4
  36. data/test/test_helper.rb +9 -6
  37. metadata +38 -43
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .rvmrc
data/.travis.yml CHANGED
@@ -1,3 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 1.9.2
3
4
  - 1.9.3
5
+ - 2.0.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # CsvRecord
2
2
 
3
- [[![Build Status](https://travis-ci.org/lukelex/csv_record.png?branch=2.0.0)](https://travis-ci.org/lukelex/csv_record) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/lukelex/csv_record) [![Dependency Status](https://gemnasium.com/lukasalexandre/csv_record.png)](https://gemnasium.com/lukasalexandre/csv_record) [![Gem Version](https://fury-badge.herokuapp.com/rb/csv_record.png)](http://badge.fury.io/rb/csv_record)
3
+ [![Build Status](https://travis-ci.org/lukelex/csv_record.png?branch=2.0.0)](https://travis-ci.org/lukelex/csv_record) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/lukelex/csv_record) [![Dependency Status](https://gemnasium.com/lukasalexandre/csv_record.png)](https://gemnasium.com/lukasalexandre/csv_record) [![Gem Version](https://fury-badge.herokuapp.com/rb/csv_record.png)](http://badge.fury.io/rb/csv_record)
4
4
 
5
5
 
6
6
  CSV Record connects Ruby classes to CSV documents database to establish an almost zero-configuration persistence layer for applications.
@@ -11,7 +11,7 @@ CSV Record connects Ruby classes to CSV documents database to establish an almos
11
11
 
12
12
  The CSV_Record Wiki has lots of additional information about CSV_Record. Please browse the Wiki after finishing this README:
13
13
 
14
- wiki.github.com/lukasalexandre/csv_record
14
+ https://github.com/lukelex/csv_record/wiki
15
15
 
16
16
  ##Bug reports
17
17
 
@@ -21,7 +21,7 @@ If you discover a problem with CSV_Record, we would like to know about it. Howev
21
21
 
22
22
  We hope that you will consider contributing to CSV_Record. Please read this short overview for some information about how to get started:
23
23
 
24
- https://github.com/lukasalexandre/csv_record/wiki/Contributing
24
+ https://github.com/lukelex/csv_record/wiki/Contributing
25
25
 
26
26
  You will usually want to write tests for your changes. To run the test suite, go into CSV_Record's top-level directory and run "bundle install" and "rake". For the tests to pass.
27
27
 
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require "rake/testtask"
5
5
  Rake::TestTask.new do |t|
6
6
  t.libs.push "lib"
7
7
  t.test_files = FileList['test/csv_record/*_test.rb', 'test/monkey_patches/*_test.rb']
8
- t.verbose = true
8
+ t.verbose = false
9
9
  end
10
10
 
11
11
  desc "Run tests"
data/csv_record.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["lukasalexandre@gmail.com"]
7
7
  gem.description = %q{CSV Object-relational mapping for Ruby}
8
8
  gem.summary = %q{CSV Record connects Ruby classes to CSV documents database to establish an almost zero-configuration persistence layer for applications.}
9
- gem.homepage = "https://github.com/lukasalexandre/csv_record"
9
+ gem.homepage = "https://github.com/lukelex/csv_record"
10
10
 
11
11
  gem.license = 'MIT'
12
12
 
@@ -17,11 +17,10 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = ["lib"]
18
18
  gem.version = CsvRecord::VERSION
19
19
 
20
- gem.add_dependency 'activesupport'
20
+ gem.add_dependency 'activesupport', '~> 3.2.9'
21
21
 
22
- gem.add_development_dependency 'rake'
23
- gem.add_development_dependency 'pry'
24
- gem.add_development_dependency 'timecop'
25
- gem.add_development_dependency 'turn'
26
- gem.add_development_dependency 'minitest'
22
+ gem.add_development_dependency 'rake', '~> 0.9.2.2'
23
+ gem.add_development_dependency 'timecop', '~> 0.5.3'
24
+ gem.add_development_dependency 'turn', '~> 0.9.6'
25
+ gem.add_development_dependency 'minitest', '~> 4.3.1'
27
26
  end
@@ -39,9 +39,9 @@ module CsvRecord::Callbacks
39
39
  define_method "run_#{callback}_callbacks" do
40
40
  const_variable = "#{callback}_callbacks".upcase
41
41
  if self.class.const_defined? const_variable
42
- callbacks_collection = self.class.const_get(const_variable)
42
+ callbacks_collection = self.class.const_get const_variable
43
43
  callbacks_collection.each do |callback|
44
- callback.run_on(self)
44
+ callback.run_on self
45
45
  end
46
46
  end
47
47
  end
@@ -49,7 +49,7 @@ module CsvRecord::Callbacks
49
49
 
50
50
  [:build, :initialize].each do |initialize_method|
51
51
  define_method initialize_method do |*args|
52
- result = super(*args)
52
+ result = super *args
53
53
  self.run_after_initialize_callbacks
54
54
  result
55
55
  end
@@ -16,14 +16,14 @@ module CsvRecord::Connector
16
16
  __initialize_db_directory__
17
17
  unless db_initialized?
18
18
  open_database_file WRITE_MODE do |csv|
19
- csv << fields
19
+ csv << doppelganger_fields
20
20
  end
21
21
  end
22
22
  end
23
23
 
24
24
  # Checks wheter the database file exists
25
25
  def db_initialized?
26
- File.exist? self.const_get('DATABASE_LOCATION')
26
+ File.exist? self.const_get 'DATABASE_LOCATION'
27
27
  end
28
28
 
29
29
  # Open the database file
@@ -32,7 +32,7 @@ module CsvRecord::Connector
32
32
  def __open_database_file__(mode=READ_MODE)
33
33
  __initialize_db__ if mode == READ_MODE # fix this later
34
34
  CSV.open(self.const_get('DATABASE_LOCATION'), mode, headers: true) do |csv|
35
- yield(csv)
35
+ yield csv
36
36
  end
37
37
  end
38
38
 
@@ -42,7 +42,7 @@ module CsvRecord::Connector
42
42
  CSV.open(self.const_get('DATABASE_LOCATION_TMP'), WRITE_MODE, headers: true) do |copy|
43
43
  copy << fields
44
44
  csv.entries.each do |entry|
45
- new_row = yield(entry)
45
+ new_row = yield entry
46
46
  copy << new_row if new_row
47
47
  end
48
48
  end
@@ -54,10 +54,10 @@ module CsvRecord::Connector
54
54
 
55
55
  # Rename the TMP database file to replace the original
56
56
  def rename_database
57
- old_file = self.const_get('DATABASE_LOCATION')
58
- tmp_file = self.const_get('DATABASE_LOCATION_TMP')
57
+ old_file = self.const_get 'DATABASE_LOCATION'
58
+ tmp_file = self.const_get 'DATABASE_LOCATION_TMP'
59
59
  File.delete old_file
60
- File.rename(tmp_file, old_file)
60
+ File.rename tmp_file, old_file
61
61
  end
62
62
 
63
63
  alias :initialize_db_directory :__initialize_db_directory__
@@ -0,0 +1,45 @@
1
+ class CsvRecord::CsvFields
2
+ include Enumerable
3
+
4
+ def fields
5
+ @fields ||= []
6
+ end
7
+
8
+ def <<(field)
9
+ self.fields << field
10
+ end
11
+
12
+ def include?(field)
13
+ self.has_doppelganger? field
14
+ end
15
+
16
+ [:name, :doppelganger].each do |attribute|
17
+ define_method "has_#{attribute}?" do |field|
18
+ self.fields.any? do |field_model|
19
+ field_model.public_send(attribute) == field
20
+ end
21
+ end
22
+ end
23
+
24
+ def find_with_doppelganger(doppelganger)
25
+ self.fields.select do |field|
26
+ field.is? doppelganger
27
+ end.first
28
+ end
29
+
30
+ def each(&block)
31
+ self.fields.each &block
32
+ end
33
+
34
+ def method_missing(meth, *args, &block)
35
+ if self.to_a.respond_to? meth
36
+ self.to_a.public_send meth, *args, &block
37
+ else
38
+ super # You *must* call super if you don't handle the
39
+ # method, otherwise you'll mess up Ruby's method
40
+ # lookup.
41
+ end
42
+ end
43
+
44
+ alias :add :<<
45
+ end
@@ -39,7 +39,7 @@ class CsvRecord::Query
39
39
  end
40
40
 
41
41
  def each(&block)
42
- to_a.each(&block)
42
+ to_a.each &block
43
43
  end
44
44
 
45
45
  def empty?
@@ -1,4 +1,4 @@
1
- require_relative 'csv_queries/query'
1
+ require 'csv_record/csv_queries/query'
2
2
 
3
3
  module CsvRecord::Reader
4
4
  module ClassMethods
@@ -7,13 +7,20 @@ module CsvRecord::Reader
7
7
  def build(params={})
8
8
  inst = new
9
9
  params.each do |key, value|
10
- inst.public_send("#{key}=", value)
10
+ attribute = fields.find_with_doppelganger(key)
11
+ attr_name = attribute ? attribute.name : key
12
+ inst.public_send "#{attr_name}=", value
11
13
  end if params
14
+ yield inst if block_given?
12
15
  inst
13
16
  end
14
17
 
15
18
  def __fields__
16
- @relevant_instance_variables
19
+ @fields ||= CsvRecord::CsvFields.new
20
+ end
21
+
22
+ def __doppelganger_fields__
23
+ self.__fields__.map &:doppelganger
17
24
  end
18
25
 
19
26
  def all
@@ -70,31 +77,6 @@ module CsvRecord::Reader
70
77
  alias :find :__find__
71
78
  alias :count :__count__
72
79
  alias :where :__where__
73
- end
74
-
75
- module InstanceMethods
76
- def __values__
77
- self.class.fields.map { |attribute| self.public_send(attribute) }
78
- end
79
-
80
- def __attributes__
81
- Hash[self.class.fields.zip self.values]
82
- end
83
-
84
- def __to_param__
85
- self.id.to_s
86
- end
87
-
88
- def ==(obj)
89
- self.class == obj.class and self.to_param == obj.to_param
90
- end
91
-
92
- def !=(obj)
93
- self.class != obj.class || self.to_param != obj.to_param
94
- end
95
-
96
- alias :attributes :__attributes__
97
- alias :values :__values__
98
- alias :to_param :__to_param__
80
+ alias :doppelganger_fields :__doppelganger_fields__
99
81
  end
100
82
  end
@@ -0,0 +1,29 @@
1
+ module CsvRecord::Reader
2
+ module InstanceMethods
3
+ def __values__
4
+ self.class.fields.map do |attribute|
5
+ self.public_send attribute.name
6
+ end
7
+ end
8
+
9
+ def __attributes__
10
+ Hash[self.class.fields.zip self.values]
11
+ end
12
+
13
+ def __to_param__
14
+ self.id.to_s
15
+ end
16
+
17
+ def ==(obj)
18
+ self.class == obj.class and self.to_param == obj.to_param
19
+ end
20
+
21
+ def !=(obj)
22
+ self.class != obj.class || self.to_param != obj.to_param
23
+ end
24
+
25
+ alias :attributes :__attributes__
26
+ alias :values :__values__
27
+ alias :to_param :__to_param__
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ require 'csv_record/csv_readers/class_reader'
2
+ require 'csv_record/csv_readers/instance_reader'
3
+
4
+ module CsvRecord::Reader
5
+ def self.included(receiver)
6
+ receiver.extend ClassMethods
7
+ receiver.send :include, InstanceMethods
8
+ end
9
+ end
@@ -7,7 +7,7 @@ class CsvRecord::CustomValidation
7
7
 
8
8
  def run_on(obj)
9
9
  if self.message.is_a? Proc
10
- obj.instance_eval &(self.message)
10
+ obj.instance_eval &self.message
11
11
  else
12
12
  obj.send self.message
13
13
  end
@@ -20,7 +20,7 @@ module CsvRecord::Validations
20
20
  define_method "__#{class_macro}__" do |*field_names|
21
21
  field_names.each do |field|
22
22
  validation_obj = validation_class_name.new field
23
- self.public_send("add_to_#{validator_collection}", validation_obj)
23
+ self.public_send "add_to_#{validator_collection}", validation_obj
24
24
  end
25
25
  end
26
26
 
@@ -55,7 +55,7 @@ module CsvRecord::Validations
55
55
  def errors
56
56
  unless @errors
57
57
  @errors = []
58
- def @errors.add(attribute)
58
+ def @errors.add attribute
59
59
  self << attribute
60
60
  end
61
61
  end
@@ -0,0 +1,52 @@
1
+ module CsvRecord::Writer
2
+ module ClassMethods
3
+ def __create__(attributes={})
4
+ instance = self.build attributes
5
+ yield instance if block_given?
6
+ result = instance.save
7
+ instance
8
+ end
9
+
10
+ [:attr_accessor, :attr_writer].each do |custom_accessor|
11
+ define_method custom_accessor do |*attributes|
12
+ attributes.each do |attribute|
13
+ unless self.fields.has_name? attribute
14
+ self.fields << CsvRecord::Field.new(attribute)
15
+ end
16
+ end
17
+ super *attributes
18
+ end
19
+ end
20
+
21
+ def store_as(name)
22
+ @table_name = name.to_s.underscore.pluralize
23
+ redefine_database_location
24
+
25
+ @table_name
26
+ end
27
+
28
+ def table_name
29
+ @table_name ||= store_as name
30
+ end
31
+
32
+ def mapping(config=[])
33
+ config.each do |field, doppelganger|
34
+ unless self.fields.include? field
35
+ self.fields << (CsvRecord::Field.new field, doppelganger)
36
+ end
37
+ end
38
+ end
39
+
40
+ def redefine_database_location
41
+ if const_defined?('DATABASE_LOCATION') || const_defined?('DATABASE_LOCATION_TMP')
42
+ send :remove_const, 'DATABASE_LOCATION'
43
+ send :remove_const, 'DATABASE_LOCATION_TMP'
44
+ end
45
+
46
+ const_set 'DATABASE_LOCATION', "db/#{@table_name}.csv"
47
+ const_set 'DATABASE_LOCATION_TMP', "db/#{@table_name}_tmp.csv"
48
+ end
49
+
50
+ alias :create :__create__
51
+ end
52
+ end
@@ -1,22 +1,4 @@
1
1
  module CsvRecord::Writer
2
- module ClassMethods
3
- def __create__(attributes={})
4
- instance = self.build attributes
5
- result = instance.save
6
- instance
7
- end
8
-
9
- [:attr_accessor, :attr_writer].each do |custom_accessor|
10
- define_method custom_accessor do |*args|
11
- @relevant_instance_variables ||= []
12
- args.each { |arg| @relevant_instance_variables << arg }
13
- super *args
14
- end
15
- end
16
-
17
- alias :create :__create__
18
- end
19
-
20
2
  module InstanceMethods
21
3
  def self.included(receiver)
22
4
  receiver.send :attr_accessor, :id
@@ -41,11 +23,14 @@ module CsvRecord::Writer
41
23
 
42
24
  def __update_attributes__(params={validate: true})
43
25
  validate = params[:validate]
44
- params.delete(:validate)
26
+ params.delete :validate
45
27
 
46
28
  params.each do |field, value|
47
29
  self.public_send "#{field}=", value
48
30
  end
31
+
32
+ yield self if block_given?
33
+
49
34
  self.save validate
50
35
  end
51
36
 
@@ -66,12 +51,10 @@ module CsvRecord::Writer
66
51
  end
67
52
 
68
53
  def append_registry
69
- set_created_at
70
54
  write_object
71
55
  end
72
56
 
73
57
  def update_registry
74
- set_updated_at
75
58
  self.class.parse_database_file do |row|
76
59
  new_row = row
77
60
  new_row = self.values if self.id.to_i == row.field('id').to_i
@@ -0,0 +1,9 @@
1
+ require 'csv_record/csv_writers/instance_writer'
2
+ require 'csv_record/csv_writers/class_writer'
3
+
4
+ module CsvRecord::Writer
5
+ def self.included(receiver)
6
+ receiver.extend ClassMethods
7
+ receiver.send :include, InstanceMethods
8
+ end
9
+ end
@@ -1,32 +1,31 @@
1
1
  require 'active_support/core_ext/string/inflections.rb'
2
2
 
3
3
  require 'csv_record/connector'
4
- require 'csv_record/writer'
5
- require 'csv_record/reader'
4
+ require 'csv_record/csv_writers/writer'
5
+ require 'csv_record/csv_readers/reader'
6
6
  require 'csv_record/timestamps'
7
7
  require 'csv_record/callbacks'
8
8
  require 'csv_record/helpers'
9
9
  require 'csv_record/associations'
10
10
  require 'csv_record/csv_validations/validations'
11
11
 
12
+ require 'csv_record/csv_fields'
13
+ require 'csv_record/field'
14
+ require 'csv_record/exceptions'
15
+
12
16
  # This is the base module for all domain objects that need to be persisted to
13
17
  # the database.
14
18
  module CsvRecord::Document
15
19
  def self.included(receiver)
16
- klass = receiver.name
17
-
18
- receiver.const_set 'DATABASE_LOCATION',"db/#{klass.underscore.pluralize}.csv"
19
- receiver.const_set 'DATABASE_LOCATION_TMP',"db/#{klass.underscore.pluralize}_tmp.csv"
20
-
21
20
  receiver.extend CsvRecord::Connector
22
- receiver.extend CsvRecord::Writer::ClassMethods
23
- receiver.extend CsvRecord::Reader::ClassMethods
24
21
  receiver.extend CsvRecord::Associations
25
22
  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
23
+ receiver.send :include, CsvRecord::Reader
24
+ receiver.send :include, CsvRecord::Writer
29
25
  receiver.send :include, CsvRecord::Validations::InstanceMethods
30
26
  receiver.send :include, CsvRecord::Callbacks
27
+ receiver.send :include, CsvRecord::Timestamps
28
+
29
+ receiver.store_as receiver.name
31
30
  end
32
31
  end
@@ -0,0 +1 @@
1
+ class UndefienedCsvField < Exception ; end
@@ -0,0 +1,21 @@
1
+ class CsvRecord::Field
2
+ attr_reader :name, :doppelganger
3
+
4
+ def initialize(name, doppelganger=nil)
5
+ @name = name
6
+ @doppelganger = doppelganger
7
+ end
8
+
9
+ def doppelganger
10
+ @doppelganger || @name
11
+ end
12
+
13
+ def to_s
14
+ self.name.to_s
15
+ end
16
+
17
+ def is?(value)
18
+ self.doppelganger.to_sym == value.to_sym ||
19
+ self.name.to_sym == value.to_sym
20
+ end
21
+ end
@@ -20,7 +20,7 @@ end
20
20
 
21
21
  class String
22
22
  def constantize
23
- self.split('_').map {|w| w.capitalize}.join
23
+ self.split('_').map { |w| w.capitalize }.join
24
24
  end
25
25
  def to_class
26
26
  Object.const_get self.constantize.singularize
@@ -2,8 +2,17 @@
2
2
  # timestamp.
3
3
  module CsvRecord::Timestamps
4
4
  def self.included(receiver)
5
- receiver.send :attr_accessor, :created_at
6
- receiver.send :attr_accessor, :updated_at
5
+ receiver.class_eval do
6
+ attr_accessor :created_at, :updated_at
7
+
8
+ before_create do
9
+ set_created_at
10
+ end
11
+
12
+ after_update do
13
+ set_updated_at
14
+ end
15
+ end
7
16
  end
8
17
 
9
18
  private
@@ -1,3 +1,6 @@
1
1
  module CsvRecord
2
- VERSION = '2.0.0'
2
+ MAJOR = 2
3
+ MINOR = 1
4
+ PATCH = 0
5
+ VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}"
3
6
  end
@@ -1,7 +1,4 @@
1
1
  require_relative '../test_helper'
2
- require_relative '../models/jedi'
3
- require_relative '../models/jedi_order'
4
- require_relative '../models/padawan'
5
2
 
6
3
  describe CsvRecord::Associations do
7
4
  describe 'belongs_to' do
@@ -1,5 +1,4 @@
1
1
  require_relative '../test_helper'
2
- require_relative '../models/callback_test_class'
3
2
 
4
3
  describe CsvRecord::Callbacks do
5
4
  describe "Check the callback definitions" do
@@ -1,7 +1,5 @@
1
1
  require_relative '../test_helper'
2
2
 
3
- require_relative '../models/jedi'
4
-
5
3
  describe CsvRecord::Connector do
6
4
  describe 'initializing methods' do
7
5
  it ('responds to initialize_db_directory') { Jedi.must_respond_to :initialize_db_directory }
@@ -1,7 +1,6 @@
1
1
  require_relative '../test_helper'
2
2
 
3
3
  describe 'CsvRecord::Helpers' do
4
-
5
4
  it 'check if a string is an integer' do
6
5
  '2000'.integer?.must_equal true
7
6
  end
@@ -1,5 +1,4 @@
1
1
  require_relative '../test_helper'
2
- require_relative '../models/jedi'
3
2
 
4
3
  describe CsvRecord::Query do
5
4
  let (:query) { CsvRecord::Query.new Jedi, age: 37 }