resourced 0.0.1.beta1
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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +9 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +75 -0
- data/Rakefile +7 -0
- data/lib/resourced.rb +71 -0
- data/lib/resourced/active_record.rb +36 -0
- data/lib/resourced/active_record/actions.rb +40 -0
- data/lib/resourced/active_record/meta.rb +15 -0
- data/lib/resourced/active_record/proxy.rb +35 -0
- data/lib/resourced/finders.rb +78 -0
- data/lib/resourced/params.rb +179 -0
- data/lib/resourced/version.rb +3 -0
- data/resourced.gemspec +21 -0
- data/spec/resourced/adapters/active_record_spec.rb +103 -0
- data/spec/resourced/finders_spec.rb +81 -0
- data/spec/resourced/params_spec.rb +89 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/active_record.rb +22 -0
- metadata +103 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Andrey Savchenko
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Resourced
|
2
|
+
|
3
|
+
[](https://travis-ci.org/Ptico/resourced)
|
4
|
+
|
5
|
+
Resourced adds a missing layer between model and controller.
|
6
|
+
It takes all parameter- and scope-related jobs from the model and makes your model thin. Here is example:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
class PostResource
|
10
|
+
include Resourced::ActiveRecord
|
11
|
+
|
12
|
+
model Post
|
13
|
+
key :id
|
14
|
+
|
15
|
+
params do
|
16
|
+
allow :title, :body, :tags
|
17
|
+
allow :category, if: -> { scope.admin? }
|
18
|
+
end
|
19
|
+
|
20
|
+
finders do
|
21
|
+
restrict :search, if: -> { scope.guest? }
|
22
|
+
|
23
|
+
finder :title do |val|
|
24
|
+
chain.where(title: val)
|
25
|
+
end
|
26
|
+
|
27
|
+
finder :search do |val|
|
28
|
+
chain.where(t[:body].matches("%#{val}%"))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
Now you can do following:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
before_filter do
|
38
|
+
posts = PostResource.new(params, current_user)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Let params: { title: "My first post", body: "Lorem ipsum" }
|
42
|
+
post = posts.build
|
43
|
+
post # => #<Post id: nil, title: "My first post", body: "Lorem ipsum", category: nil>
|
44
|
+
|
45
|
+
# Let params: { id: 2, title: "My awesome post" }
|
46
|
+
posts.update! # Will update title for post with id 2
|
47
|
+
posts.first # Will find first post with title "My awesome post"
|
48
|
+
|
49
|
+
# Let params: { search: "Atlas Shrugged" }
|
50
|
+
posts.all # Will return all posts which contains "Atlas Shrugged" unless current user is guest
|
51
|
+
```
|
52
|
+
|
53
|
+
## Installation
|
54
|
+
|
55
|
+
Add this line to your application's Gemfile:
|
56
|
+
|
57
|
+
gem 'resourced'
|
58
|
+
|
59
|
+
And then execute:
|
60
|
+
|
61
|
+
$ bundle
|
62
|
+
|
63
|
+
Or install it yourself as:
|
64
|
+
|
65
|
+
$ gem install resourced
|
66
|
+
|
67
|
+
## Usage
|
68
|
+
|
69
|
+
## Contributing
|
70
|
+
|
71
|
+
1. Fork it
|
72
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
73
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
74
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
75
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/resourced.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "resourced/version"
|
2
|
+
require "resourced/params"
|
3
|
+
require "resourced/finders"
|
4
|
+
|
5
|
+
module Resourced
|
6
|
+
module Facade
|
7
|
+
module InstanceMethods
|
8
|
+
def initialize(params, scope)
|
9
|
+
@scope = scope
|
10
|
+
@model = self.class.instance_variable_get(:@model)
|
11
|
+
@key = self.class.instance_variable_get(:@key)
|
12
|
+
@chain = @model
|
13
|
+
super
|
14
|
+
@body = @params.keep_if { |k, v| attribute_names.include?(k) }
|
15
|
+
end
|
16
|
+
attr_accessor :params, :scope
|
17
|
+
attr_reader :model, :chain, :key
|
18
|
+
|
19
|
+
##
|
20
|
+
# Run external code in context of facade
|
21
|
+
#
|
22
|
+
# Examples:
|
23
|
+
#
|
24
|
+
# resource = UserResource.new(params, scope)
|
25
|
+
# subj = "john"
|
26
|
+
# resource.context do
|
27
|
+
# chain.where(name: subj)
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
def context(&block)
|
31
|
+
if block_given?
|
32
|
+
@chain = self.instance_eval(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
##
|
41
|
+
# Set or get model class
|
42
|
+
#
|
43
|
+
def model(model_class=nil)
|
44
|
+
model_class ? @model = model_class : @model
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Duplicate facade and set another model class
|
49
|
+
#
|
50
|
+
def [](model_class)
|
51
|
+
klass = self.dup
|
52
|
+
klass.instance_variable_set(:@model, model_class)
|
53
|
+
klass
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Set primary key
|
58
|
+
#
|
59
|
+
def key(key_name=nil)
|
60
|
+
key_name ? @key = key_name.to_sym : @key
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.included(base)
|
65
|
+
base.send(:include, Resourced::Params)
|
66
|
+
base.send(:include, Resourced::Finders)
|
67
|
+
base.send(:include, InstanceMethods)
|
68
|
+
base.extend ClassMethods
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "resourced"
|
2
|
+
require "resourced/active_record/meta"
|
3
|
+
require "resourced/active_record/proxy"
|
4
|
+
require "resourced/active_record/actions"
|
5
|
+
|
6
|
+
module Resourced
|
7
|
+
module ActiveRecord
|
8
|
+
|
9
|
+
module Helpers
|
10
|
+
def t
|
11
|
+
@table ||= model.arel_table
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def key(key_name=nil)
|
17
|
+
super
|
18
|
+
key_name = key_name.to_sym
|
19
|
+
finders do
|
20
|
+
finder key_name do |v|
|
21
|
+
chain.where(key_name => v)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.send(:include, Resourced::Facade)
|
29
|
+
base.send(:include, Meta)
|
30
|
+
base.send(:include, Proxy)
|
31
|
+
base.send(:include, Actions)
|
32
|
+
base.extend ClassMethods
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Resourced
|
2
|
+
module ActiveRecord
|
3
|
+
|
4
|
+
module Actions
|
5
|
+
def build
|
6
|
+
model.new(@body)
|
7
|
+
end
|
8
|
+
|
9
|
+
def update
|
10
|
+
body = @body.reject { |k,_| k == :id }
|
11
|
+
|
12
|
+
collection = if params[key]
|
13
|
+
[model.find(params[key])]
|
14
|
+
else
|
15
|
+
all
|
16
|
+
end
|
17
|
+
|
18
|
+
collection.map do |obj|
|
19
|
+
obj.assign_attributes(body)
|
20
|
+
obj
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def update!
|
25
|
+
body = @body.reject { |k,_| k == :id }
|
26
|
+
|
27
|
+
collection = if params[key]
|
28
|
+
[model.find(params[key])]
|
29
|
+
else
|
30
|
+
all
|
31
|
+
end
|
32
|
+
|
33
|
+
collection.map do |obj|
|
34
|
+
obj.update_attributes(body)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Resourced
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
|
5
|
+
module Proxy
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def first
|
9
|
+
apply_finders.chain.first
|
10
|
+
end
|
11
|
+
|
12
|
+
def last
|
13
|
+
apply_finders.chain.last
|
14
|
+
end
|
15
|
+
|
16
|
+
def all
|
17
|
+
apply_finders.chain.all
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
all.each(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def as_json(*args)
|
25
|
+
all.as_json(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_json(*args)
|
29
|
+
all.to_json(*args)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "resourced/params"
|
2
|
+
|
3
|
+
module Resourced
|
4
|
+
module Finders
|
5
|
+
class Finders < Resourced::Params::RuleSet
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
@finders = {}
|
9
|
+
end
|
10
|
+
attr_reader :finders
|
11
|
+
|
12
|
+
##
|
13
|
+
# Add finder
|
14
|
+
#
|
15
|
+
# Params:
|
16
|
+
# - name {Symbol} Finder key
|
17
|
+
# - options {Hash} Finder options (optional, default: {})
|
18
|
+
# - default Default value
|
19
|
+
# - if {Proc|Symbol} Condition for finder, should return Boolean
|
20
|
+
#
|
21
|
+
# Examples:
|
22
|
+
#
|
23
|
+
# finder :limit, default: 20 do |val|
|
24
|
+
# chain.limit(val)
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# Yields: Block with finder/filtration logic with a given parameter value
|
28
|
+
#
|
29
|
+
def finder(name, options={}, &block)
|
30
|
+
name = name.to_sym
|
31
|
+
|
32
|
+
allow(name, options)
|
33
|
+
@finders[name] = block
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
def initialize(params, scope)
|
41
|
+
super
|
42
|
+
@finders_obj = self.class.instance_variable_get(:@_finders_obj)
|
43
|
+
@finders = @finders_obj.sanitize_params(chain, params)
|
44
|
+
|
45
|
+
defaults = self.class.instance_variable_get(:@_default_finders)
|
46
|
+
defaults.each do |finder|
|
47
|
+
context(&finder)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def apply_finders
|
52
|
+
@finders.each_pair do |key, value|
|
53
|
+
@chain = self.instance_exec(value, &@finders_obj.finders[key.to_sym])
|
54
|
+
end
|
55
|
+
|
56
|
+
return self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module ClassMethods
|
61
|
+
def finders(&block)
|
62
|
+
@_finders_obj ||= Finders.new
|
63
|
+
@_finders_obj.instance_eval(&block)
|
64
|
+
end
|
65
|
+
attr_reader :_finders_obj
|
66
|
+
|
67
|
+
def default_finder(&block)
|
68
|
+
@_default_finders << block if block_given?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.included(base)
|
73
|
+
base.instance_variable_set(:@_default_finders, [])
|
74
|
+
base.extend ClassMethods
|
75
|
+
base.send(:include, InstanceMethods)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require "active_support/core_ext/array"
|
2
|
+
|
3
|
+
module Resourced
|
4
|
+
module Params
|
5
|
+
|
6
|
+
module InstanceMethods
|
7
|
+
def initialize(params, scope)
|
8
|
+
set(params)
|
9
|
+
end
|
10
|
+
|
11
|
+
##
|
12
|
+
# Set additional params
|
13
|
+
#
|
14
|
+
# Params:
|
15
|
+
# - params {Hash} List of params to be assigned
|
16
|
+
#
|
17
|
+
# Examples:
|
18
|
+
#
|
19
|
+
# resource = UserResource.new(params, scope)
|
20
|
+
# resource.set(role: "guest")
|
21
|
+
#
|
22
|
+
def set(params={})
|
23
|
+
sanitized = self.class._params_obj.sanitize_params(self, params)
|
24
|
+
|
25
|
+
if @params
|
26
|
+
@params.merge(sanitized)
|
27
|
+
else
|
28
|
+
@params = sanitized
|
29
|
+
end
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Erase existing params
|
36
|
+
#
|
37
|
+
# Params:
|
38
|
+
# - params {Hash} List of param keys to be erased
|
39
|
+
#
|
40
|
+
# Examples:
|
41
|
+
#
|
42
|
+
# resource = UserResource.new(params, scope)
|
43
|
+
# resource.erase(:password, :auth_token)
|
44
|
+
#
|
45
|
+
def erase(*keys)
|
46
|
+
keys.each do |key|
|
47
|
+
@params.delete(key.to_sym)
|
48
|
+
end
|
49
|
+
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
def params
|
56
|
+
@params
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class RuleSet
|
61
|
+
def initialize
|
62
|
+
@defaults = {}
|
63
|
+
@conditional_allows = []
|
64
|
+
@unconditional_allows = []
|
65
|
+
@conditional_restricts = []
|
66
|
+
@unconditional_restricts = []
|
67
|
+
end
|
68
|
+
attr_reader :defaults
|
69
|
+
|
70
|
+
##
|
71
|
+
# Allow field(s) to be assigned
|
72
|
+
#
|
73
|
+
# Options:
|
74
|
+
# - default Default value
|
75
|
+
# - if {Proc} Condition for allowing, should return Boolean
|
76
|
+
#
|
77
|
+
# Examples:
|
78
|
+
#
|
79
|
+
# allow :name, :email, if: -> { scope == :admin }
|
80
|
+
# allow :role, default: "guest"
|
81
|
+
#
|
82
|
+
def allow(*fields)
|
83
|
+
opts = fields.extract_options! # AS
|
84
|
+
|
85
|
+
if opts[:if]
|
86
|
+
@conditional_allows << ConditionalGroup.new(opts[:if], fields)
|
87
|
+
else
|
88
|
+
@unconditional_allows += fields
|
89
|
+
end
|
90
|
+
|
91
|
+
if opts[:default]
|
92
|
+
fields.each do |field|
|
93
|
+
@defaults[field] = opts[:default]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Restrict allowed fields
|
102
|
+
#
|
103
|
+
# Options:
|
104
|
+
# - if {Proc} Condition for restriction, should return Boolean
|
105
|
+
#
|
106
|
+
# Examples:
|
107
|
+
#
|
108
|
+
# restrict :role, if: -> { scope !== :admin }
|
109
|
+
#
|
110
|
+
def restrict(*fields)
|
111
|
+
opts = fields.extract_options! # AS
|
112
|
+
|
113
|
+
if opts[:if]
|
114
|
+
@conditional_restricts << ConditionalGroup.new(opts[:if], fields)
|
115
|
+
else
|
116
|
+
@unconditional_restricts += fields
|
117
|
+
end
|
118
|
+
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
def sanitize_params(context, params)
|
123
|
+
allowed_params = @unconditional_allows
|
124
|
+
|
125
|
+
@conditional_allows.each do |cond|
|
126
|
+
if cond.test(context)
|
127
|
+
allowed_params += cond.fields
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
allowed_params.uniq!
|
132
|
+
|
133
|
+
allowed_params -= @unconditional_restricts
|
134
|
+
|
135
|
+
@conditional_restricts.each do |cond|
|
136
|
+
if cond.test(context)
|
137
|
+
allowed_params -= cond.fields
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
@defaults.merge(params).symbolize_keys.keep_if { |k, v| allowed_params.include?(k) } # AS
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class ConditionalGroup
|
146
|
+
def initialize(condition, fields)
|
147
|
+
@condition = case condition
|
148
|
+
when Symbol, String
|
149
|
+
lambda { send(condition.to_sym) }
|
150
|
+
when Proc
|
151
|
+
condition
|
152
|
+
end
|
153
|
+
|
154
|
+
@fields = fields
|
155
|
+
end
|
156
|
+
|
157
|
+
attr_reader :fields
|
158
|
+
|
159
|
+
def test(context)
|
160
|
+
!!context.instance_exec(&@condition)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
module ClassMethods
|
165
|
+
def params(&block)
|
166
|
+
@_params_obj ||= RuleSet.new
|
167
|
+
@_params_obj.instance_eval(&block)
|
168
|
+
end
|
169
|
+
|
170
|
+
attr_reader :_params_obj
|
171
|
+
end
|
172
|
+
|
173
|
+
include InstanceMethods
|
174
|
+
|
175
|
+
def self.included(base)
|
176
|
+
base.extend ClassMethods
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
data/resourced.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'resourced/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "resourced"
|
8
|
+
gem.version = Resourced::VERSION
|
9
|
+
gem.authors = ["Andrey Savchenko"]
|
10
|
+
gem.email = ["andrey@aejis.eu"]
|
11
|
+
gem.description = %q{WIP - not for production}
|
12
|
+
gem.summary = %q{Missing layer between model and controller}
|
13
|
+
gem.homepage = "https://github.com/Ptico/resourced"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
|
19
|
+
gem.add_dependency "active_support"
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/active_record"
|
3
|
+
|
4
|
+
require "active_record"
|
5
|
+
require "resourced/active_record"
|
6
|
+
|
7
|
+
describe Resourced::ActiveRecord do
|
8
|
+
before :each do
|
9
|
+
setup_db
|
10
|
+
end
|
11
|
+
|
12
|
+
after :each do
|
13
|
+
teardown_db
|
14
|
+
end
|
15
|
+
|
16
|
+
class User < ActiveRecord::Base; end;
|
17
|
+
|
18
|
+
let(:klass) do
|
19
|
+
Class.new do
|
20
|
+
include Resourced::ActiveRecord
|
21
|
+
|
22
|
+
model User
|
23
|
+
key :id
|
24
|
+
|
25
|
+
params do
|
26
|
+
allow :name, :email
|
27
|
+
allow :role, :if => lambda { scope == "admin" }
|
28
|
+
end
|
29
|
+
|
30
|
+
finders do
|
31
|
+
finder :name do |v|
|
32
|
+
chain.where(:name => v)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "Create" do
|
39
|
+
it "should filter params" do
|
40
|
+
inst = klass.new({ :name => "Peter", :email => "peter@test.com", :role => "admin" }, "")
|
41
|
+
attrs = inst.build.attributes
|
42
|
+
|
43
|
+
attrs["name"].should eq("Peter")
|
44
|
+
attrs["role"].should be_nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Read" do
|
49
|
+
before :each do
|
50
|
+
add_user 1, "Homer", "homer@test.com", "admin"
|
51
|
+
add_user 2, "Bart", "bart@test.com", "user"
|
52
|
+
add_user 3, "Lisa", "lisa@test.com", "user"
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should find by pkey" do
|
56
|
+
inst = klass.new({ :id => 3 }, "")
|
57
|
+
|
58
|
+
inst.first.name.should eq("Lisa")
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should find with finder" do
|
62
|
+
inst = klass.new({ :name => "Bart" }, "")
|
63
|
+
|
64
|
+
inst.first.email.should eq("bart@test.com")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should iterate over results with #map" do
|
68
|
+
inst = klass.new({}, "")
|
69
|
+
|
70
|
+
result = inst.map do |user|
|
71
|
+
user.name
|
72
|
+
end
|
73
|
+
|
74
|
+
result.should eq(%w(Homer Bart Lisa))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "Update" do
|
79
|
+
before :each do
|
80
|
+
add_user 1, "Homer", "homer@test.com", "admin"
|
81
|
+
add_user 2, "Bart", "bart@test.com", "user"
|
82
|
+
add_user 3, "Lisa", "lisa@test.com", "user"
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should prepare collection to update" do
|
86
|
+
inst = klass.new({ :id => [2, 3], :role => "guest" }, "admin")
|
87
|
+
|
88
|
+
collection = inst.update
|
89
|
+
|
90
|
+
collection.map{ |u| u.role }.should eq(["guest", "guest"])
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should update the record immediatly" do
|
94
|
+
inst = klass.new({ :id => [2, 3], :role => "guest" }, "admin")
|
95
|
+
|
96
|
+
inst.update!
|
97
|
+
|
98
|
+
User.find(2).role.should eq("guest")
|
99
|
+
User.find(3).role.should eq("guest")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Resourced::Finders do
|
4
|
+
class FindersTestRelation
|
5
|
+
def initialize
|
6
|
+
@result = ""
|
7
|
+
end
|
8
|
+
attr_reader :result
|
9
|
+
|
10
|
+
def method_missing(name, value)
|
11
|
+
@result += "##{name}(#{value})"
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Workaround
|
17
|
+
class FinderSuper; def initialize(*args); end; end
|
18
|
+
|
19
|
+
let(:klass) {
|
20
|
+
Class.new(FinderSuper) do
|
21
|
+
include Resourced::Finders
|
22
|
+
|
23
|
+
def initialize(params={}, scope="guest")
|
24
|
+
super
|
25
|
+
@scope = scope
|
26
|
+
@chain = FindersTestRelation.new
|
27
|
+
end
|
28
|
+
attr_reader :chain
|
29
|
+
end
|
30
|
+
}
|
31
|
+
let(:inst) { klass.new(params) }
|
32
|
+
|
33
|
+
describe "Basic finder" do
|
34
|
+
before :each do
|
35
|
+
klass.finders do
|
36
|
+
finder :offset do |val|
|
37
|
+
chain.offset(val)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "with given corresponding parameters" do
|
43
|
+
let(:params) { {:offset => 2} }
|
44
|
+
|
45
|
+
it "should be called" do
|
46
|
+
inst.apply_finders.chain.result.should eq("#offset(2)")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "without corresponding parameters" do
|
51
|
+
let(:params) { {} }
|
52
|
+
|
53
|
+
it "should not be called" do
|
54
|
+
inst.apply_finders.chain.result.should eq("")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "Finder with default" do
|
60
|
+
before :each do
|
61
|
+
klass.finders do
|
62
|
+
finder :limit, :default => 10 do |val|
|
63
|
+
chain.limit(val)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "when finder not specified" do
|
69
|
+
let(:params) { {} }
|
70
|
+
|
71
|
+
it { inst.apply_finders.chain.result.should eq("#limit(10)") }
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when finder specified" do
|
75
|
+
let(:params) { {:limit => 20} }
|
76
|
+
|
77
|
+
it { inst.apply_finders.chain.result.should eq("#limit(20)") }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Resourced::Params do
|
4
|
+
class ParamsTest
|
5
|
+
include Resourced::Params
|
6
|
+
|
7
|
+
def initialize(params, scope)
|
8
|
+
@scope = scope
|
9
|
+
super
|
10
|
+
end
|
11
|
+
attr_reader :params
|
12
|
+
end
|
13
|
+
|
14
|
+
params = { :a => 1, :b => 2, :c => 3, :d => 4 }
|
15
|
+
|
16
|
+
describe "Unconditional allows" do
|
17
|
+
klass = ParamsTest.dup
|
18
|
+
klass.params do
|
19
|
+
allow :a, :b, :c
|
20
|
+
end
|
21
|
+
inst = klass.new(params, "admin")
|
22
|
+
|
23
|
+
it "should contain only allowed" do
|
24
|
+
inst.params.keys.should eq([:a, :b, :c])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "Conditional allows" do
|
29
|
+
klass = ParamsTest.dup
|
30
|
+
|
31
|
+
klass.params do
|
32
|
+
allow :a, :b, :c
|
33
|
+
allow :d, :if => lambda { @scope == "admin" }
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when condition matches" do
|
37
|
+
inst = klass.new(params, "admin")
|
38
|
+
|
39
|
+
it "should contain conditional param" do
|
40
|
+
inst.params.keys.should eq([:a, :b, :c, :d])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when condition not matches" do
|
45
|
+
inst = klass.new(params, "guest")
|
46
|
+
|
47
|
+
it "should not contain conditional param" do
|
48
|
+
inst.params.keys.should eq([:a, :b, :c])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "Unconditional restricts" do
|
54
|
+
klass = ParamsTest.dup
|
55
|
+
klass.params do
|
56
|
+
allow :a, :b, :c
|
57
|
+
restrict :c
|
58
|
+
end
|
59
|
+
inst = klass.new(params, "admin")
|
60
|
+
|
61
|
+
it "should not contain restricted" do
|
62
|
+
inst.params.keys.should eq([:a, :b])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "Conditional restricts" do
|
67
|
+
klass = ParamsTest.dup
|
68
|
+
klass.params do
|
69
|
+
allow :a, :b, :c
|
70
|
+
restrict :c, :if => lambda { @scope != "admin" }
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when condition matches" do
|
74
|
+
inst = klass.new(params, "guest")
|
75
|
+
|
76
|
+
it "should not contain conditional param" do
|
77
|
+
inst.params.keys.should eq([:a, :b])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when condition not matches" do
|
82
|
+
inst = klass.new(params, "admin")
|
83
|
+
|
84
|
+
it "should contain conditional param" do
|
85
|
+
inst.params.keys.should eq([:a, :b, :c])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "resourced"
|
2
|
+
|
3
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
4
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
5
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
6
|
+
# loaded once.
|
7
|
+
#
|
8
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
config.filter_run :focus
|
13
|
+
|
14
|
+
# Run specs in random order to surface order dependencies. If you find an
|
15
|
+
# order dependency and want to debug it, you can fix the order by providing
|
16
|
+
# the seed, which is printed after each run.
|
17
|
+
# --seed 1234
|
18
|
+
config.order = 'random'
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
def setup_db
|
2
|
+
FileUtils.rm("test.sqlite3") if File.exists?("test.sqlite3")
|
3
|
+
|
4
|
+
ActiveRecord::Migration.verbose = false
|
5
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "test.sqlite3")
|
6
|
+
|
7
|
+
ActiveRecord::Schema.define do
|
8
|
+
create_table :users, :force => true do |t|
|
9
|
+
t.string :name
|
10
|
+
t.string :email
|
11
|
+
t.string :role
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown_db
|
17
|
+
FileUtils.rm("test.sqlite3") if File.exists?("test.sqlite3")
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_user(id, name, email, role)
|
21
|
+
ActiveRecord::Base.connection.execute("INSERT INTO users (id, name, email, role) VALUES (#{id}, '#{name}', '#{email}', '#{role}')")
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resourced
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.beta1
|
5
|
+
prerelease: 6
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrey Savchenko
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-14 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: active_support
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: WIP - not for production
|
47
|
+
email:
|
48
|
+
- andrey@aejis.eu
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- .rspec
|
55
|
+
- .travis.yml
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE.txt
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- lib/resourced.rb
|
61
|
+
- lib/resourced/active_record.rb
|
62
|
+
- lib/resourced/active_record/actions.rb
|
63
|
+
- lib/resourced/active_record/meta.rb
|
64
|
+
- lib/resourced/active_record/proxy.rb
|
65
|
+
- lib/resourced/finders.rb
|
66
|
+
- lib/resourced/params.rb
|
67
|
+
- lib/resourced/version.rb
|
68
|
+
- resourced.gemspec
|
69
|
+
- spec/resourced/adapters/active_record_spec.rb
|
70
|
+
- spec/resourced/finders_spec.rb
|
71
|
+
- spec/resourced/params_spec.rb
|
72
|
+
- spec/spec_helper.rb
|
73
|
+
- spec/support/active_record.rb
|
74
|
+
homepage: https://github.com/Ptico/resourced
|
75
|
+
licenses: []
|
76
|
+
post_install_message:
|
77
|
+
rdoc_options: []
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>'
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 1.3.1
|
92
|
+
requirements: []
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.8.23
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: Missing layer between model and controller
|
98
|
+
test_files:
|
99
|
+
- spec/resourced/adapters/active_record_spec.rb
|
100
|
+
- spec/resourced/finders_spec.rb
|
101
|
+
- spec/resourced/params_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/support/active_record.rb
|