roaster 0.0.1 → 0.0.2

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.
@@ -0,0 +1,73 @@
1
+ module Roaster
2
+ class Request
3
+
4
+ ALLOWED_OPERATIONS = [:create, :read, :update, :delete]
5
+
6
+ #TODO: Move this elsewhere (factory)
7
+ def self.mapping_class_from_target(target)
8
+ if target.relationship_name
9
+ mapping_class_from_name(target.relationship_name)
10
+ else
11
+ mapping_class_from_name(target.resource_name)
12
+ end
13
+ end
14
+
15
+ def self.mapping_class_from_name(name)
16
+ "#{name.to_s.singularize}_mapping".classify.constantize
17
+ end
18
+
19
+ def initialize(operation, target, resource, params, opts = {})
20
+ # :create, :read, :update, :delete
21
+ unless ALLOWED_OPERATIONS.include?(operation)
22
+ raise "#{operation} is not a valid operation."
23
+ end
24
+ @operation = operation
25
+ @resource = resource
26
+ @mapping_class = opts[:mapping_class] || self.class.mapping_class_from_target(target)
27
+ @query = Roaster::Query.new(@operation, target, @mapping_class, params)
28
+ #TODO: Oh snap this is confusing
29
+ @document = opts[:document]
30
+ end
31
+
32
+ def execute
33
+ case @operation
34
+ when :create
35
+ #TODO: - IDEA - Maybe make `new` return a fake 'relationship' object so a relationship special case wouldn't be needed
36
+ if @query.target.relationship_name.nil?
37
+ obj = @resource.new(@query)
38
+ parse(obj, @document)
39
+ @resource.save(obj)
40
+ else
41
+ @resource.create_relationship(@query, @document)
42
+ end
43
+ when :read
44
+ res = @resource.query(@query)
45
+ represent(res).to_hash
46
+ when :update
47
+ obj = @resource.find(@query)
48
+ links = @document.delete('links')
49
+ @resource.update_relationships(@query, links) if links
50
+ parse(obj, @document)
51
+ @resource.save(obj)
52
+ when :delete
53
+ @resource.delete(@query)
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def parse(object, data)
60
+ rp = @mapping_class.new(object)
61
+ rp.from_hash(data)
62
+ end
63
+
64
+ def represent(data)
65
+ if data.respond_to?(:each)
66
+ @mapping_class.for_collection.prepare(data)
67
+ else
68
+ @mapping_class.prepare(data)
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,41 @@
1
+ module Roaster
2
+
3
+ class Resource
4
+
5
+ def initialize(adapter_class, opts = {})
6
+ @adapter = adapter_class.new
7
+ @model_class = opts[:model_class]
8
+ end
9
+
10
+ def new(query)
11
+ @adapter.new(query)
12
+ end
13
+
14
+ def save(model)
15
+ @adapter.save(model)
16
+ model
17
+ end
18
+
19
+ def delete(query)
20
+ @adapter.delete(query)
21
+ end
22
+
23
+ def create_relationship(query, document)
24
+ @adapter.create_relationship(query, document)
25
+ end
26
+
27
+ def update_relationships(query, document)
28
+ @adapter.update_relationship(query, document)
29
+ end
30
+
31
+ def find(query)
32
+ @adapter.find(query, model_class: @model_class)
33
+ end
34
+
35
+ def query(query)
36
+ @adapter.read(query, model_class: @model_class)
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,3 @@
1
+ module Roaster
2
+ VERSION = '0.0.2'
3
+ end
data/roaster.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/roaster/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'roaster'
6
+ gem.authors = ['Nicolas Albeza', 'Jérémy Lecerf']
7
+ gem.email = ['n.albeza@gmail.com', 'redpist.com@gmail.com']
8
+ gem.description = %q{Model/JSONAPI mapping}
9
+ gem.summary = %q{Expose your models through a JSONAPI API with a simple mapping}
10
+ gem.homepage = 'https://github.com/pause/roaster'
11
+ gem.license = 'MIT'
12
+
13
+ gem.files = `git ls-files`.split($\)
14
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
15
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
16
+ gem.require_paths = ['lib']
17
+ gem.version = Roaster::VERSION
18
+
19
+ gem.add_runtime_dependency "representable", "~> 2.0"
20
+ gem.add_runtime_dependency "activerecord", "~> 4.1"
21
+ gem.add_runtime_dependency "activesupport", "~> 4.1"
22
+
23
+ gem.add_development_dependency 'minitest', '~> 5.1'
24
+ gem.add_development_dependency 'rake', '~> 10.3'
25
+ gem.add_development_dependency 'factory_girl', '~> 4.4'
26
+ gem.add_development_dependency 'sqlite3', '~> 1.3'
27
+ gem.add_development_dependency 'database_cleaner', '~> 1.3'
28
+ gem.add_development_dependency 'byebug', '~> 3.4'
29
+
30
+ end
@@ -0,0 +1,124 @@
1
+ require 'minitest/autorun'
2
+
3
+ require 'roaster/query'
4
+ require 'roaster/adapters/active_record'
5
+
6
+ require_relative 'test_helper'
7
+ require_relative 'models/album'
8
+ #require_relative 'models/namespaced/model'
9
+
10
+ class ActiveRecordAdapterTest < MiniTest::Test
11
+
12
+ def setup
13
+ super
14
+ @adapter = Roaster::Adapters::ActiveRecord.new
15
+ @album_mapping = AlbumMapping
16
+ @albums_target = Roaster::Query::Target.new(:albums)
17
+ end
18
+
19
+ def test_interface
20
+ assert @adapter.respond_to?(:new)
21
+ assert @adapter.respond_to?(:save)
22
+ assert @adapter.respond_to?(:find)
23
+ assert @adapter.respond_to?(:read)
24
+ assert @adapter.respond_to?(:delete)
25
+ end
26
+
27
+ def test_mapping_class_from_resource_name
28
+ mc = Roaster::Adapters::ActiveRecord.model_class_from_resource_name(:albums)
29
+ assert_equal Album, mc
30
+ end
31
+
32
+ def test_simple_model_for
33
+ model = @adapter.send(:model_for, :albums)
34
+ assert_equal model, Album
35
+ end
36
+
37
+ def test_custom_model_for
38
+ model = @adapter.send(:model_for, Album)
39
+ assert_equal model, Album
40
+ end
41
+
42
+ def test_new
43
+ query = build_query(:create)
44
+ model_instance = @adapter.new(query)
45
+ assert_kind_of Album, model_instance
46
+ end
47
+
48
+ def test_save
49
+ title = 'Serial Smokers'
50
+ query = build_query(:create)
51
+ model_instance = @adapter.new(query)
52
+ model_instance.send(:title=, title)
53
+ @adapter.save(model_instance)
54
+
55
+ new_album = Album.last
56
+ assert_equal model_instance, new_album
57
+ assert_equal title, new_album.title
58
+ end
59
+
60
+ def test_find
61
+ ref_album = FactoryGirl.create(:album)
62
+ target = Roaster::Query::Target.new(:albums, ref_album.id)
63
+ #TODO: API problems (why do i need a full query here ?!)!
64
+ query = build_query(:update, target)
65
+ album = @adapter.find(query)
66
+
67
+ assert_equal album, ref_album
68
+ end
69
+
70
+ private
71
+
72
+ def call_adapter_method(method,
73
+ target,
74
+ params = {})
75
+ query = Roaster::Query.new(method, target, @mapping, params)
76
+ @adapter.send(method, query)
77
+ end
78
+
79
+ def build_query(operation, target = @albums_target, mapping = @album_mapping, params = {})
80
+ Roaster::Query.new(:create, @albums_target, @album_mapping, params)
81
+ end
82
+
83
+ def test_new
84
+ query = build_query(:create)
85
+ model_instance = @adapter.new(query)
86
+ assert_kind_of Album, model_instance
87
+ end
88
+
89
+ def test_save
90
+ title = 'Serial Smokers'
91
+ query = build_query(:create)
92
+ model_instance = @adapter.new(query)
93
+ model_instance.send(:title=, title)
94
+ @adapter.save(model_instance)
95
+
96
+ new_album = Album.last
97
+ assert_equal model_instance, new_album
98
+ assert_equal title, new_album.title
99
+ end
100
+
101
+ def test_find
102
+ ref_album = FactoryGirl.create(:album)
103
+ target = Roaster::Query::Target.new(:albums, ref_album.id)
104
+ #TODO: API problems (why do i need a full query here ?!)!
105
+ query = build_query(:update, target)
106
+ album = @adapter.find(query)
107
+
108
+ assert_equal album, ref_album
109
+ end
110
+
111
+ private
112
+
113
+ def call_adapter_method(method,
114
+ target,
115
+ params = {})
116
+ query = Roaster::Query.new(method, target, @mapping, params)
117
+ @adapter.send(method, query)
118
+ end
119
+
120
+ def build_query(operation, target = @albums_target, mapping = @album_mapping, params = {})
121
+ Roaster::Query.new(:create, @albums_target, @album_mapping, params)
122
+ end
123
+
124
+ end
@@ -0,0 +1,10 @@
1
+ require 'minitest/autorun'
2
+
3
+ require 'roaster/decorator'
4
+
5
+ require_relative 'test_helper'
6
+
7
+ class QueryTest < MiniTest::Test
8
+
9
+
10
+ end
@@ -0,0 +1,25 @@
1
+ require_relative './band'
2
+
3
+ FactoryGirl.define do
4
+ factory :album do
5
+ title 'Album Title'
6
+ band
7
+
8
+ factory :pink_floyd_albums do
9
+ association :band, factory: :pink_floyd
10
+
11
+ factory :animals_album do
12
+ title 'Animals'
13
+ end
14
+
15
+ factory :the_wall_album do
16
+ title 'The Wall'
17
+ end
18
+
19
+ factory :meddle_album do
20
+ title 'Meddle'
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ FactoryGirl.define do
2
+ factory :band do
3
+ name 'Band Name'
4
+
5
+ factory :pink_floyd do
6
+ name 'Pink Floyd'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ FactoryGirl.define do
2
+ factory :track do
3
+ title 'Track Title'
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ require 'roaster/json_api'
2
+
3
+ class AlbumMapping < Roaster::JsonApi::Mapping
4
+
5
+ property :title
6
+ #property :created_at
7
+
8
+ # TODO: auto include included mapping
9
+ # Aiming: Nested sparse fieldsets authorizations and default behaviour for sort
10
+ can_include :band, :tracks
11
+
12
+ can_filter_by :band, :title
13
+
14
+ can_sort_by :band, :title, :created_at, band: [:name]
15
+
16
+ # resource_name 'test_lautre'
17
+
18
+ collection_representer class: Album
19
+ end
@@ -0,0 +1,6 @@
1
+ require 'roaster/json_api'
2
+
3
+ class BandMapping < Roaster::JsonApi::Mapping
4
+
5
+ property :name
6
+ end
@@ -0,0 +1,8 @@
1
+ require 'roaster/json_api'
2
+
3
+ class TrackMapping < Roaster::JsonApi::Mapping
4
+
5
+ property :title
6
+
7
+ end
8
+
@@ -0,0 +1,14 @@
1
+ require 'active_model'
2
+ require 'active_record'
3
+
4
+ ActiveRecord::Migration.class_eval do
5
+ create_table :albums do |t|
6
+ t.string :title
7
+ t.belongs_to :band
8
+ end
9
+ end
10
+
11
+ class Album < ActiveRecord::Base
12
+ belongs_to :band
13
+ has_many :tracks
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_model'
2
+ require 'active_record'
3
+
4
+ ActiveRecord::Migration.class_eval do
5
+ create_table :bands do |t|
6
+ t.string :name
7
+ end
8
+ end
9
+
10
+ class Band < ActiveRecord::Base
11
+ end
@@ -0,0 +1,4 @@
1
+ module Namespaced
2
+ class Model < ActiveRecord::Base
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_model'
2
+ require 'active_record'
3
+
4
+ ActiveRecord::Migration.class_eval do
5
+ create_table :tracks do |t|
6
+ t.string :title
7
+ t.belongs_to :album
8
+ end
9
+ end
10
+
11
+ class Track < ActiveRecord::Base
12
+ belongs_to :album
13
+ end
@@ -0,0 +1,29 @@
1
+ require 'minitest/autorun'
2
+
3
+ require 'roaster/query'
4
+
5
+ require_relative 'test_helper'
6
+
7
+ class QueryTargetTest < MiniTest::Test
8
+
9
+ def setup
10
+ super
11
+ end
12
+
13
+ def test_defaults
14
+ target = Roaster::Query::Target.new(:albums)
15
+ assert_equal target.resource_name, :albums
16
+ assert_equal target.resource_ids, []
17
+ assert_equal target.relationship_name, nil
18
+ assert_equal target.relationship_ids, []
19
+ end
20
+
21
+ def test_init
22
+ target = Roaster::Query::Target.new(:albums, 1, :tracks, [2, 3])
23
+ assert_equal target.resource_name, :albums
24
+ assert_equal target.resource_ids, [1]
25
+ assert_equal target.relationship_name, :tracks
26
+ assert_equal target.relationship_ids, [2, 3]
27
+ end
28
+
29
+ end