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 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,12 @@
1
+ module IfItWerent4AR
2
+ module Search
3
+ def where(attrs = {})
4
+ IfItWerent4AR::FileOp::find_in_storage_by(attrs, self)
5
+ end
6
+
7
+ def find(id)
8
+ object = self.new(id: id)
9
+ IfItWerent4AR::FileOp::find_in_storage(object)
10
+ end
11
+ end
12
+ 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,9 @@
1
+ module IfItWerent4AR
2
+ module Validations
3
+ module_function
4
+
5
+ def validate_primary_key_presence(id)
6
+ raise("You must provide 'id' attribute in order to work with Storage!").inspect if id.blank?
7
+ end
8
+ end
9
+ 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: []