object_json_mapper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3c9d3a3c7d0b38d0cdc2fbc17841cca3ce028a7
4
+ data.tar.gz: 30cde2c2a57c26d300eec9564c138cc0c59bf226
5
+ SHA512:
6
+ metadata.gz: 018f71e9d07923bc2bd7c718cc0abd2353a7df1d34b9aadfbf7acedd0a03ae44db479308233266436df0769edddd88f2998f33240624c2f6d12123642131b270
7
+ data.tar.gz: ab609054b4a783bea7fec658dcdcd058cd9f98b3d3cf5ce1ebc93f09895f3148db56b4e85251117ddb4e0266cf2b2e01e3aa78f91a5a8d913deb0d3fed69f5e2
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --format documentation
3
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at novikov359@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in object_json_mapper.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Inspire
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,251 @@
1
+ # ObjectJSONMapper
2
+
3
+ [![CircleCI](https://circleci.com/gh/InspireNL/ObjectJSONMapper.svg?style=svg&circle-token=28455d7bc9acb59984023207070f1f4afdc60d15)](https://circleci.com/gh/InspireNL/ObjectJSONMapper)
4
+
5
+ * [Installation](#installation)
6
+ * [Usage](#usage)
7
+ * [Find](#find)
8
+ * [Where](#where)
9
+ * [Create](#create)
10
+ * [Update](#update)
11
+ * [Delete](#delete)
12
+ * [Attributes](#attributes)
13
+ * [Defaults](#defaults)
14
+ * [Coercion](#coercion)
15
+ * [Associations](#associations)
16
+ * [HasMany](#hasmany)
17
+ * [BelongsTo / HasOne](#belongsto--hasone)
18
+ * [Scope](#scope)
19
+ * [Pluck](#pluck)
20
+ * [None](#none)
21
+ * [Root](#root)
22
+ * [Configure](#configure)
23
+
24
+ ## Installation
25
+
26
+ Add this line to your application's Gemfile:
27
+
28
+ ```ruby
29
+ gem 'object_json_mapper'
30
+ ```
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install object_json_mapper
39
+
40
+ ## Usage
41
+
42
+ Set default url for your API in `config/initializers/object_json_mapper.rb`:
43
+
44
+ ```ruby
45
+ require 'object_json_mapper'
46
+
47
+ ObjectJSONMapper.configure do |config|
48
+ # Required
49
+ config.base_url = 'http://localhost:3000'
50
+
51
+ # Optional
52
+ config.headers = {
53
+ 'Authorization' => 'secret-token'
54
+ }
55
+ end
56
+ ```
57
+
58
+ Define class, e.g. `User`:
59
+
60
+ ```ruby
61
+ class User < ObjectJSONMapper::Base
62
+ end
63
+ ```
64
+
65
+ ### Find
66
+
67
+ ```ruby
68
+ User.find(1)
69
+ # => GET http://localhost:3000/users/1
70
+ ```
71
+
72
+ ### Where
73
+
74
+ ```ruby
75
+ User.where(id: 1)
76
+ # => GET http://localhost:3000/users?id=1
77
+
78
+ User.where(id: 1).where(name: 'Name')
79
+ # => GET http://localhost:3000/users?id=1&name=Name
80
+ ```
81
+
82
+ ### Create
83
+
84
+ ```ruby
85
+ User.create(name: 'Name')
86
+ # => POST http://localhost:3000/users
87
+ # => {
88
+ # => name: 'Name'
89
+ # => }
90
+ ```
91
+
92
+ ### Update
93
+
94
+ ```ruby
95
+ u = User.find(1)
96
+ u.update(name: 'Name')
97
+ # => PATCH http://localhost:3000/users/1
98
+ # => {
99
+ # => name: 'Name'
100
+ # => }
101
+ ```
102
+
103
+ ### Delete
104
+
105
+ ```ruby
106
+ u = User.find(1)
107
+ u.delete
108
+ # => DELETE http://localhost:3000/users/1
109
+ ```
110
+
111
+ ## Attributes
112
+
113
+ ### Defaults
114
+
115
+ Executes given `Proc` if value is `nil`.
116
+
117
+ ```ruby
118
+ class User < ObjectJSONMapper::Base
119
+ attribute :created_at, default: -> { DateTime.now }
120
+ end
121
+ ```
122
+
123
+ ### Coercion
124
+
125
+ Expects a object with `#call` interface to return modified value.
126
+
127
+ ```ruby
128
+ class User < ObjectJSONMapper::Base
129
+ attribute :created_at, type: Dry::Types['form.date_time']
130
+ end
131
+ ```
132
+
133
+ ## Associations
134
+
135
+ ### HasMany
136
+
137
+ Returns a `Relation` with model objects.
138
+
139
+ ```ruby
140
+ class User < ObjectJSONMapper::Base
141
+ has_many :posts
142
+ end
143
+
144
+ User.find(1).posts
145
+ # => GET http://localhost:3000/users/1/posts
146
+ ```
147
+
148
+ ### BelongsTo / HasOne
149
+
150
+ Returns a single model object.
151
+
152
+ ```ruby
153
+ class Post < ObjectJSONMapper::Base
154
+ belongs_to :user
155
+ has_one :image
156
+ end
157
+
158
+ Post.find(1).user
159
+ # => GET http://localhost:3000/posts/1/user
160
+
161
+ Post.find(1).image
162
+ # => GET http://localhost:3000/posts/1/image
163
+ ```
164
+
165
+ ## Scope
166
+
167
+ Defines a scope for current model and all it relations.
168
+ It's possible to chain `#where` methods and defined scopes in any order.
169
+
170
+ ```ruby
171
+ class User < ObjectJSONMapper::Base
172
+ scope :published, -> { where(published: true) }
173
+ scope :active, -> { where(active: true) }
174
+ end
175
+
176
+ User.published
177
+ # => GET http://localhost:3000/users?published=true
178
+
179
+ User.published.active
180
+ # => GET http://localhost:3000/users?published=true&active=true
181
+
182
+ User.published.where(active: true)
183
+ # => GET http://localhost:3000/users?published=true&active=true
184
+
185
+ User.where(active: true).published
186
+ # => GET http://localhost:3000/users?active=true&published=true
187
+ ```
188
+
189
+ ## Pluck
190
+
191
+ ```ruby
192
+ User.where(published: true).pluck(:id, :email)
193
+ # => GET http://localhost:3000/users?published=true
194
+ # => [[1, 'first@example.com', [2, 'second@example.com']]
195
+ ```
196
+
197
+ ## None
198
+
199
+ Returns a chainable relation without records, wouldn't make any queries.
200
+
201
+ ```ruby
202
+ User.none
203
+ # => []
204
+
205
+ User.where(id: 1).none
206
+ # => []
207
+
208
+ User.none.where(id: 1)
209
+ # => []
210
+ ```
211
+
212
+ ## Root
213
+
214
+ Allows to change resource path for model client.
215
+
216
+ ```ruby
217
+ User.root('people').where(name: 'Name')
218
+ # => GET http://localhost:3000/people?name=Name
219
+ ```
220
+
221
+ ## Configure
222
+
223
+ Available options:
224
+
225
+ * `root_url` - resource path for current model.
226
+
227
+ ```ruby
228
+ class User < ObjectJSONMapper::Base
229
+ configure do |c|
230
+ c.root_url = 'people'
231
+ end
232
+ end
233
+
234
+ User.all
235
+ # => GET http://localhost:3000/people
236
+
237
+ User.find(1)
238
+ # => GET http://localhost:3000/people/1
239
+
240
+ User.find(1).posts
241
+ # => GET http://localhost:3000/people/1
242
+ # => GET http://localhost:3000/people/1/posts
243
+ ```
244
+
245
+ ## License
246
+
247
+ ObjectJSONMapper is released under the [MIT License](https://github.com/InspireNL/ObjectJSONMapper/blob/master/LICENSE).
248
+
249
+ ## About
250
+
251
+ ObjectJSONMapper is maintained by [Inspire](https://inspire.nl).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'object_json_mapper'
5
+ require 'pry'
6
+ # require 'irb'
7
+
8
+ ObjectJSONMapper.configure do |config|
9
+ config.base_url = 'http://localhost:3000/v1/'
10
+ config.headers = {
11
+ 'Authorization-Consumer' => '65d064d8-4b52-4b4d-8f5b-acc36a21e3bf'
12
+ }
13
+ end
14
+
15
+ class User < ObjectJSONMapper::Base
16
+ attribute :id
17
+ attribute :email
18
+ attribute :password
19
+
20
+ has_many :subscriptions
21
+
22
+ scope :qwe, -> { where(id: 1) }
23
+
24
+ configure do |c|
25
+ c.root_url = 'children'
26
+ end
27
+ end
28
+
29
+ class Subscription < ObjectJSONMapper::Base
30
+ belongs_to :user
31
+ end
32
+
33
+ Pry.start
34
+ # IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,36 @@
1
+ module ObjectJSONMapper
2
+ module Associations
3
+ # Base class for association builders
4
+ #
5
+ # @abstract
6
+ class Association
7
+ attr_reader :name, :params
8
+
9
+ # @param name [Symbol]
10
+ # @param options [Hash]
11
+ # @option options [Object] :klass
12
+ # @option options [String] :endpoint
13
+ # @option options [Hash] :params
14
+ def initialize(name, options = {})
15
+ @name = name
16
+ @klass = options.fetch(:class_name, name)
17
+ @endpoint = options.fetch(:endpoint, name)
18
+ @params = options.fetch(:params, {})
19
+ end
20
+
21
+ def call(*)
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def klass
26
+ return @klass.to_s.classify.constantize if @klass.is_a?(Symbol)
27
+
28
+ @klass
29
+ end
30
+
31
+ def endpoint
32
+ @endpoint.to_s.underscore
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ module ObjectJSONMapper
2
+ module Associations
3
+ class HasMany < Association
4
+ # @param object [ObjectJSONMapper::Base]
5
+ # @return [ObjectJSONMapper::Relation]
6
+ def call(object)
7
+ klass.where.tap do |relation|
8
+ relation.path = object.client[endpoint].url
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module ObjectJSONMapper
2
+ module Associations
3
+ class HasOne < Association
4
+ # @param object [ObjectJSONMapper::Base]
5
+ # @return [ObjectJSONMapper::Base]
6
+ def call(object)
7
+ klass.persist(
8
+ HTTP.parse_json(object.client[endpoint].get.body)
9
+ )
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,25 @@
1
+ module ObjectJSONMapper
2
+ module Associations
3
+ # Collection of model associations
4
+ class Registry
5
+ include Enumerable
6
+
7
+ attr_reader :associations
8
+
9
+ delegate :[],
10
+ :[]=,
11
+ :<<,
12
+ to: :associations
13
+
14
+ def initialize
15
+ @associations = []
16
+ end
17
+
18
+ # @param name [Symbol]
19
+ # @return [ObjectJSONMapper::Associations::Association]
20
+ def find(name)
21
+ associations.find { |a| a.name == name }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,96 @@
1
+ module ObjectJSONMapper
2
+ module Associations
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ # rubocop:disable Style/PredicateName
9
+ #
10
+ # @param association_name [Symbol]
11
+ # @param class_name [Symbol]
12
+ # @return [ObjectJSONMapper::Relation<ObjectJSONMapper::Base>]
13
+ #
14
+ # @example Basic usage
15
+ # class User < ObjectJSONMapper::Base
16
+ # has_many :products
17
+ # end
18
+ #
19
+ # User.find(1).products
20
+ # # GET http://localhost:3000/v1/users/1/products
21
+ # # => #<ObjectJSONMapper::Relation @collection=[<Product>...]>
22
+ #
23
+ # @example With class_name
24
+ # class User < ObjectJSONMapper::Base
25
+ # has_many :things, class_name: :products
26
+ # end
27
+ #
28
+ # User.find(1).things
29
+ # # GET http://localhost:3000/v1/users/1/things
30
+ # # => #<ObjectJSONMapper::Relation @collection=[<Product>...]>
31
+ #
32
+ # @example With endpoint
33
+ # class User < ObjectJSONMapper::Base
34
+ # has_many :products, endpoint: :things
35
+ # end
36
+ #
37
+ # User.find(1).products
38
+ # # GET http://localhost:3000/v1/users/1/things
39
+ # # => #<ObjectJSONMapper::Relation @collection=[<Product>...]>
40
+ #
41
+ # @example With params
42
+ # class User < ObjectJSONMapper::Base
43
+ # has_many :products, params: { status: :active }
44
+ # end
45
+ #
46
+ # User.find(1).products
47
+ # # GET http://localhost:3000/v1/users/1/products?status=active
48
+ # # => #<ObjectJSONMapper::Relation @collection=[<Product>...]>
49
+ def has_many(name, options = {})
50
+ associations << HasMany.new(name, options)
51
+
52
+ define_method(name) do |reload = false|
53
+ cache_name = :"@#{__method__}"
54
+
55
+ if instance_variable_defined?(cache_name) && reload == false
56
+ return instance_variable_get(cache_name)
57
+ end
58
+
59
+ instance_variable_set(
60
+ cache_name,
61
+ self.class.associations.find(__method__).call(self)
62
+ )
63
+ end
64
+ end
65
+
66
+ # @param association_name [Symbol]
67
+ # @return [ObjectJSONMapper::Base]
68
+ #
69
+ # @example Basic usage
70
+ # class User < ObjectJSONMapper::Base
71
+ # has_one :profile
72
+ # end
73
+ #
74
+ # User.find(1).profile
75
+ # # GET http://localhost:3000/v1/users/1/profile
76
+ # # => #<ObjectJSONMapper::Base @attributes={...}>
77
+ def has_one(name, options = {})
78
+ associations << HasOne.new(name, options)
79
+
80
+ define_method(name) do |reload = false|
81
+ cache_name = :"@#{__method__}"
82
+
83
+ if instance_variable_defined?(cache_name) && reload == false
84
+ return instance_variable_get(cache_name)
85
+ end
86
+
87
+ instance_variable_set(
88
+ cache_name,
89
+ self.class.associations.find(__method__).call(self)
90
+ )
91
+ end
92
+ end
93
+ alias belongs_to has_one
94
+ end
95
+ end
96
+ end