active_row 0.0.1

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 32981502f001b4782992312dde3995a47b7580ff
4
+ data.tar.gz: 36c08097e9719a92263b35fca67a23863edccf26
5
+ SHA512:
6
+ metadata.gz: 1a0ce23f41c880696ee84167010c3a482ce4220a46cc69fd4d5f46f3b68a7c6951420a5a225806fa6693c601a0d6e45aa6c1db84f3269ab1507b3779f6803fa7
7
+ data.tar.gz: 928e0520767b60002914c3d903cff1e87f1b106fc888a376c6450000a1af0f2130c753bf6c9ef9412dc12cdf1ee6aabfc44a82a7062f014eb542dc3f87975780
@@ -0,0 +1,139 @@
1
+ require 'csv'
2
+ require_relative 'active_row/class_attribute'
3
+
4
+ class ActiveRow
5
+
6
+ CONVERTERS = :all
7
+
8
+ class_attribute :table_name, :table_directory, :table_path
9
+
10
+ def self.table_name
11
+ "#{self.name.downcase}s"
12
+ end
13
+
14
+ def self.table_directory
15
+ "./tables/"
16
+ end
17
+
18
+ def self.table_path
19
+ "#{self.table_directory}#{self.table_name}.csv"
20
+ end
21
+
22
+ # Accepts a value for the id field.
23
+ #
24
+ # Returns self if a row is found and returns nil if no row is found
25
+ def self.find(id = nil)
26
+ csv_table = CSV.table(table_path, converters: CONVERTERS)
27
+ csv_row = csv_table.find { |row| row.field(:id) == id }
28
+ csv_row ? self.new(csv_row.to_hash) : nil
29
+ end
30
+
31
+ # Accepts a hash. By convention, keys should be the names of attributes
32
+ # as defined in the CSV headers and the atrributes of the subclass.
33
+ # However, attributes names are not validated by this method.
34
+ #
35
+ # Returns self if a row is found and returns nil if no row is found.
36
+ def self.find_by(attr_hash)
37
+ csv_table = CSV.table(table_path, converters: CONVERTERS)
38
+ # Find the row where values in the CSV::Row are
39
+ # equal to the values of the attributes that were provided
40
+ csv_row = csv_table.find { |row| row.values_at(*attr_hash.keys) == attr_hash.values}
41
+ csv_row ? self.new(csv_row.to_hash) : nil
42
+ end
43
+
44
+ # Returns true if the row is successfully saved. (Otherwise, an error will have occurred).
45
+ def save
46
+ attr_hash = self.attributes
47
+ if self.class.find_by(attr_hash)
48
+ return false
49
+ else
50
+ self.class.create(attr_hash)
51
+ return true
52
+ end
53
+ end
54
+
55
+ # Accepts a hash. By convention, keys should be the names of attributes
56
+ # as defined in the CSV headers and the atrributes of the subclass.
57
+ # However, attributes names are not validated by this method.
58
+ #
59
+ # Returns self if a row is successfully created.
60
+ def self.create(attr_hash)
61
+ CSV.open(table_path, 'a+') do |csv| # a+ => append to document
62
+ # concatenate an array of values [1, 'text', 'username']
63
+ csv << CSV::Row.new(attr_hash.keys, attr_hash.values).fields
64
+ end
65
+ self.new(attr_hash)
66
+ end
67
+
68
+ # Accepts a hash. By convention, keys should be the names of attributes
69
+ # as defined in the CSV headers and the atrributes of the subclass.
70
+ # However, attributes names are not validated by this method.
71
+ #
72
+ # Returns new_row as an instance of the subclass, or returns false.
73
+ def update(attr_hash)
74
+ # Prevent update of id field
75
+ attr_hash.reject! { |k, v| k == :id || k == "id" }
76
+ csv_table = CSV.table(table_path, converters: CONVERTERS)
77
+ old_row = csv_table.find { |row| row.field(:id) == id }
78
+ new_row = CSV::Row.new(old_row.headers, old_row.fields)
79
+
80
+ # Assign new values to the row by param name (which should be a field name)
81
+ attr_hash.each { |k, v| new_row[k] = v }
82
+ # Delete the old_row
83
+ csv_table = CSV.table(table_path, converters: CONVERTERS)
84
+ csv_table.delete_if do |row|
85
+ row == old_row
86
+ end
87
+ csv_table << new_row
88
+ # Write the csv_table to a file, replacing the original
89
+ File.open(table_path, 'w') do |f| # w => write-only
90
+ f.write(csv_table.to_csv)
91
+ end
92
+ # Note: AR returns true if successful, false if not.
93
+ # If AR is successful, it updates the receiving instance's attributes.
94
+ # This returns a new instance with the variables on success.
95
+ new_row ? self.class.new(new_row.to_hash) : false
96
+ end
97
+
98
+ def destroy
99
+ csv_table = CSV.table(table_path, converters: CONVERTERS)
100
+ # Delete the row from the table if it is equivalent to the receiving instance.
101
+ csv_table.delete_if do |row|
102
+ row == CSV::Row.new(attributes.keys, attributes.values)
103
+ end
104
+
105
+ # Write the csv_table to a file, replacing the original
106
+ File.open(table_path, 'w') do |f| # w => write-only
107
+ f.write(csv_table.to_csv)
108
+ end
109
+
110
+ # Get the file's contents and close it
111
+ file = File.open(table_path, 'rb')
112
+ contents = file.read
113
+ file.close
114
+
115
+ # If all rows but the header have been deleted...
116
+ if contents.lines.count < 2
117
+ # then ensure the headers are still there.
118
+ File.open(table_path, 'w') do |f| # w => write-only
119
+ f.write(attributes.keys.map(&:to_s).join(','))
120
+ end
121
+ end
122
+ self.freeze
123
+ end
124
+
125
+ # Returns a hash of the receiving's instance variables.
126
+ def attributes
127
+ attrs = {}
128
+ instance_variables.each { |instance_variable|
129
+ # Convert the instariable to a string, removing the @ (instance notation), and convert back to a symbol
130
+ attrs[instance_variable.to_s[1..-1].to_sym] = instance_variable_get(instance_variable)
131
+ }
132
+ attrs
133
+ end
134
+
135
+ def attributes=(new_attributes)
136
+ new_attributes.each { |key, value| instance_variable_set(key, value) }
137
+ attributes
138
+ end
139
+ end
@@ -0,0 +1,178 @@
1
+ # This file contains the source of the Class#class_attribute method from ActiveSupport.
2
+ # It also contains the dependencies of this method, which are defined in ActiveSupport.
3
+ # Reference: http://api.rubyonrails.org/classes/Class.html#method-i-class_attribute
4
+ # Source: https://github.com/rails/rails/blob/v4.2.5/activesupport/lib/active_support/core_ext/class/attribute.rb
5
+
6
+ module Kernel
7
+ # class_eval on an object acts like singleton_class.class_eval.
8
+ def class_eval(*args, &block)
9
+ singleton_class.class_eval(*args, &block)
10
+ end
11
+ end
12
+
13
+ class Module
14
+ def remove_possible_method(method)
15
+ if method_defined?(method) || private_method_defined?(method)
16
+ undef_method(method)
17
+ end
18
+ end
19
+
20
+ def redefine_method(method, &block)
21
+ remove_possible_method(method)
22
+ define_method(method, &block)
23
+ end
24
+ end
25
+
26
+ class Hash
27
+ # By default, only instances of Hash itself are extractable.
28
+ # Subclasses of Hash may implement this method and return
29
+ # true to declare themselves as extractable. If a Hash
30
+ # is extractable, Array#extract_options! pops it from
31
+ # the Array when it is the last element of the Array.
32
+ def extractable_options?
33
+ instance_of?(Hash)
34
+ end
35
+ end
36
+
37
+ class Array
38
+ # Extracts options from a set of arguments. Removes and returns the last
39
+ # element in the array if it's a hash, otherwise returns a blank hash.
40
+ #
41
+ # def options(*args)
42
+ # args.extract_options!
43
+ # end
44
+ #
45
+ # options(1, 2) # => {}
46
+ # options(1, 2, a: :b) # => {:a=>:b}
47
+ def extract_options!
48
+ if last.is_a?(Hash) && last.extractable_options?
49
+ pop
50
+ else
51
+ {}
52
+ end
53
+ end
54
+ end
55
+
56
+ class Class
57
+ # Declare a class-level attribute whose value is inheritable by subclasses.
58
+ # Subclasses can change their own value and it will not impact parent class.
59
+ #
60
+ # class Base
61
+ # class_attribute :setting
62
+ # end
63
+ #
64
+ # class Subclass < Base
65
+ # end
66
+ #
67
+ # Base.setting = true
68
+ # Subclass.setting # => true
69
+ # Subclass.setting = false
70
+ # Subclass.setting # => false
71
+ # Base.setting # => true
72
+ #
73
+ # In the above case as long as Subclass does not assign a value to setting
74
+ # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
75
+ # would read value assigned to parent class. Once Subclass assigns a value then
76
+ # the value assigned by Subclass would be returned.
77
+ #
78
+ # This matches normal Ruby method inheritance: think of writing an attribute
79
+ # on a subclass as overriding the reader method. However, you need to be aware
80
+ # when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
81
+ # In such cases, you don't want to do changes in places but use setters:
82
+ #
83
+ # Base.setting = []
84
+ # Base.setting # => []
85
+ # Subclass.setting # => []
86
+ #
87
+ # # Appending in child changes both parent and child because it is the same object:
88
+ # Subclass.setting << :foo
89
+ # Base.setting # => [:foo]
90
+ # Subclass.setting # => [:foo]
91
+ #
92
+ # # Use setters to not propagate changes:
93
+ # Base.setting = []
94
+ # Subclass.setting += [:foo]
95
+ # Base.setting # => []
96
+ # Subclass.setting # => [:foo]
97
+ #
98
+ # For convenience, an instance predicate method is defined as well.
99
+ # To skip it, pass <tt>instance_predicate: false</tt>.
100
+ #
101
+ # Subclass.setting? # => false
102
+ #
103
+ # Instances may overwrite the class value in the same way:
104
+ #
105
+ # Base.setting = true
106
+ # object = Base.new
107
+ # object.setting # => true
108
+ # object.setting = false
109
+ # object.setting # => false
110
+ # Base.setting # => true
111
+ #
112
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
113
+ #
114
+ # object.setting # => NoMethodError
115
+ # object.setting? # => NoMethodError
116
+ #
117
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
118
+ #
119
+ # object.setting = false # => NoMethodError
120
+ #
121
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
122
+ def class_attribute(*attrs)
123
+ options = attrs.extract_options!
124
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
125
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
126
+ instance_predicate = options.fetch(:instance_predicate, true)
127
+
128
+ attrs.each do |name|
129
+ define_singleton_method(name) { nil }
130
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
131
+
132
+ ivar = "@#{name}"
133
+
134
+ define_singleton_method("#{name}=") do |val|
135
+ singleton_class.class_eval do
136
+ remove_possible_method(name)
137
+ define_method(name) { val }
138
+ end
139
+
140
+ if singleton_class?
141
+ class_eval do
142
+ remove_possible_method(name)
143
+ define_method(name) do
144
+ if instance_variable_defined? ivar
145
+ instance_variable_get ivar
146
+ else
147
+ singleton_class.send name
148
+ end
149
+ end
150
+ end
151
+ end
152
+ val
153
+ end
154
+
155
+ if instance_reader
156
+ remove_possible_method name
157
+ define_method(name) do
158
+ if instance_variable_defined?(ivar)
159
+ instance_variable_get ivar
160
+ else
161
+ self.class.public_send name
162
+ end
163
+ end
164
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
165
+ end
166
+
167
+ attr_writer name if instance_writer
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ unless respond_to?(:singleton_class?)
174
+ def singleton_class?
175
+ ancestors.first != self
176
+ end
177
+ end
178
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_row
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nick Quaranto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-11 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Provides behavior similar to ActiveRecord, but stores records in a CSV.
14
+ email: local.mat@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/active_row.rb
20
+ - lib/active_row/class_attribute.rb
21
+ homepage: https://github.com/sealocal/active_row
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.4.6
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: active_row
45
+ test_files: []