json-orm 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 461557a2ba968f8defd11ef7b9d883a90b1266c2a1d6efb25efb90557934c43e
4
+ data.tar.gz: fe002f32aa2023c804739b45e339cf460806bf5346861cc07e9b5b14d674f05b
5
+ SHA512:
6
+ metadata.gz: 815c89c584fbd99e2ffeafd23e2c06ca0929652b21d437cd74a50cc775ad1df6a2927191d7fb43fd958e740fac18e45c76ac67735f0d605b910c22208a0b861a
7
+ data.tar.gz: d382da205f87b4374f434533c70dfc4a12facb2f9b59eeb34bdacb7c702ac9b3c5f147e5d106acd878c7a4fa932635f1993a5b99b2784bcd63db84789d4fe430
data/.gitignore ADDED
@@ -0,0 +1,37 @@
1
+
2
+ # See https://help.github.com/articles/ignoring-files for more about ignoring files.
3
+ orm.log
4
+ orm.test.log
5
+ test_data.json.backup
6
+
7
+
8
+ # Ignore bundler config.
9
+ /.bundle
10
+
11
+ # Ignore all logfiles and tempfiles.
12
+ /log/*
13
+ /tmp/*
14
+
15
+ # Ignore system files
16
+ .DS_Store
17
+ .AppleDouble
18
+ .LSOverride
19
+
20
+ # Thumbnails
21
+ ._*
22
+
23
+ # Files that might appear in the root of a volume
24
+ .DocumentRevisions-V100
25
+ .fseventsd
26
+ .Spotlight-V100
27
+ .TemporaryItems
28
+ .Trashes
29
+ .VolumeIcon.icns
30
+ .com.apple.timemachine.donotpresent
31
+
32
+ # Directories potentially created on remote AFP share
33
+ .AppleDB
34
+ .AppleDesktop
35
+ Network Trash Folder
36
+ Temporary Items
37
+ .apdisk
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Glowing-Pixels-UG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # JSON ORM 🚀
2
+
3
+ `json-orm` is a Ruby gem providing a lightweight, JSON-based Object-Relational Mapping (ORM) system, primarily designed for simple data analytical applications 📊. It includes CRUD operations, transaction support, custom validations, and query chaining, ideal for small-scale projects.
4
+
5
+ 🚧 **Important Development Notice** 🚧
6
+
7
+ While designed for simplicity and ease of use, `json-orm` hasn't been optimized for file size or fully vetted for reliability as a database storage solution. It's best used in contexts where these factors are not critical.
8
+
9
+ ## Features ✨
10
+
11
+ - CRUD operations on JSON files
12
+ - Transaction support with commit and rollback
13
+ - Custom attribute validation
14
+ - Query chaining for advanced data filtering
15
+ - Basic logging for debugging
16
+ - Thread-safe operations
17
+
18
+ ## Future Plans
19
+
20
+ - [ ] Refactor logging
21
+ - [ ] Add tests to DB class
22
+ - [ ] Clean up better after test
23
+ - [ ] Improve test to validate reliability
24
+ - [ ] Add Validations class
25
+
26
+ ## Installation 🔧
27
+
28
+ Clone the repository and include it in your Ruby project:
29
+
30
+ ```bash
31
+ git clone https://github.com/your-username/jsonorm.git
32
+ ```
33
+
34
+ ## Usage 📘
35
+
36
+ ### Basic Operations
37
+
38
+ ```ruby
39
+ db = JSONORM::JSONDB.new('your_data.json')
40
+ orm = JSONORM::ORM.new(db)
41
+
42
+ orm.create({name: "John Doe", email: "john@example.com"})
43
+ found_record = orm.find(1)
44
+ orm.update(1, {name: "Jane Doe"})
45
+ orm.delete(1)
46
+ ```
47
+
48
+ ### Transactions
49
+
50
+ ```ruby
51
+ orm.begin_transaction
52
+ # Operations...
53
+ orm.commit_transaction
54
+ ```
55
+
56
+ ### Custom Validations
57
+
58
+ ```ruby
59
+ JSONORM.register_validator(:email) do |value|
60
+ # Validation logic...
61
+ end
62
+ ```
63
+
64
+ ### Query Chaining
65
+
66
+ ```ruby
67
+ results = orm.where(age: 30).where(city: "Wonderland").execute
68
+ ```
69
+
70
+ ## Testing with MiniTest 🧪
71
+
72
+ Tests are located in the `test` directory. Run them using MiniTest to ensure reliability.
73
+
74
+ ## Contributing 🤝
75
+
76
+ Contributions are welcome. Please ensure to follow Ruby coding style and best practices, and write tests for new functionalities.
77
+
78
+ ## License
79
+
80
+ Distributed under the MIT License.
data/json-orm.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "json-orm"
3
+ spec.version = File.read(File.join(File.dirname(__FILE__), 'lib', 'json-orm', 'version.rb')).match(/VERSION = ['"](.*)['"]/)[1]
4
+ spec.authors = ["Damir Mukimov"]
5
+ spec.email = ["mukimov.d@gmail.com"]
6
+
7
+ spec.summary = %q{A lightweight, JSON-based ORM for Ruby}
8
+ spec.description = %q{Provides basic ORM functionalities like CRUD operations, transaction support, and custom validations.}
9
+ spec.homepage = "https://www.glowing-pixels.com/json-orm"
10
+ spec.license = "MIT"
11
+
12
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
13
+ spec.bindir = "exe"
14
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
15
+ spec.require_paths = ["lib"]
16
+
17
+ spec.add_development_dependency "bundler", "~> 2.0"
18
+ spec.add_development_dependency "rake", "~> 13.0"
19
+ spec.add_development_dependency "minitest", "~> 5.0"
20
+ end
@@ -0,0 +1,17 @@
1
+ module JSONORM
2
+ class ChainableQuery
3
+ def initialize(orm, data)
4
+ @orm = orm
5
+ @data = data
6
+ end
7
+
8
+ def where(attribute, value)
9
+ @data = @data.select { |record| record[attribute].to_s == value.to_s }
10
+ self
11
+ end
12
+
13
+ def execute
14
+ @data
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,69 @@
1
+ require 'json'
2
+ require 'fileutils'
3
+ require 'logger'
4
+ module JSONORM
5
+ class DB
6
+ attr_reader :file_path, :backup_path, :logger
7
+
8
+ def initialize(file_path, log_file)
9
+ @file_path = file_path
10
+ @backup_path = "#{file_path}.backup"
11
+ @logger = Logger.new(log_file)
12
+ initialize_file unless File.exist?(file_path)
13
+ end
14
+
15
+ def read
16
+ with_lock do
17
+ JSON.parse(File.read(file_path), symbolize_names: true)
18
+ rescue JSON::ParserError
19
+ raise "Error parsing JSON data in #{file_path}"
20
+ end
21
+ end
22
+
23
+ def write(data)
24
+ with_lock do
25
+ create_backup
26
+ File.open(file_path, 'w') { |f| f.write(JSON.pretty_generate(data)) }
27
+ logger.info("Data written successfully")
28
+ rescue IOError => e
29
+ restore_backup
30
+ logger.error("Error writing to file: #{e.message}")
31
+ raise "Error writing to file: #{e.message}"
32
+ end
33
+ end
34
+
35
+
36
+ private
37
+
38
+ def initialize_file
39
+ with_lock { File.open(file_path, 'w') { |f| f.write('[]') } }
40
+ end
41
+
42
+ def create_backup
43
+ logger.info("Creating backup")
44
+ FileUtils.cp(file_path, backup_path)
45
+ rescue => e
46
+ logger.error("Failed to create backup: #{e.message}")
47
+ raise "Failed to create backup: #{e.message}"
48
+ end
49
+
50
+ def restore_backup
51
+ logger.info("Restoring from backup")
52
+ FileUtils.cp(backup_path, file_path)
53
+ rescue => e
54
+ logger.error("Failed to restore backup: #{e.message}")
55
+ raise "Failed to restore backup: #{e.message}"
56
+ end
57
+
58
+
59
+ def with_lock
60
+ File.open("#{file_path}.lock", 'w') do |f|
61
+ f.flock(File::LOCK_EX)
62
+ yield
63
+ ensure
64
+ f.flock(File::LOCK_UN)
65
+ end
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,114 @@
1
+ module JSONORM
2
+ class ORM
3
+ attr_reader :database, :transaction_data, :logger
4
+
5
+ def initialize(database, log_file = 'orm.log')
6
+ @database = database
7
+ @transaction_data = nil
8
+ @attributes = {}
9
+ @logger = Logger.new(log_file)
10
+ end
11
+
12
+ # # Define a custom validator
13
+ # ORM.register_validator('email') do |value|
14
+ # raise "Invalid email format" unless value.match?(/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
15
+ # end
16
+
17
+ # # Define another custom validator, for example, for 'age'
18
+ # ORM.register_validator('age') do |value|
19
+ # raise "Invalid age" unless value.is_a?(Integer) && value >= 0
20
+ # end
21
+
22
+ def self.register_validator(attribute, &block)
23
+ @custom_validators ||= {}
24
+ @custom_validators[attribute] = block
25
+ end
26
+
27
+ def self.custom_validators
28
+ @custom_validators || {}
29
+ end
30
+
31
+ def all
32
+ read_data
33
+ end
34
+
35
+ def find(id)
36
+ read_data.detect { |record| record[:id] == id }
37
+ end
38
+
39
+ def where(attribute, value)
40
+ ChainableQuery.new(self, read_data).where(attribute, value)
41
+ end
42
+
43
+ def create(attributes)
44
+ attributes[:id] = next_id unless attributes.key?(:id)
45
+ validate_attributes!(attributes)
46
+ transaction_data.push(attributes)
47
+ attributes
48
+ end
49
+
50
+ def update(id, new_attributes)
51
+ validate_attributes!(new_attributes, false)
52
+ transaction_data.map! do |record|
53
+ if record[:id] == id
54
+ updated_record = record.merge(new_attributes)
55
+ validate_attributes!(updated_record, false) # Validate after merge
56
+ updated_record
57
+ else
58
+ record
59
+ end
60
+ end
61
+ end
62
+
63
+ def delete(id)
64
+ transaction_data.reject! { |record| record[:id] == id }
65
+ end
66
+
67
+ def begin_transaction
68
+ @transaction_data = read_data.dup
69
+ end
70
+
71
+ def commit_transaction
72
+ logger.info("Starting transaction commit")
73
+ database.write(transaction_data)
74
+ logger.info("Transaction committed successfully")
75
+ rescue => e
76
+ logger.error("Failed to commit transaction: #{e.message}")
77
+ raise "Failed to commit transaction: #{e.message}"
78
+ ensure
79
+ @transaction_data = nil
80
+ end
81
+
82
+ def rollback_transaction
83
+ @transaction_data = nil
84
+ end
85
+
86
+ private
87
+
88
+ def read_data
89
+ transaction_data || database.read
90
+ end
91
+
92
+ def next_id
93
+ max_id = read_data.map { |record| record[:id] }.max || 0
94
+ max_id + 1
95
+ end
96
+
97
+ def validate_attributes!(attributes, check_id = true)
98
+ raise "Record must have an id" if check_id && !attributes[:id]
99
+
100
+ attributes.each do |key, value|
101
+ validate_attribute(key, value)
102
+ end
103
+ end
104
+
105
+ # Update the validation method
106
+ def validate_attribute(key, value)
107
+ if self.class.custom_validators[key]
108
+ self.class.custom_validators[key].call(value)
109
+ else
110
+ # Default validations (if any)
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ module JSONORM
2
+ VERSION = "0.1.0"
3
+ end
data/lib/json-orm.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'logger'
2
+ require_relative 'json-orm/version'
3
+ require_relative 'json-orm/db'
4
+ require_relative 'json-orm/orm'
5
+ require_relative 'json-orm/chainable-query'
6
+
7
+ module JSONORM
8
+ # Your gem's core logic or namespace setup
9
+ end
data/test_jsorm.rb ADDED
@@ -0,0 +1,109 @@
1
+ require 'minitest/autorun'
2
+ require_relative 'lib/json-orm'
3
+
4
+ class JSONORMTest < Minitest::Test
5
+ def setup
6
+ @db = JSONORM::DB.new('test_data.json', 'orm.test.log')
7
+ @orm = JSONORM::ORM.new(@db, 'orm.test.log')
8
+ @orm.begin_transaction
9
+ end
10
+
11
+ def after_tests
12
+ File.delete('test_data.json.backup') if File.exist?('test_data.backup')
13
+ end
14
+
15
+ def teardown
16
+ @orm.rollback_transaction
17
+ File.delete('test_data.json') if File.exist?('test_data.json')
18
+ File.delete('test_data.json.lock') if File.exist?('test_data.json.lock')
19
+ File.delete('test_data.json.backup') if File.exist?('test_data.backup')
20
+ end
21
+
22
+ def test_create
23
+ record = @orm.create({"name": "John Doe", "email": "john@example.com"})
24
+ assert_equal "John Doe", record[:name]
25
+ assert_equal "john@example.com", record[:email]
26
+ end
27
+
28
+ def test_read
29
+ record = @orm.create({"name": "Jane Doe", "email": "jane@example.com"})
30
+ found = @orm.find(record[:id])
31
+ assert_equal "Jane Doe", found[:name]
32
+ assert_equal "jane@example.com", found[:email]
33
+ end
34
+
35
+ def test_update
36
+ record = @orm.create({"name": "Jim Doe", "email": "jim@example.com"})
37
+ @orm.update(record[:id], {name: "James Doe"})
38
+ updated = @orm.find(record[:id])
39
+
40
+ assert_equal "James Doe", updated[:name]
41
+ assert_equal "jim@example.com", updated[:email]
42
+ end
43
+
44
+ def test_delete
45
+ record = @orm.create({"name": "Jack Doe", "email": "jack@example.com"})
46
+ @orm.delete(record[:id])
47
+
48
+ assert_nil @orm.find(record[:id])
49
+ end
50
+
51
+ def test_transaction_commit
52
+ @orm.begin_transaction
53
+ record = @orm.create({"name": "Jill Doe", "email": "jill@example.com"})
54
+ @orm.commit_transaction
55
+ assert_equal "Jill Doe", @orm.find(record[:id])[:name]
56
+ end
57
+
58
+ def test_transaction_rollback
59
+ record = @orm.create({"name": "Joe Doe", "email": "joe@example.com"})
60
+ @orm.rollback_transaction
61
+ assert_nil @orm.find(record[:id])
62
+ end
63
+
64
+ def test_valid_email
65
+ JSONORM::ORM.register_validator(:email) do |value|
66
+ raise "Invalid email format" unless value.match?(/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
67
+ end
68
+
69
+ assert_raises("Invalid email format") do
70
+ @orm.create({"name": "Invalid Email", "email": "invalid"})
71
+ end
72
+ end
73
+
74
+ def test_valid_age
75
+ JSONORM::ORM.register_validator(:age) do |value|
76
+ raise "Invalid age" unless value.is_a?(Integer) && value >= 0
77
+ end
78
+
79
+ assert_raises(RuntimeError) do
80
+ @orm.create({"name": "Invalid Age", "age": -5})
81
+ end
82
+ end
83
+
84
+ def test_query_chaining
85
+ @orm.create({"name": "Alice", "age": 30, "city": "Wonderland"})
86
+ @orm.create({"name": "Bob", "age": 30, "city": "Gotham"})
87
+ @orm.create({"name": "Charlie", "age": 40, "city": "Wonderland"})
88
+
89
+ results = @orm.where(:age, 30).where(:city, "Wonderland").execute
90
+ assert_equal 1, results.size
91
+ assert_equal "Alice", results.first[:name]
92
+ end
93
+
94
+
95
+ def test_error_handling_on_write
96
+ # Simulate an error during write operation, e.g., invalid data format
97
+ @orm.create({"name": "Test", "email": "test@example.com"})
98
+ @orm.database.stub :write, ->(_data) { raise IOError, "Write error" } do
99
+ assert_raises(RuntimeError) { @orm.commit_transaction }
100
+ end
101
+ end
102
+
103
+ def test_file_locking
104
+ # Test to ensure file locking is working (may require mocking)
105
+ # Mock file locking and simulate concurrent access
106
+ end
107
+
108
+ # Additional tests for other features or edge cases
109
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json-orm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Damir Mukimov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ description: Provides basic ORM functionalities like CRUD operations, transaction
56
+ support, and custom validations.
57
+ email:
58
+ - mukimov.d@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - LICENSE
65
+ - README.md
66
+ - json-orm.gemspec
67
+ - lib/json-orm.rb
68
+ - lib/json-orm/chainable-query.rb
69
+ - lib/json-orm/db.rb
70
+ - lib/json-orm/orm.rb
71
+ - lib/json-orm/version.rb
72
+ - test_jsorm.rb
73
+ homepage: https://www.glowing-pixels.com/json-orm
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubygems_version: 3.4.1
93
+ signing_key:
94
+ specification_version: 4
95
+ summary: A lightweight, JSON-based ORM for Ruby
96
+ test_files: []