active_row 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []