├── .gitignore ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── rom-json.rb └── rom │ ├── json.rb │ └── json │ ├── dataset.rb │ ├── gateway.rb │ ├── relation.rb │ ├── schema.rb │ └── version.rb ├── rakelib └── rubocop.rake ├── rom-json.gemspec └── spec ├── fixtures ├── db │ ├── tasks.json │ └── users.json └── test_db.json ├── integration └── adapter_spec.rb ├── spec_helper.rb └── unit ├── dataset_spec.rb └── repository_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --order random 3 | --require ./spec/spec_helper.rb 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Generated by `rubocop --auto-gen-config` 2 | inherit_from: .rubocop_todo.yml 3 | 4 | AllCops: 5 | NewCops: enable 6 | 7 | Layout/DotPosition: 8 | EnforcedStyle: trailing 9 | 10 | # Documentation checked by Inch CI 11 | Style/Documentation: 12 | Enabled: false 13 | 14 | # Early returns have their vices 15 | Style/GuardClause: 16 | Enabled: false 17 | 18 | # Don’t introduce semantic fail/raise distinction 19 | Style/SignalException: 20 | EnforcedStyle: only_raise 21 | 22 | # Allow rom-json 23 | Naming/FileName: 24 | Enabled: false 25 | 26 | Metrics/BlockLength: 27 | Enabled: true 28 | Exclude: 29 | - 'spec/integration/adapter_spec.rb' 30 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by `rubocop --auto-gen-config` 2 | # on 2015-02-09 17:10:04 +0100 using RuboCop version 0.28.0. 3 | # The point is for the user to remove these configuration records 4 | # one by one as the offenses are removed from the code base. 5 | # Note that changes in the inspected code, or installation of new 6 | # versions of RuboCop, may require this file to be generated again. 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | bundler_args: --without tools 3 | env: 4 | - CODECLIMATE_REPO_TOKEN=ef69eb6a9e19987527e5010a02bbf71cd4ddc8a5137ba308f299817f56d06e7f 5 | script: "bundle exec rake spec" 6 | rvm: 7 | - 2.1.9 8 | - 2.2.5 9 | - 2.3.1 10 | - ruby-2.4.0-preview2 11 | - rbx 12 | - jruby-9.1.5.0 13 | - ruby-head 14 | matrix: 15 | allow_failures: 16 | - rvm: ruby-head 17 | - rvm: rbx 18 | notifications: 19 | webhooks: 20 | urls: 21 | - https://webhooks.gitter.im/e/39e1225f489f38b0bd09 22 | on_success: change 23 | on_failure: always 24 | on_start: false 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.0.1 2015-08-07 2 | 3 | First public release 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | group :test do 8 | gem 'codeclimate-test-reporter', require: false 9 | gem 'inflecto' 10 | gem 'rom', '~> 5.0' 11 | gem 'rspec', '~> 3.4' 12 | gem 'virtus' 13 | end 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Piotr Solnica 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [gem]: https://rubygems.org/gems/rom-json 2 | [travis]: https://travis-ci.org/rom-rb/rom-json 3 | [gemnasium]: https://gemnasium.com/rom-rb/rom-json 4 | [codeclimate]: https://codeclimate.com/github/rom-rb/rom-json 5 | [inchpages]: http://inch-ci.org/github/rom-rb/rom-json 6 | 7 | # ROM::JSON 8 | 9 | [![Gem Version](https://badge.fury.io/rb/rom-json.svg)][gem] 10 | [![Build Status](https://travis-ci.org/rom-rb/rom-json.svg?branch=master)][travis] 11 | [![Dependency Status](https://gemnasium.com/rom-rb/rom-json.png)][gemnasium] 12 | [![Code Climate](https://codeclimate.com/github/rom-rb/rom-json/badges/gpa.svg)][codeclimate] 13 | [![Test Coverage](https://codeclimate.com/github/rom-rb/rom-json/badges/coverage.svg)][codeclimate] 14 | [![Inline docs](http://inch-ci.org/github/rom-rb/rom-json.svg?branch=master)][inchpages] 15 | 16 | JSON support for [Ruby Object Mapper](https://github.com/rom-rb/rom) 17 | 18 | ## Installation 19 | 20 | Add this line to your application's Gemfile: 21 | 22 | ```ruby 23 | gem 'rom-json' 24 | ``` 25 | 26 | And then execute: 27 | 28 | $ bundle 29 | 30 | Or install it yourself as: 31 | 32 | $ gem install rom-json 33 | 34 | ## Usage 35 | 36 | See `spec/integration/adapter_spec.rb` for a sample usage. 37 | 38 | ## License 39 | 40 | See `LICENSE` file. 41 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspec/core/rake_task' 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | task default: :spec 7 | -------------------------------------------------------------------------------- /lib/rom-json.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rom/json' 4 | -------------------------------------------------------------------------------- /lib/rom/json.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rom' 4 | 5 | require 'rom/json/version' 6 | require 'rom/json/gateway' 7 | require 'rom/json/relation' 8 | 9 | ROM.register_adapter(:json, ROM::JSON) 10 | -------------------------------------------------------------------------------- /lib/rom/json/dataset.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rom/memory/dataset' 4 | 5 | module ROM 6 | module JSON 7 | # JSON in-memory dataset used by JSON gateways 8 | # 9 | # @api public 10 | class Dataset < ROM::Memory::Dataset 11 | # Data-row transformation proc 12 | # 13 | # @api private 14 | def self.row_proc 15 | Transforms[:deep_symbolize_keys] 16 | end 17 | end 18 | 19 | class Transforms 20 | extend Transproc::Registry 21 | import Transproc::HashTransformations 22 | import Transproc::Recursion 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/rom/json/gateway.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'json' 4 | 5 | require 'rom/gateway' 6 | require 'rom/json/dataset' 7 | 8 | module ROM 9 | module JSON 10 | # JSON gateway 11 | # 12 | # Connects to a json file and uses it as a data-source 13 | # 14 | # @example 15 | # rom = ROM.container(:json, '/path/to/data.yml') 16 | # gateway = rom.gateways[:default] 17 | # gateway[:users] # => data under 'users' key from the json file 18 | # 19 | # @api public 20 | class Gateway < ROM::Gateway 21 | adapter :json 22 | 23 | # @attr_reader [Hash] sources Data loaded from files 24 | # 25 | # @api private 26 | attr_reader :sources 27 | 28 | # @attr_reader [Hash] datasets JSON datasets from sources 29 | # 30 | # @api private 31 | attr_reader :datasets 32 | 33 | # Create a new json gateway from a path to file(s) 34 | # 35 | # @example 36 | # gateway = ROM::JSON::Gateway.new('/path/to/files') 37 | # 38 | # @param [String, Pathname] path The path to your JSON file(s) 39 | # 40 | # @return [Gateway] 41 | # 42 | # @api public 43 | def self.new(path) 44 | super(load_from(path)) 45 | end 46 | 47 | # Load data from json file(s) 48 | # 49 | # @api private 50 | def self.load_from(path) 51 | if File.directory?(path) 52 | load_files(path) 53 | else 54 | load_file(path) 55 | end 56 | end 57 | 58 | # Load json files from a given directory and return a name => data map 59 | # 60 | # @api private 61 | def self.load_files(path) 62 | Dir["#{path}/*.json"].each_with_object({}) do |file, h| 63 | name = File.basename(file, '.*') 64 | h[name] = load_file(file).fetch(name) 65 | end 66 | end 67 | 68 | # Load json file 69 | # 70 | # @api private 71 | def self.load_file(path) 72 | ::JSON.parse(File.read(path)) 73 | end 74 | 75 | # @param [String] path The absolute path to json file 76 | # 77 | # @api private 78 | def initialize(sources) 79 | super() 80 | @sources = sources 81 | @datasets = {} 82 | end 83 | 84 | # Return dataset by its name 85 | # 86 | # @param [Symbol] 87 | # 88 | # @return [Array] 89 | # 90 | # @api public 91 | def [](name) 92 | datasets.fetch(name) 93 | end 94 | 95 | # Register a new dataset 96 | # 97 | # @param [Symbol] 98 | # 99 | # @return [Dataset] 100 | # 101 | # @api public 102 | def dataset(name) 103 | datasets[name] = Dataset.new(sources.fetch(name.to_s)) 104 | end 105 | 106 | # Return if a dataset with provided name exists 107 | # 108 | # @api public 109 | def dataset?(name) 110 | datasets.key?(name) 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/rom/json/relation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rom/memory' 4 | require 'rom/json/schema' 5 | 6 | module ROM 7 | module JSON 8 | # JSON-specific relation subclass 9 | # 10 | # @api private 11 | class Relation < ROM::Memory::Relation 12 | adapter :json 13 | 14 | schema_class JSON::Schema 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rom/json/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rom/types' 4 | require 'rom/schema' 5 | 6 | module ROM 7 | module JSON 8 | # YAML relation schema 9 | # 10 | # @api public 11 | class Schema < ROM::Schema 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/rom/json/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ROM 4 | module JSON 5 | VERSION = '0.0.3' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /rakelib/rubocop.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubocop/rake_task' 4 | 5 | Rake::Task[:default].enhance [:rubocop] 6 | 7 | RuboCop::RakeTask.new do |task| 8 | task.options << '--display-cop-names' 9 | end 10 | 11 | namespace :rubocop do 12 | desc 'Generate a configuration file acting as a TODO list.' 13 | task :auto_gen_config do 14 | exec 'bundle exec rubocop --auto-gen-config' 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /rom-json.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'rom/json/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'rom-json' 9 | spec.version = ROM::JSON::VERSION.dup 10 | spec.authors = ['Piotr Solnica', 'Rolf Bjaanes'] 11 | spec.email = ['piotr.solnica@gmail.com'] 12 | spec.summary = 'JSON support for Ruby Object Mapper' 13 | spec.description = spec.summary 14 | spec.homepage = 'http://rom-rb.org' 15 | spec.license = 'MIT' 16 | 17 | spec.files = `git ls-files -z`.split("\x0") 18 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 19 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 20 | spec.require_paths = ['lib'] 21 | 22 | spec.required_ruby_version = '>= 2.4.0' 23 | 24 | spec.add_runtime_dependency 'rom-core', '~> 5.0' 25 | 26 | spec.add_development_dependency 'bundler' 27 | spec.add_development_dependency 'rake' 28 | spec.add_development_dependency 'rubocop' 29 | end 30 | -------------------------------------------------------------------------------- /spec/fixtures/db/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | { 4 | "title": "Task One" 5 | }, 6 | { 7 | "title": "Task Two" 8 | }, 9 | { 10 | "title": "Task Three" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /spec/fixtures/db/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "name": "Jane", 5 | "email": "jane@doe.org" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /spec/fixtures/test_db.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "name": "Jane", 5 | "email": "jane@doe.org", 6 | "roles": [ 7 | { 8 | "role_name": "Member" 9 | }, 10 | { 11 | "role_name": "Admin" 12 | } 13 | ] 14 | }, 15 | { 16 | "name": "Another", 17 | "email": "another@one.orb", 18 | "roles": [ 19 | { 20 | "role_name": "Member" 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /spec/integration/adapter_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe ROM::JSON do 4 | let(:configuration) { ROM::Configuration.new(:json, path) } 5 | let(:root) { Pathname(__FILE__).dirname.join('..') } 6 | let(:container) { ROM.container(configuration) } 7 | 8 | subject(:relations) { ROM.container(configuration).relations } 9 | 10 | context 'with single file' do 11 | let(:path) { "#{root}/fixtures/test_db.json" } 12 | 13 | let(:users_relation) do 14 | Class.new(ROM::JSON::Relation) do 15 | schema(:users) do 16 | attribute :name, ROM::Types::String 17 | attribute :email, ROM::Types::String 18 | attribute :roles, ROM::Types::Array 19 | end 20 | 21 | auto_struct true 22 | 23 | def by_name(name) 24 | restrict(name: name) 25 | end 26 | end 27 | end 28 | 29 | before { configuration.register_relation(users_relation) } 30 | 31 | it 'returns mapped struct' do 32 | jane = relations[:users].by_name('Jane').first 33 | 34 | expect(jane.name).to eql('Jane') 35 | expect(jane.email).to eql('jane@doe.org') 36 | expect(jane.roles). 37 | to eql([{ role_name: 'Member' }, { role_name: 'Admin' }]) 38 | end 39 | end 40 | 41 | context 'with muli-file setup' do 42 | let(:path) { "#{root}/fixtures/db" } 43 | 44 | let(:users_relation) do 45 | Class.new(ROM::JSON::Relation) do 46 | schema(:users) do 47 | attribute :name, ROM::Types::String 48 | attribute :email, ROM::Types::String 49 | end 50 | end 51 | end 52 | 53 | let(:tasks_relation) do 54 | Class.new(ROM::JSON::Relation) do 55 | schema(:tasks) do 56 | attribute :title, ROM::Types::String 57 | end 58 | end 59 | end 60 | 61 | before do 62 | [users_relation, tasks_relation]. 63 | each { |relation| configuration.register_relation(relation) } 64 | end 65 | 66 | it 'returns data from users.json file' do 67 | expect(relations[:users]). 68 | to match_array([{ name: 'Jane', email: 'jane@doe.org' }]) 69 | end 70 | 71 | it 'returns data from tasks.json file' do 72 | expect(relations[:tasks]).to match_array( 73 | [ 74 | { title: 'Task One' }, 75 | { title: 'Task Two' }, 76 | { title: 'Task Three' } 77 | ] 78 | ) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler' 4 | Bundler.setup 5 | 6 | require 'rom-json' 7 | 8 | if RUBY_ENGINE == 'rbx' 9 | require 'codeclimate-test-reporter' 10 | CodeClimate::TestReporter.start 11 | end 12 | -------------------------------------------------------------------------------- /spec/unit/dataset_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rom/lint/spec' 4 | 5 | describe ROM::JSON::Dataset do 6 | let(:data) { [{ id: 1 }, { id: 2 }] } 7 | let(:dataset) { ROM::JSON::Dataset.new(data) } 8 | 9 | it_behaves_like 'a rom enumerable dataset' 10 | 11 | it 'symbolizes keys' do 12 | dataset = ROM::JSON::Dataset.new(['foo' => 23]) 13 | expect(dataset.to_a).to eq([{ foo: 23 }]) 14 | end 15 | 16 | it 'symbolizes keys recursively' do 17 | dataset = ROM::JSON::Dataset.new(['foo' => { 'bar' => :baz }]) 18 | expect(dataset.to_a).to eq([foo: { bar: :baz }]) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/unit/repository_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe ROM::JSON::Gateway do 4 | let(:root) { Pathname(__FILE__).dirname.join('..') } 5 | 6 | it_behaves_like 'a rom gateway' do 7 | let(:identifier) { :json } 8 | let(:gateway) { ROM::JSON::Gateway } 9 | let(:uri) { "#{root}/fixtures/test_db.json" } 10 | end 11 | end 12 | --------------------------------------------------------------------------------