resourced 0.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/Ptico/resourced.png)](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
|