ruson 1.2.0 → 1.3.4
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 +4 -4
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/Dockerfile +30 -0
- data/README.md +187 -19
- data/lib/ruson.rb +2 -1
- data/lib/ruson/base.rb +58 -15
- data/lib/ruson/class/array.rb +7 -0
- data/lib/ruson/class/boolean.rb +13 -11
- data/lib/ruson/class/float.rb +6 -4
- data/lib/ruson/class/integer.rb +6 -4
- data/lib/ruson/class/time.rb +14 -0
- data/lib/ruson/converter.rb +2 -2
- data/lib/ruson/error.rb +3 -3
- data/lib/ruson/json.rb +9 -0
- data/lib/ruson/persistence.rb +83 -0
- data/lib/ruson/querying.rb +90 -0
- data/lib/ruson/value.rb +0 -1
- data/lib/ruson/version.rb +1 -1
- data/ruson.gemspec +18 -14
- metadata +50 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fba9838b250ac9e2b66d41f371c2bf5c5bcd6614699cbae56fa16e453bd18ced
|
4
|
+
data.tar.gz: 79fb3b97b0e7f034ded6b547ee46f158b71adde353cbdb6e38af74d32903de56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a112f20c0b9b09e1c83fc1e0b5450b7fe99022686e670a6932b15d17bb4a50defa7c64df0ec3fb714261f2108a8b0cc7c6ae67d911c110419006b9bdbb8fc295
|
7
|
+
data.tar.gz: a8337d211ff0a51fa57aab817bf1adc1becface00dd628f511fd14960399a4d9ece199a0a8302e001d529a62122a26733c81d728f591889151f36f6e0185d71f
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.5.0
|
data/Dockerfile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# How to use it
|
2
|
+
# =============
|
3
|
+
#
|
4
|
+
# Visit https://blog.zedroot.org/2015/04/30/using-docker-to-maintain-a-ruby-gem/
|
5
|
+
|
6
|
+
# ~~~~ Image base ~~~~
|
7
|
+
# Base image
|
8
|
+
FROM ruby:2.6.5-alpine
|
9
|
+
LABEL maintainer="klriutsa"
|
10
|
+
|
11
|
+
RUN mkdir -p /gem/
|
12
|
+
WORKDIR /gem/
|
13
|
+
ADD . /gem/
|
14
|
+
|
15
|
+
# ~~~~ OS Maintenance & Rails Preparation ~~~~
|
16
|
+
# Rubygems and Bundler
|
17
|
+
RUN apk add --no-cache \
|
18
|
+
--quiet \
|
19
|
+
build-base \
|
20
|
+
git \
|
21
|
+
&& touch ~/.gemrc \
|
22
|
+
&& echo "gem: --no-ri --no-rdoc" >> ~/.gemrc \
|
23
|
+
&& gem install rubygems-update \
|
24
|
+
&& update_rubygems \
|
25
|
+
&& gem install bundler \
|
26
|
+
&& bundle install \
|
27
|
+
&& rm -rf /var/cache/apk/*
|
28
|
+
|
29
|
+
ENTRYPOINT ["bundle", "exec"]
|
30
|
+
CMD ["rake", "-T"]
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Ruson
|
2
2
|
|
3
|
-
|
3
|
+
A Ruby serialization/deserialization library to convert Ruby Objects into JSON and back
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -20,6 +20,8 @@ Or install it yourself as:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
+
### Basic
|
24
|
+
|
23
25
|
post.json
|
24
26
|
```json
|
25
27
|
{
|
@@ -42,7 +44,7 @@ post.title #=> 'Ruson'
|
|
42
44
|
post.content #=> 'Welcome!'
|
43
45
|
```
|
44
46
|
|
45
|
-
|
47
|
+
#### name
|
46
48
|
|
47
49
|
post.json
|
48
50
|
```json
|
@@ -65,7 +67,7 @@ post = Post.new(json)
|
|
65
67
|
post.url #=> 'http://sample.com'
|
66
68
|
```
|
67
69
|
|
68
|
-
|
70
|
+
#### nilable
|
69
71
|
|
70
72
|
post.json
|
71
73
|
```json
|
@@ -85,9 +87,9 @@ json = File.read('post.json')
|
|
85
87
|
post = Post.new(json) #=> Ruson::NotNilException
|
86
88
|
```
|
87
89
|
|
88
|
-
|
90
|
+
#### nested class
|
89
91
|
|
90
|
-
|
92
|
+
##### class
|
91
93
|
|
92
94
|
post.json
|
93
95
|
```json
|
@@ -119,39 +121,47 @@ post = Post.new(json)
|
|
119
121
|
post.picture.url #=> 'http://sample.com/picture.png'
|
120
122
|
```
|
121
123
|
|
122
|
-
|
124
|
+
###### Primary classes
|
123
125
|
|
124
|
-
*
|
125
|
-
*
|
126
|
-
* Float
|
126
|
+
* Ruson::Array
|
127
|
+
* Ruson::Boolean
|
128
|
+
* Ruson::Float
|
129
|
+
* Ruson::Integer
|
130
|
+
* Ruson::Time
|
127
131
|
|
128
132
|
|
129
133
|
post.json
|
130
134
|
```json
|
131
135
|
{
|
132
136
|
"title": "Ruson",
|
137
|
+
"items": ["orange", "apple"],
|
133
138
|
"is_new": "true",
|
139
|
+
"rate": "3.8",
|
134
140
|
"view": "1234",
|
135
|
-
"
|
141
|
+
"expired_at": 1575608299
|
136
142
|
}
|
137
143
|
```
|
138
144
|
|
139
145
|
```ruby
|
140
146
|
class Post < Ruson::Base
|
141
147
|
field :title
|
142
|
-
field :
|
143
|
-
field :
|
144
|
-
field :rate, class: Float
|
148
|
+
field :items, class: Ruson::Array
|
149
|
+
field :is_new, class: Ruson::Boolean
|
150
|
+
field :rate, class: Ruson::Float
|
151
|
+
field :view, class: Ruson::Integer
|
152
|
+
field :expired_at, class: Ruson::Time
|
145
153
|
end
|
146
154
|
|
147
155
|
json = File.read('post.json')
|
148
156
|
post = Post.new(json)
|
157
|
+
post.items #=> ["orange", "apple"]
|
149
158
|
post.is_new #=> true
|
150
|
-
post.view #=> 1234
|
151
159
|
post.rate #=> 3.8
|
160
|
+
post.view #=> 1234
|
161
|
+
post.expired_at #=> 2019-12-06 04:58:19 +0000
|
152
162
|
```
|
153
163
|
|
154
|
-
|
164
|
+
##### each class
|
155
165
|
|
156
166
|
post.json
|
157
167
|
```json
|
@@ -185,7 +195,7 @@ post = Post.new(json)
|
|
185
195
|
post.tags.first.name #=> 'Ruby'
|
186
196
|
```
|
187
197
|
|
188
|
-
|
198
|
+
#### enum
|
189
199
|
|
190
200
|
article.json
|
191
201
|
```json
|
@@ -213,7 +223,7 @@ article.status = 'undefined'
|
|
213
223
|
#=> undefined is not a valid status (ArgumentError)
|
214
224
|
```
|
215
225
|
|
216
|
-
|
226
|
+
#### to_json
|
217
227
|
|
218
228
|
```ruby
|
219
229
|
class Post < Ruson::Base
|
@@ -228,7 +238,7 @@ post.url = 'https://example.com/examples'
|
|
228
238
|
post.to_json #=> "{\"title\":\"Ruson\",\"url\":\"https://example.com/examples\"}"
|
229
239
|
```
|
230
240
|
|
231
|
-
|
241
|
+
#### to_hash
|
232
242
|
|
233
243
|
```ruby
|
234
244
|
class Post < Ruson::Base
|
@@ -243,7 +253,7 @@ post.url = 'https://example.com/examples'
|
|
243
253
|
post.to_hash #=> {title: "Ruson", url: "https://example.com/examples" }
|
244
254
|
```
|
245
255
|
|
246
|
-
|
256
|
+
#### API json parser
|
247
257
|
|
248
258
|
```ruby
|
249
259
|
class Article < Ruson::Base
|
@@ -261,12 +271,170 @@ article = Article.new(response.body)
|
|
261
271
|
article.title
|
262
272
|
```
|
263
273
|
|
274
|
+
### Persistence
|
275
|
+
|
276
|
+
Persistence will save the models as JSON file, with the model ID as filename, and model class name as parent folder so that a `User` model with ID `1` will be saved as `Users/1.json`.
|
277
|
+
|
278
|
+
You *must* define the `output_folder` before to be able to call `save` or `destroy`.
|
279
|
+
|
280
|
+
```ruby
|
281
|
+
Ruson.output_folder = './db/'
|
282
|
+
```
|
283
|
+
|
284
|
+
#### Creating new record
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
class User < Ruson::Base
|
288
|
+
field :first_name
|
289
|
+
field :last_name
|
290
|
+
field :email
|
291
|
+
field :title
|
292
|
+
end
|
293
|
+
|
294
|
+
# Using new + save
|
295
|
+
guillaume = User.new(first_name: 'Guillaume', last_name: 'Briat', email: 'guillaume@kaamelott.fr')
|
296
|
+
guillaume.save # Creates the ./db/Users/1.json file
|
297
|
+
|
298
|
+
# Or using the create method
|
299
|
+
guillaume = User.create(first_name: 'Guillaume', last_name: 'Briat', email: 'guillaume@kaamelott.fr')
|
300
|
+
|
301
|
+
puts File.read('./db/Users/1.json')
|
302
|
+
{"first_name":"Guillaume","last_name":"Briat","email":"guillaume@kaamelott.fr"}
|
303
|
+
=> nil
|
304
|
+
```
|
305
|
+
|
306
|
+
#### Updating a record
|
307
|
+
|
308
|
+
```ruby
|
309
|
+
# Assigning a value + save
|
310
|
+
guillaume.title = 'Burgundians King'
|
311
|
+
guillaume.save # Updates the ./db/Users/1.json file
|
312
|
+
|
313
|
+
# Or using the update method
|
314
|
+
guillaume.update(title: 'Burgundians King')
|
315
|
+
|
316
|
+
puts File.read('./db/Users/1.json')
|
317
|
+
{"first_name":"Guillaume","last_name":"Briat","email":"guillaume@kaamelott.fr","title":"Burgundians King"}
|
318
|
+
=> nil
|
319
|
+
```
|
320
|
+
|
321
|
+
#### Destroying a record
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
guillaume.destroy # Deletes the ./db/Users/1.json file
|
325
|
+
|
326
|
+
puts File.read('./db/Users/1.json')
|
327
|
+
Traceback (most recent call last):
|
328
|
+
16: from /usr/local/bundle/gems/bundler-2.0.2/exe/bundle:30:in `block in <top (required)>'
|
329
|
+
...
|
330
|
+
2: from (irb):26
|
331
|
+
1: from (irb):26:in `read'
|
332
|
+
Errno::ENOENT (No such file or directory @ rb_sysopen - ./db/Users/1.json)
|
333
|
+
```
|
334
|
+
|
335
|
+
### Querying
|
336
|
+
|
337
|
+
Ruson allows you to query for existing records.
|
338
|
+
|
339
|
+
You *must* define the `output_folder` before to query records.
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
Ruson.output_folder = './db/'
|
343
|
+
```
|
344
|
+
|
345
|
+
#### Find a record by ID
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
User.find(1) # Searches for a ./db/Users/1.json file
|
349
|
+
|
350
|
+
# Searching a user which doesn't exist
|
351
|
+
User.find(1234) #=> nil
|
352
|
+
User.find!(1234) #=> raises Ruson::RecordNotFound
|
353
|
+
```
|
354
|
+
|
355
|
+
#### Find first record
|
356
|
+
|
357
|
+
```ruby
|
358
|
+
User.first # Loads the first ./db/Users/*.json file.
|
359
|
+
|
360
|
+
# Without existing User records
|
361
|
+
User.first #=> nil
|
362
|
+
User.first! #=> raises Ruson::RecordNotFound
|
363
|
+
```
|
364
|
+
|
365
|
+
#### Find a record by attributes
|
366
|
+
|
367
|
+
post.json
|
368
|
+
|
369
|
+
```json
|
370
|
+
{
|
371
|
+
"title": "Ruson",
|
372
|
+
"content": "Welcome!"
|
373
|
+
}
|
374
|
+
```
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
Post.create(File.read('post.json'))
|
378
|
+
```
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
Post.where(title: 'Ruson')
|
382
|
+
#=> [#<Post:0x000055bb2e907b78 @title="Ruson", @content="Welcome!", @id=1>]
|
383
|
+
|
384
|
+
Post.where(content: 'Wel')
|
385
|
+
#=> []
|
386
|
+
|
387
|
+
Post.where(content: 'Welcome!')
|
388
|
+
#=> [#<Post:0x000055bb2e907b78 @title="Ruson", @content="Welcome!", @id=1>]
|
389
|
+
|
390
|
+
Post.where(title: 'Ruson', content: 'Welcome!')
|
391
|
+
#=> [#<Post:0x000055bb2e907b78 @title="Ruson", @content="Welcome!", @id=1>]
|
392
|
+
```
|
393
|
+
|
264
394
|
## Development
|
265
395
|
|
396
|
+
### Without Docker
|
397
|
+
|
266
398
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
267
399
|
|
268
400
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
269
401
|
|
402
|
+
### With Docker
|
403
|
+
|
404
|
+
```
|
405
|
+
$ docker build -t `whoami`/ruson .
|
406
|
+
```
|
407
|
+
|
408
|
+
In order to see the available Rake tasks:
|
409
|
+
|
410
|
+
```
|
411
|
+
$ docker --rm `whoami`/ruson
|
412
|
+
rake build # Build ruson-1.2.0.gem into the pkg directory
|
413
|
+
rake clean # Remove any temporary products
|
414
|
+
rake clobber # Remove any generated files
|
415
|
+
rake install # Build and install ruson-1.2.0.gem into system gems
|
416
|
+
rake install:local # Build and install ruson-1.2.0.gem into system gems without network access
|
417
|
+
rake release[remote] # Create tag v1.2.0 and build and push ruson-1.2.0.gem to rubygems.org
|
418
|
+
rake spec # Run RSpec code examples
|
419
|
+
```
|
420
|
+
_`--rm` means delete the container after the command has ended._
|
421
|
+
|
422
|
+
In order to execute the tests:
|
423
|
+
|
424
|
+
```
|
425
|
+
$ docker run --rm -it --volume "$PWD":/gem/ `whoami`/ruson rake spec
|
426
|
+
```
|
427
|
+
_`--volume` is used to sync the files from your current folder into the container so that if you change a file, the modification is available in the container._
|
428
|
+
|
429
|
+
In the case you'd like to access the IRB console:
|
430
|
+
|
431
|
+
```
|
432
|
+
$ docker run --rm -it --volume "$PWD":/gem/ `whoami`/ruson irb
|
433
|
+
irb(main):001:0> require 'ruson'
|
434
|
+
=> true
|
435
|
+
irb(main):002:0>
|
436
|
+
```
|
437
|
+
|
270
438
|
## Contributing
|
271
439
|
|
272
440
|
Bug reports and pull requests are welcome on GitHub at https://github.com/klriutsa/ruson. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/lib/ruson.rb
CHANGED
data/lib/ruson/base.rb
CHANGED
@@ -2,14 +2,18 @@ require 'json'
|
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/core_ext'
|
4
4
|
|
5
|
+
require 'ruson/class/array'
|
5
6
|
require 'ruson/class/boolean'
|
6
|
-
require 'ruson/class/integer'
|
7
7
|
require 'ruson/class/float'
|
8
|
+
require 'ruson/class/integer'
|
9
|
+
require 'ruson/class/time'
|
8
10
|
|
9
11
|
require 'ruson/converter'
|
10
12
|
require 'ruson/json'
|
11
|
-
require 'ruson/value'
|
12
13
|
require 'ruson/nilable'
|
14
|
+
require 'ruson/persistence'
|
15
|
+
require 'ruson/querying'
|
16
|
+
require 'ruson/value'
|
13
17
|
|
14
18
|
require 'ruson/error'
|
15
19
|
|
@@ -48,16 +52,30 @@ module Ruson
|
|
48
52
|
end
|
49
53
|
|
50
54
|
def add_accessor(name, options = {})
|
55
|
+
options[:name] = options[:name].try(:to_sym) if options[:name]
|
56
|
+
|
51
57
|
@accessors ||= {}
|
52
58
|
@accessors.merge!({ name.to_sym => options })
|
53
59
|
end
|
54
60
|
end
|
55
61
|
|
62
|
+
# ~~~~ Mixins ~~~~
|
56
63
|
include Ruson::Converter
|
57
64
|
include Ruson::Json
|
58
|
-
include Ruson::Value
|
59
65
|
include Ruson::Nilable
|
66
|
+
include Ruson::Persistence
|
67
|
+
include Ruson::Querying
|
68
|
+
include Ruson::Value
|
69
|
+
|
70
|
+
# ~~~~ Class Methods ~~~~
|
71
|
+
def self.ensure_output_folder_is_defined
|
72
|
+
return if Ruson.output_folder
|
73
|
+
|
74
|
+
raise ArgumentError, 'No output folder defined. You can define it ' \
|
75
|
+
'using Ruson.output_folder = "/path/to/db/folder"'
|
76
|
+
end
|
60
77
|
|
78
|
+
# ~~~~ Instance Methods ~~~~
|
61
79
|
def initialize(json, root_key: nil)
|
62
80
|
params = get_hash_from_json(json)
|
63
81
|
params = params[root_key.to_s] unless root_key.nil?
|
@@ -66,28 +84,53 @@ module Ruson
|
|
66
84
|
end
|
67
85
|
|
68
86
|
def to_hash
|
69
|
-
convert_to_hash(self.class.accessors)
|
87
|
+
res = convert_to_hash(self.class.accessors)
|
88
|
+
|
89
|
+
res.inject({}) do |result, attributes|
|
90
|
+
key, value = attributes
|
91
|
+
accessor = self.class.accessors[key]
|
92
|
+
if accessor && accessor.key?(:name)
|
93
|
+
result[accessor[:name]] = value
|
94
|
+
else
|
95
|
+
result[key] = value
|
96
|
+
end
|
97
|
+
result
|
98
|
+
end
|
70
99
|
end
|
71
100
|
|
72
|
-
def to_json
|
73
|
-
to_hash
|
101
|
+
def to_json(options = {})
|
102
|
+
hash = to_hash
|
103
|
+
|
104
|
+
options[:exclude].each { |key| hash.delete(key) } if options[:exclude]
|
105
|
+
|
106
|
+
hash.to_json
|
74
107
|
end
|
75
108
|
|
76
109
|
private
|
77
110
|
|
111
|
+
def cast_value(value, accessor_options, options = {})
|
112
|
+
check_nilable(value, accessor_options)
|
113
|
+
get_val(value, accessor_options)
|
114
|
+
end
|
115
|
+
|
78
116
|
def init_attributes(accessors, params)
|
79
|
-
accessors
|
80
|
-
key_name = options[:name] || key
|
81
|
-
value = params[key_name]
|
117
|
+
update_attributes(accessors, params)
|
82
118
|
|
83
|
-
|
84
|
-
|
85
|
-
set_attribute(key, val)
|
86
|
-
end
|
119
|
+
self.class.attr_accessor(:id)
|
120
|
+
set_attribute(:id, params[:id]) if params && params[:id]
|
87
121
|
end
|
88
122
|
|
89
|
-
def set_attribute(attr_name,
|
90
|
-
|
123
|
+
def set_attribute(attr_name, value)
|
124
|
+
send("#{attr_name}=".to_sym, value)
|
125
|
+
end
|
126
|
+
|
127
|
+
def update_attributes(accessors, params)
|
128
|
+
accessors.each do |key, options|
|
129
|
+
value = params[options[:name]] || params[key] if params
|
130
|
+
|
131
|
+
casted_value = cast_value(value, options)
|
132
|
+
set_attribute(key, casted_value)
|
133
|
+
end
|
91
134
|
end
|
92
135
|
end
|
93
136
|
end
|
data/lib/ruson/class/boolean.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
module Ruson
|
2
|
+
class Boolean
|
3
|
+
def self.new(value)
|
4
|
+
return value if value.kind_of?(TrueClass) || value.kind_of?(FalseClass)
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
case value
|
7
|
+
when 'true'
|
8
|
+
return true
|
9
|
+
when 'false'
|
10
|
+
return false
|
11
|
+
else
|
12
|
+
return false
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
14
|
-
end
|
16
|
+
end
|
data/lib/ruson/class/float.rb
CHANGED
data/lib/ruson/class/integer.rb
CHANGED
data/lib/ruson/converter.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Ruson
|
2
2
|
module Converter
|
3
3
|
def convert_to_hash(accessors)
|
4
|
-
accessors.keys.inject({}) do |result, key|
|
4
|
+
accessors.keys.inject({ id: id }) do |result, key|
|
5
5
|
value = send(key)
|
6
6
|
result[key.to_sym] = convert_array_to_hash_value(value)
|
7
7
|
result
|
@@ -11,7 +11,7 @@ module Ruson
|
|
11
11
|
private
|
12
12
|
|
13
13
|
def convert_array_to_hash_value(value)
|
14
|
-
if value.instance_of?(Array)
|
14
|
+
if value.instance_of?(::Array)
|
15
15
|
value.inject([]) { |result, v| result << convert_ruson_to_hash_value(v) }
|
16
16
|
else
|
17
17
|
convert_ruson_to_hash_value(value)
|
data/lib/ruson/error.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
module Ruson
|
2
|
-
class NotNilException < StandardError
|
3
|
-
end
|
4
|
-
end
|
2
|
+
class NotNilException < StandardError; end
|
3
|
+
class RecordNotFound < StandardError; end
|
4
|
+
end
|
data/lib/ruson/json.rb
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
module Ruson
|
2
2
|
module Json
|
3
3
|
def get_hash_from_json(json)
|
4
|
+
return unless json
|
4
5
|
return json if json.class == ActiveSupport::HashWithIndifferentAccess
|
6
|
+
|
5
7
|
(json.class == Hash ? json : JSON.parse(json)).with_indifferent_access
|
6
8
|
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# Returns the ID from the JSON file path.
|
12
|
+
#
|
13
|
+
def id_from_file_path(path)
|
14
|
+
File.basename(path, '.json').to_i
|
15
|
+
end
|
7
16
|
end
|
8
17
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Ruson
|
2
|
+
module Persistence
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend(ClassMethods)
|
5
|
+
klass.extend(Ruson::Json)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def create(*args)
|
10
|
+
new_record = new(*args)
|
11
|
+
new_record.save
|
12
|
+
new_record
|
13
|
+
end
|
14
|
+
|
15
|
+
def model_base_path
|
16
|
+
File.join(Ruson.output_folder, "#{self.name}s")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete_file_from_disk
|
21
|
+
FileUtils.rm_f(model_path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy
|
25
|
+
delete_file_from_disk
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def ensure_model_folder_exists
|
30
|
+
return if File.exist?(self.class.model_base_path)
|
31
|
+
|
32
|
+
FileUtils.mkdir_p(self.class.model_base_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_uniq_id
|
36
|
+
ensure_model_folder_exists
|
37
|
+
|
38
|
+
return if id
|
39
|
+
|
40
|
+
id = 0
|
41
|
+
|
42
|
+
Dir.glob(File.join(self.class.model_base_path, '*.json')).each do |path|
|
43
|
+
file_id = id_from_file_path(path)
|
44
|
+
|
45
|
+
id = file_id if file_id > id
|
46
|
+
end
|
47
|
+
|
48
|
+
self.id = id + 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def model_path
|
52
|
+
File.join(self.class.model_base_path, "#{id}.json")
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_file_to_disk
|
56
|
+
File.open(model_path, 'w') do |file|
|
57
|
+
file.write(to_json(exclude: %i[id]))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def save
|
62
|
+
self.class.ensure_output_folder_is_defined
|
63
|
+
generate_uniq_id
|
64
|
+
write_file_to_disk
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
def update(attributes)
|
69
|
+
attributes.symbolize_keys!
|
70
|
+
|
71
|
+
# Takes only accessor for attributes to be updated, avoiding to nullify
|
72
|
+
# the other attributes.
|
73
|
+
filtered_accessors = self.class.accessors.select do |key, accessor_attrs|
|
74
|
+
attributes.key?(key) || attributes.key?(accessor_attrs[:name])
|
75
|
+
end
|
76
|
+
|
77
|
+
update_attributes(filtered_accessors, attributes)
|
78
|
+
|
79
|
+
save
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Ruson
|
2
|
+
module Querying
|
3
|
+
def self.included(klass)
|
4
|
+
klass.extend(ClassMethods)
|
5
|
+
klass.extend(Ruson::Json)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def all
|
10
|
+
ensure_output_folder_is_defined
|
11
|
+
|
12
|
+
model_files.collect do |path|
|
13
|
+
json = JSON.parse(File.read(path))
|
14
|
+
|
15
|
+
new(json.merge(id: id_from_file_path(path)))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def find(id)
|
20
|
+
ensure_output_folder_is_defined
|
21
|
+
|
22
|
+
file_path = File.join(model_base_path, "#{id}.json")
|
23
|
+
|
24
|
+
return unless File.exist?(file_path)
|
25
|
+
|
26
|
+
load(file_path, id: id)
|
27
|
+
end
|
28
|
+
|
29
|
+
def find!(id)
|
30
|
+
record = find(id)
|
31
|
+
|
32
|
+
raise Ruson::RecordNotFound unless record
|
33
|
+
|
34
|
+
record
|
35
|
+
end
|
36
|
+
|
37
|
+
def first
|
38
|
+
ensure_output_folder_is_defined
|
39
|
+
|
40
|
+
file_path = model_files.first
|
41
|
+
|
42
|
+
return unless file_path
|
43
|
+
|
44
|
+
id = id_from_file_path(file_path)
|
45
|
+
|
46
|
+
load(file_path, id: id)
|
47
|
+
end
|
48
|
+
|
49
|
+
def first!
|
50
|
+
record = first
|
51
|
+
|
52
|
+
raise Ruson::RecordNotFound unless record
|
53
|
+
|
54
|
+
record
|
55
|
+
end
|
56
|
+
|
57
|
+
def load(file_path, extra_json = {})
|
58
|
+
json = JSON.parse(File.read(file_path))
|
59
|
+
|
60
|
+
json.merge!(extra_json) if extra_json
|
61
|
+
|
62
|
+
new json
|
63
|
+
end
|
64
|
+
|
65
|
+
def where(attributes)
|
66
|
+
ensure_output_folder_is_defined
|
67
|
+
|
68
|
+
query_attributes = attributes.stringify_keys
|
69
|
+
|
70
|
+
models = model_files.collect do |path|
|
71
|
+
json = JSON.parse(File.read(path))
|
72
|
+
|
73
|
+
query_attributes_matches = query_attributes.keys.all? do |key|
|
74
|
+
json[key] == query_attributes[key]
|
75
|
+
end
|
76
|
+
|
77
|
+
if query_attributes_matches
|
78
|
+
new(json.merge(id: id_from_file_path(path)))
|
79
|
+
end
|
80
|
+
end.compact
|
81
|
+
|
82
|
+
Array(models)
|
83
|
+
end
|
84
|
+
|
85
|
+
def model_files
|
86
|
+
Dir.glob(File.join(model_base_path, '*.json'))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/ruson/value.rb
CHANGED
data/lib/ruson/version.rb
CHANGED
data/ruson.gemspec
CHANGED
@@ -1,28 +1,32 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require 'ruson/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'ruson'
|
8
8
|
spec.version = Ruson::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['klriutsa']
|
10
|
+
spec.email = ['kurushima.hiroya@gmail.com']
|
11
11
|
|
12
|
-
spec.summary = %q{
|
13
|
-
spec.description = %q{
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
12
|
+
spec.summary = %q{A Ruby serialization/deserialization library to convert Ruby Objects into JSON and back}
|
13
|
+
spec.description = %q{A Ruby serialization/deserialization library to convert Ruby Objects into JSON and back}
|
14
|
+
spec.homepage = 'https://github.com/klriutsa/ruson'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
spec.required_ruby_version = '>= 2.5'
|
16
17
|
|
17
18
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
19
|
f.match(%r{^(test|spec|features)/})
|
19
20
|
end
|
20
|
-
spec.bindir =
|
21
|
+
spec.bindir = 'exe'
|
21
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
-
spec.require_paths = [
|
23
|
+
spec.require_paths = ['lib']
|
23
24
|
|
24
|
-
spec.add_development_dependency "bundler", "~> 2.0"
|
25
|
-
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
27
25
|
spec.add_dependency 'activesupport'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
28
|
+
spec.add_development_dependency 'ffaker', '~> 2.13.0'
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
31
|
+
spec.add_development_dependency 'timecop', '~> 0.9.1'
|
28
32
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruson
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- klriutsa
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -24,6 +38,20 @@ dependencies:
|
|
24
38
|
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: ffaker
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.13.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.13.0
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rake
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,30 +81,33 @@ dependencies:
|
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '3.0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
84
|
+
name: timecop
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
58
86
|
requirements:
|
59
|
-
- - "
|
87
|
+
- - "~>"
|
60
88
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
62
|
-
type: :
|
89
|
+
version: 0.9.1
|
90
|
+
type: :development
|
63
91
|
prerelease: false
|
64
92
|
version_requirements: !ruby/object:Gem::Requirement
|
65
93
|
requirements:
|
66
|
-
- - "
|
94
|
+
- - "~>"
|
67
95
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
69
|
-
description:
|
96
|
+
version: 0.9.1
|
97
|
+
description: A Ruby serialization/deserialization library to convert Ruby Objects
|
98
|
+
into JSON and back
|
70
99
|
email:
|
71
|
-
- hiroya
|
100
|
+
- kurushima.hiroya@gmail.com
|
72
101
|
executables: []
|
73
102
|
extensions: []
|
74
103
|
extra_rdoc_files: []
|
75
104
|
files:
|
76
105
|
- ".gitignore"
|
77
106
|
- ".rspec"
|
107
|
+
- ".ruby-version"
|
78
108
|
- ".travis.yml"
|
79
109
|
- CODE_OF_CONDUCT.md
|
110
|
+
- Dockerfile
|
80
111
|
- Gemfile
|
81
112
|
- LICENSE.txt
|
82
113
|
- README.md
|
@@ -85,13 +116,17 @@ files:
|
|
85
116
|
- bin/setup
|
86
117
|
- lib/ruson.rb
|
87
118
|
- lib/ruson/base.rb
|
119
|
+
- lib/ruson/class/array.rb
|
88
120
|
- lib/ruson/class/boolean.rb
|
89
121
|
- lib/ruson/class/float.rb
|
90
122
|
- lib/ruson/class/integer.rb
|
123
|
+
- lib/ruson/class/time.rb
|
91
124
|
- lib/ruson/converter.rb
|
92
125
|
- lib/ruson/error.rb
|
93
126
|
- lib/ruson/json.rb
|
94
127
|
- lib/ruson/nilable.rb
|
128
|
+
- lib/ruson/persistence.rb
|
129
|
+
- lib/ruson/querying.rb
|
95
130
|
- lib/ruson/value.rb
|
96
131
|
- lib/ruson/version.rb
|
97
132
|
- ruson.gemspec
|
@@ -107,15 +142,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
107
142
|
requirements:
|
108
143
|
- - ">="
|
109
144
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
145
|
+
version: '2.5'
|
111
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
147
|
requirements:
|
113
148
|
- - ">="
|
114
149
|
- !ruby/object:Gem::Version
|
115
150
|
version: '0'
|
116
151
|
requirements: []
|
117
|
-
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 2.7.3
|
118
154
|
signing_key:
|
119
155
|
specification_version: 4
|
120
|
-
summary:
|
156
|
+
summary: A Ruby serialization/deserialization library to convert Ruby Objects into
|
157
|
+
JSON and back
|
121
158
|
test_files: []
|