if_it_werent_4_AR 0.0.9
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/README.md +95 -0
- data/lib/if_it_werent_4_AR.rb +47 -0
- data/lib/if_it_werent_4_AR/associations.rb +27 -0
- data/lib/if_it_werent_4_AR/file_op.rb +102 -0
- data/lib/if_it_werent_4_AR/initialization.rb +25 -0
- data/lib/if_it_werent_4_AR/search.rb +12 -0
- data/lib/if_it_werent_4_AR/serialization.rb +28 -0
- data/lib/if_it_werent_4_AR/storage_crud.rb +18 -0
- data/lib/if_it_werent_4_AR/validations.rb +9 -0
- data/lib/rails/generators/iitw4ar_generator.rb +25 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 917458a44b4cea7afbc9e7fc011a5b8295f38d87
|
4
|
+
data.tar.gz: 40c4d9a37128a215b8d2e06e0fcd29a63027ac87
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 554f673f318bc4bb3e52d97a3fa0b831f1b9473c76fe93473c5d079cef65b01f87fcfaa1a95b4cb5878384e20c1b7b4588f7d6c90cdf11d84080c9fc567ba04d
|
7
|
+
data.tar.gz: 5fe46815e8a043edc6d22a893bcb6e6fbe175d3ce1e12edd2ca566e3b507ad92c48b470cdcf14e4cb737c963064c582bf3dab60c644a94b4c6b4349a7f80d204
|
data/README.md
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# If it were not for ActiveRecord
|
2
|
+
|
3
|
+
A test task with minimum functionality to demonstrate basic knowledge and understanding of the AR pattern.
|
4
|
+
|
5
|
+
- Records are stored directly in filesystem
|
6
|
+
- Storage CRUD functionality and search capabilities
|
7
|
+
- Minimalistic error handler
|
8
|
+
- SerDes
|
9
|
+
- Generator for creating models
|
10
|
+
- Simple associations
|
11
|
+
|
12
|
+
### Version
|
13
|
+
0.0.3
|
14
|
+
|
15
|
+
### Details
|
16
|
+
- Storage
|
17
|
+
- Common dataset schema is stored under `db/schema_IFWN4AR.yml`. Each first level key represents a model. The second level keys stand for attributes and their values are their types correspondingly. Convention has it that keys(table names) should be the singular of the entity that they store attributes of.
|
18
|
+
- Each dataset is stored under `'db/DATASET_NAME_storage.csv'` (CSVs' must have a header)
|
19
|
+
- id must be present in schema as the first attribute.
|
20
|
+
- Only integer and text types are supported.
|
21
|
+
- SerDes
|
22
|
+
- Object serializes in a string where attributes have tabulation symbol between. Be sure to not put tabulation in object attributes.
|
23
|
+
- Associations
|
24
|
+
- belongs_to, has_many associations supported. In order to use them you need to assign them in models as class instance variavles and restart server.
|
25
|
+
```
|
26
|
+
class User < IfItWerent4AR::Base
|
27
|
+
@has_many = [MobilePhone, Passport]
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
### Installation
|
32
|
+
|
33
|
+
Add to your gemfile:
|
34
|
+
|
35
|
+
```
|
36
|
+
gem 'if_it_werent_4_AR', '>=0.0.6'
|
37
|
+
```
|
38
|
+
|
39
|
+
Add schema file `db/schema_IFWN4AR.yml`
|
40
|
+
|
41
|
+
```
|
42
|
+
user:
|
43
|
+
id: integer
|
44
|
+
name: text
|
45
|
+
mobile_phone:
|
46
|
+
id: integer
|
47
|
+
user_id: integer
|
48
|
+
book:
|
49
|
+
id: integer
|
50
|
+
author_id: integer
|
51
|
+
content: text
|
52
|
+
author:
|
53
|
+
id: integer
|
54
|
+
name: text
|
55
|
+
```
|
56
|
+
|
57
|
+
Run generator to create Model, storage if not exists with appropriate headers.
|
58
|
+
|
59
|
+
```
|
60
|
+
rails generate iitw4ar
|
61
|
+
```
|
62
|
+
|
63
|
+
### TODOs
|
64
|
+
|
65
|
+
- Don't hardcode serialization delimiter
|
66
|
+
- Add tests
|
67
|
+
- Optimize storage solution
|
68
|
+
- Add validations
|
69
|
+
- Add ability to change primary/foreign keys
|
70
|
+
- Add id auto incrementation
|
71
|
+
- Add callbacks
|
72
|
+
- A lot
|
73
|
+
|
74
|
+
### Features
|
75
|
+
|
76
|
+
- When a record is being deleted in the storage an empty string remains.
|
77
|
+
- Objects with the same attributes are considered equal(same object).
|
78
|
+
- When trying to get associations for a record (class instance) with an id existing in storage, associations will be fetched for that record in storage.
|
79
|
+
|
80
|
+
### By
|
81
|
+
|
82
|
+
[Azat Gataoulline]
|
83
|
+
|
84
|
+
### Source
|
85
|
+
|
86
|
+
[https://github.com/Gataniel/if_it_werent_4_AR.git]
|
87
|
+
|
88
|
+
License
|
89
|
+
----
|
90
|
+
MIT
|
91
|
+
|
92
|
+
[//]: #
|
93
|
+
|
94
|
+
[Azat Gataoulline]: <https://github.com/gataniel>
|
95
|
+
[https://github.com/joemccann/dillinger.git]: <https://github.com/joemccann/dillinger.git>
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'if_it_werent_4_AR/associations'
|
2
|
+
require 'if_it_werent_4_AR/initialization'
|
3
|
+
require 'if_it_werent_4_AR/file_op'
|
4
|
+
require 'if_it_werent_4_AR/storage_crud'
|
5
|
+
require 'if_it_werent_4_AR/search'
|
6
|
+
require 'if_it_werent_4_AR/serialization'
|
7
|
+
require 'if_it_werent_4_AR/validations'
|
8
|
+
|
9
|
+
module IfItWerent4AR
|
10
|
+
VALID_DATA_TYPES = %w(
|
11
|
+
integer
|
12
|
+
text
|
13
|
+
)
|
14
|
+
|
15
|
+
class Base
|
16
|
+
include StorageCRUD
|
17
|
+
include Serialization
|
18
|
+
extend Associations
|
19
|
+
extend Search
|
20
|
+
|
21
|
+
class << self
|
22
|
+
attr_accessor :attributes_with_types
|
23
|
+
attr_reader :belongs_to, :has_many
|
24
|
+
|
25
|
+
def attributes
|
26
|
+
attributes_with_types.keys
|
27
|
+
end
|
28
|
+
|
29
|
+
def primary_key
|
30
|
+
attributes.first
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(attrs = {})
|
35
|
+
# if attrs.present?
|
36
|
+
attrs.stringify_keys!
|
37
|
+
uknown_attrs = attrs.keys - self.class.attributes
|
38
|
+
raise "Unknown attributes #{uknown_attrs}" if uknown_attrs.present?
|
39
|
+
# end
|
40
|
+
assign_attributes(attrs)
|
41
|
+
end
|
42
|
+
|
43
|
+
def assign_attributes(attrs)
|
44
|
+
self.class.attributes.each { |attr| public_send("#{attr}=", attrs[attr]) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module IfItWerent4AR
|
2
|
+
module Associations
|
3
|
+
def add_associations
|
4
|
+
IfItWerent4AR::Base.descendants.each do |descendant|
|
5
|
+
if descendant.has_many
|
6
|
+
descendant.has_many.each do |hm|
|
7
|
+
descendant.class_eval do
|
8
|
+
define_method(hm.to_s.underscore.pluralize.to_sym) do
|
9
|
+
hm.where("#{descendant.to_s.underscore}_id": self.id)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if descendant.belongs_to
|
16
|
+
descendant.belongs_to.each do |bt|
|
17
|
+
descendant.class_eval do
|
18
|
+
define_method(bt.to_s.underscore.to_sym) do
|
19
|
+
bt.find(self.public_send("#{bt.to_s.underscore}_id"))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module IfItWerent4AR
|
4
|
+
module FileOp
|
5
|
+
# writes/updates/erase objects from storage file
|
6
|
+
DELIMITER = "\t"
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def storage_path(klass)
|
11
|
+
"db/#{klass.name.underscore}_storage.csv"
|
12
|
+
end
|
13
|
+
|
14
|
+
def write_to_storage(object)
|
15
|
+
if find_in_storage(object)
|
16
|
+
raise "Record with given id already exists".inspect
|
17
|
+
else
|
18
|
+
write(object)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def update_in_storage(object)
|
23
|
+
found_object = find_in_storage(object)
|
24
|
+
return false unless found_object
|
25
|
+
found_object.serialize == object.serialize ? object : rewrite(object)
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_in_storage(object)
|
29
|
+
IfItWerent4AR::Validations::validate_primary_key_presence(object.id)
|
30
|
+
found_object = nil
|
31
|
+
serialized = nil
|
32
|
+
CSV.foreach(self.storage_path(object.class), col_sep: DELIMITER, headers: true) do |csv_row|
|
33
|
+
if csv_row['id'].to_i == object.id
|
34
|
+
serialized = csv_row.fields.to_s
|
35
|
+
break
|
36
|
+
end
|
37
|
+
end
|
38
|
+
serialized ? object.class.deserialize(serialized) : false
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_in_storage_by(attrs = {}, klass)
|
42
|
+
attrs.stringify_keys!
|
43
|
+
found_records = []
|
44
|
+
CSV.foreach(self.storage_path(klass), col_sep: DELIMITER, headers: true) do |csv_row|
|
45
|
+
match = true
|
46
|
+
attrs.each_pair do |attr, value|
|
47
|
+
if value.to_s != csv_row[attr]
|
48
|
+
match = false
|
49
|
+
break
|
50
|
+
end
|
51
|
+
end
|
52
|
+
found_records << klass.deserialize(csv_row.fields.to_s) if match
|
53
|
+
end
|
54
|
+
found_records
|
55
|
+
end
|
56
|
+
|
57
|
+
def destroy_from_storage(object)
|
58
|
+
find_in_storage(object) ? erase(object) : false
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
module_function
|
64
|
+
|
65
|
+
def write(object)
|
66
|
+
CSV.open(self.storage_path(object.class), 'ab', col_sep: DELIMITER, headers: true) do |csv|
|
67
|
+
csv << eval(object.serialize)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def rewrite(object)
|
72
|
+
delete_line(object)
|
73
|
+
write(object)
|
74
|
+
end
|
75
|
+
|
76
|
+
def erase(object)
|
77
|
+
delete_line(object)
|
78
|
+
end
|
79
|
+
|
80
|
+
def delete_line(object)
|
81
|
+
is_deleted = false
|
82
|
+
headers = nil
|
83
|
+
storage = File.open(self.storage_path(object.class), 'r+')
|
84
|
+
storage.each do |line|
|
85
|
+
if headers
|
86
|
+
line_csv = CSV.parse(line, col_sep: DELIMITER)
|
87
|
+
if line_csv[0][0].to_i == object.id
|
88
|
+
# seek back to the beginning of the line
|
89
|
+
storage.seek(-line.length, IO::SEEK_CUR)
|
90
|
+
# overwrite line with spaces and add a newline char
|
91
|
+
storage.write(' ' * (line.length - 1))
|
92
|
+
is_deleted = true
|
93
|
+
break
|
94
|
+
end
|
95
|
+
end
|
96
|
+
headers ||= line
|
97
|
+
end
|
98
|
+
storage.close
|
99
|
+
is_deleted
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module IfItWerent4AR
|
2
|
+
module Initialization
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def get_schema
|
6
|
+
YAML.load_file("#{Rails.root}/db/schema_IFWN4AR.yml") rescue raise("No 'schema_IFWN4AR.yml' found in 'db' folder").inspect
|
7
|
+
end
|
8
|
+
|
9
|
+
def init_mapping
|
10
|
+
schema = get_schema
|
11
|
+
schema.each_pair do |table_name, attrs_with_types|
|
12
|
+
invalid_datatypes = schema[table_name].values.map do |datatype|
|
13
|
+
raise("Invalid datatypes #{invalid_datatypes} detected for #{table_name} in schema").inspect if IfItWerent4AR::VALID_DATA_TYPES.exclude?(datatype)
|
14
|
+
end
|
15
|
+
|
16
|
+
IfItWerent4AR::Base.add_associations
|
17
|
+
|
18
|
+
table_name.camelize.constantize.class_eval do
|
19
|
+
attr_accessor *(attrs_with_types.symbolize_keys.keys)
|
20
|
+
self.attributes_with_types = attrs_with_types
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module IfItWerent4AR
|
4
|
+
module Serialization
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def serialize
|
10
|
+
attrs_arr = []
|
11
|
+
self.class.attributes.map do |attr|
|
12
|
+
attrs_arr << send("#{attr}")
|
13
|
+
end
|
14
|
+
attrs_arr.to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def deserialize(serialized)
|
19
|
+
attrs_arr = eval(serialized)
|
20
|
+
attrs = {}
|
21
|
+
attributes_with_types.each_with_index do |(attr, type), index|
|
22
|
+
attrs[attr] = (type == 'integer' ? attrs_arr[index].to_s.to_i : attrs_arr[index])
|
23
|
+
end
|
24
|
+
new(attrs)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module IfItWerent4AR
|
2
|
+
module StorageCRUD
|
3
|
+
# creates/updates/destroys object in storage
|
4
|
+
|
5
|
+
def create(attributes = nil)
|
6
|
+
FileOp::write_to_storage(self) ? self : false
|
7
|
+
end
|
8
|
+
|
9
|
+
def update(attributes = nil)
|
10
|
+
assign_attributes(attributes) if attributes
|
11
|
+
FileOp::update_in_storage(self) ? self : false
|
12
|
+
end
|
13
|
+
|
14
|
+
def destroy
|
15
|
+
FileOp::destroy_from_storage(self) ? true : false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'if_it_werent_4_AR'
|
2
|
+
|
3
|
+
class Iitw4arGenerator < Rails::Generators::Base
|
4
|
+
def create_models_with_storages
|
5
|
+
schema = IfItWerent4AR::Initialization::get_schema
|
6
|
+
schema.each_pair do |table_name, fields_with_types|
|
7
|
+
create_file "app/models/#{table_name.underscore}.rb", <<-FILE
|
8
|
+
class #{table_name.camelize} < IfItWerent4AR::Base
|
9
|
+
end
|
10
|
+
FILE
|
11
|
+
|
12
|
+
create_file "db/#{table_name.underscore}_storage.csv", <<-FILE
|
13
|
+
#{fields_with_types.keys.join(IfItWerent4AR::FileOp::DELIMITER)}
|
14
|
+
FILE
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_initializer_file
|
19
|
+
create_file "config/initializers/itw4ar.rb", <<-FILE
|
20
|
+
require 'if_it_werent_4_AR'
|
21
|
+
|
22
|
+
IfItWerent4AR::Initialization::init_mapping
|
23
|
+
FILE
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: if_it_werent_4_AR
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Azat Gataoulline
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-07 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Provectus test task
|
14
|
+
email: gataniel@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- README.md
|
20
|
+
- lib/if_it_werent_4_AR.rb
|
21
|
+
- lib/if_it_werent_4_AR/associations.rb
|
22
|
+
- lib/if_it_werent_4_AR/file_op.rb
|
23
|
+
- lib/if_it_werent_4_AR/initialization.rb
|
24
|
+
- lib/if_it_werent_4_AR/search.rb
|
25
|
+
- lib/if_it_werent_4_AR/serialization.rb
|
26
|
+
- lib/if_it_werent_4_AR/storage_crud.rb
|
27
|
+
- lib/if_it_werent_4_AR/validations.rb
|
28
|
+
- lib/rails/generators/iitw4ar_generator.rb
|
29
|
+
homepage: http://rubygems.org/gems/if_it_werent_4_AR
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 2.5.0
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: test
|
53
|
+
test_files: []
|