shareable_models 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +661 -0
- data/README.md +123 -0
- data/Rakefile +25 -0
- data/app/models/share_model.rb +18 -0
- data/lib/shareable_models/engine.rb +13 -0
- data/lib/shareable_models/models/shareable.rb +149 -0
- data/lib/shareable_models/models/sharer.rb +232 -0
- data/lib/shareable_models/share.rb +50 -0
- data/lib/shareable_models/version.rb +7 -0
- data/lib/shareable_models.rb +15 -0
- data/spec/dummy/Rakefile +3 -0
- data/spec/dummy/app/models/resource.rb +8 -0
- data/spec/dummy/app/models/user.rb +8 -0
- data/spec/dummy/config/application.rb +17 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/secrets.yml +3 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20150831110800_create_users.rb +8 -0
- data/spec/dummy/db/migrate/20150831110900_create_resources.rb +9 -0
- data/spec/dummy/db/schema.rb +41 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +750 -0
- data/spec/dummy/log/test.log +3476 -0
- data/spec/dummy/tmp/cache/assets/CC1/7E0/sprockets%2F90700621d32c3e2ae2a4f912a72253ab +0 -0
- data/spec/dummy/tmp/cache/assets/CCF/6B0/sprockets%2Fa4652cd2ae14bbb5631a5a6480584905 +0 -0
- data/spec/dummy/tmp/cache/assets/D1C/290/sprockets%2Fe7f8e6b08f4ae6b9d49245564016ad27 +0 -0
- data/spec/dummy/tmp/cache/assets/D22/3A0/sprockets%2F3810c34c6ed797768acf9533ac9f086c +0 -0
- data/spec/dummy/tmp/cache/assets/D40/C10/sprockets%2F13484019fee8aed93d4d7f0342c0fa66 +0 -0
- data/spec/dummy/tmp/cache/assets/D6C/9B0/sprockets%2F49fc1144e2a91d5a71afc8da6d16387b +0 -0
- data/spec/dummy/tmp/cache/assets/DFD/6A0/sprockets%2Fa3e950d0cbe582637af2dd8caec45ed6 +0 -0
- data/spec/dummy/tmp/cache/assets/EEE/C40/sprockets%2Fb9ca5bee4dfcaa86005ef95ffcca5edb +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/-KhweS4Wy6LONwXNAAX21MuCb6S5pmcc2At-PRZTnVQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/2E0BHjS9ga5CVF8p1i0BD342dTagEy6vpdptNnz4yi0.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/2SiJB6jwY0ykyL_L-2XcQS_URsqxH2jewKoEcaXdRQI.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/2b4LnUroZnwjtmAZ9wAlYXERumGQqZ06CtgLLqLlFZ4.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/4yzlzceZQUxV0k8KLBaMF73Bn_2-WuesKO0HeG3c7TI.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/57y6b0y31Rr9fh-AS-C-53-Pi2yM7ckkgwYIQeDIvu8.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/5BpxDBRXPSEVa8E4oyZEZUcbmyodKDh45wQLjs9j7LA.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/6ZWgaX4_0TmoMClNlDesNKcO2HO-goRDGx5iocHbsKo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/7F6McBtYiHW6rgiNy0YCvNTPhfMIOUrq7tsUI5xult8.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/7w0mCgW__e7l2wA7VXo4H1RR91pDMz1nJLnqNtCqGvo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/8h7VuyG1HXfABXa1B4--Oi7NfJQvchxYp4XzbT-8aWA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/BRS6CEJIYcJNLwfTJZCdYoBa9bJKVzo4o2X9PlbCDyw.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/CV97-tikdhGY51qhiE8LIBXJPysB-0urwrF9u7wOgQA.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/E3jkQYtPcnSLkXBBARnRBrW1lTIIVrHyTHO2s689urA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/EKftD-LyK0JTxeLPzIiA90IkUlw-Rfu8hf1K2kIqJpE.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/F4fl2U72q21ysGgeNZOyLkVCfw2dvQPjoEghki_D_8A.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/F6RTFgWsLUC52NYLMkg2FeNKjgEZhSQS5NV1N3T0rGw.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/IPsZefXeGnBC1-g2_BOJHuaCBLP6E60ISZQvjOn2QGc.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/J0CARfIsAx4nD1Pk3q1NhG9xCHPOwGj9PaGu8z-ah1g.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/Jaayipvdnp-lwqhyJiWdoGDwz5czFOMZLwducjDmfHc.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/K5wHnruZLsDmlCHhkWMCagLWPKPM5bxwB2dv037yO5o.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/KgWn-QmwEYBvpqByQM41ifUzsmOl40LnAJrCoq_oZcQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/LAGHT0mMgGhdBGTKU8fpHrmHSQmt5fIoHTTi4kIOUJw.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/T3dERBPkMEnJr8fvShtPBsFScRkir0lzJZQkpvrrct0.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/UTAd4qlYa57uaORsIWcp6VqcAUkRzixYzIGoEFCvCrY.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/UoWufV0QdgwAuVnwDXNWOQfgjGN6U7KnO2_2nXC5gHg.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/WpToyjdG3xfOFtl4HJYTpy4_w0U40f7qQWUm7d2SDxg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/XwozjE91sFB4cQGoJUgr2jV1ZuRlvN_HTWgpclrD59A.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/YAzUmPkKnALIIJE_0Meb0N5qTlBvVE1ront_B8yecfo.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/Yuxa44Ctnn_uPp1nr47NdNwltrmTEp0PleFL3ylXbtM.cache +4 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/ZEUypIim5xuBK679jyb340c39nHQ3EvcgqbmeiHlT0c.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/ZQf7Hy4n2V3c-98b9kqTrZAcw3B2TDRF3IKPm9I7VjY.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/_SIN6dUlHiJyeUSpOPWAgfLfMrtjGlZgx8ZLEbkjLuQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/_TUGtF7Vi9blFNr3awt9kgCoid35NdmY8hSHmxuaWbg.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/_eb4NkdcbQR63aZySRq7Yv5_w_JReWqG2Q5mYA5u62s.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/bPiSNDFYlW5YDua0D-fC4uPRV8hYABbCor092KHNzHM.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/d3_09TJ2Uoh7sdjvHWw4jPVF0bObKu065NmgG-EIWcA.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/dQTSoOSXec5z_sf_i4UrehHxFRAn4HbRvfh8mUEK2ME.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/fVIC-zRTWj6S5SLH_vJQJY-ny5le0IaRQda1YLQQr68.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/hpNHCD7sn3wufa6PLUdFP95AS8nwrAaL_b9K4X9_eCE.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/huDMU1soBHMNsbUDXnTPbLtqvAEYPe6E-tLL9phYOBY.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/i43TdJOVKXtyejasCbHJP4U34FcJ5siCLFWpnPWxxU4.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/i4gUJwKC59hdrcb7ZaO09fNtKexVsb67-y3MZ7twsyI.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/iynGcjYbH69ZuxdS02RrsK2y33uPjFHCrYHRxxNVn_k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/jR1PEBCdtdy135gPG-zLQDg25XQDP9eDBew7pmhz34k.cache +0 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/jvNKAnuW3TSUu3LxRcqdtmSHgoGiukm1onfXqD6glQA.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/kOBlorvGjGDVFhapT1HrAw48XziyeSqexuiKpVhDMac.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/mS-IpVGnBSf2F0bGZ7OPpXNCcNMtHmxm7TI1hHVlvps.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/pKsozyUJjXN2tIwiCbkGt9AzwLwdpHCYC1Cib-9GBIo.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/pP2ApxUxxz-JMscfDHnlZ4Hq4DybYrG_9B4npoDMGAE.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/r9dDy892QJeXi_CLvyTWZvGA9idLCbq9X0lnwxr9RZQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rLeviNgrP2-MqSDlwBdtz3ciY9n32a5oY9NY3iQupsg.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rf04rP-66AEEH942fvU_IcXRtrpw1_xbvnaxHD2wZGA.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/uSWRYxEuSTYl_g1MvbE90G-h747P1dyWDCSZY0z1yzA.cache +2 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/uifNaYvk6KYC76GaHQ9muL_ObOmw4C_CSl9lzOUfnDE.cache +3 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/yOVPU3Y8NJQjJYfkK-l7I_DPfd3T4mIccYc6gY6ODBQ.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/yZA3xwYBnfhABCzMVYKNfJMWpJg4HEovQnr5t48Yrf0.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/y_XqYDfeQ0fehcC3FNz-qYXXcwOxtSDfsJX1nStC1qU.cache +1 -0
- data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/z_w2fR4GzVyUeMzrOEZKQSykA7SSjpdpLtlBgXEgcUA.cache +5 -0
- data/spec/fixtures/resources.yml +14 -0
- data/spec/fixtures/share_models.yml +8 -0
- data/spec/fixtures/users.yml +11 -0
- data/spec/models/share_model_spec.rb +148 -0
- data/spec/spec_helper.rb +20 -0
- metadata +303 -0
data/README.md
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# Shareable Models
|
2
|
+
|
3
|
+
Shareable models is a gem to share models between models. Is this strange? I will explain the functionality of this Gem with an example.
|
4
|
+
|
5
|
+
Imagine you have an awesome Rails app for a publisher with three models:
|
6
|
+
* Author: it creates books.
|
7
|
+
* Book: a world behind words.
|
8
|
+
* Group: a group of authors.
|
9
|
+
|
10
|
+
An author can create many private books, but he wants to share it another author. Author class is a `sharer`, it can share and receive books. Books are things that can be shared, so they are `shareable`.
|
11
|
+
|
12
|
+
Another author wants to share its book with multiple authors, all of them form a Group. Share a book with all authors in the group can be bored. However, you can set Group `shareable` and share the book with the entire group.
|
13
|
+
|
14
|
+
Shareable Models use polymorphic relations to allow you to share resources between multiple models with no limitations.
|
15
|
+
|
16
|
+
# Installation
|
17
|
+
To use `ShareableModels` in your application, add this line to your Gemfile:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
gem 'shareable_models', '~> 1.0.0'
|
21
|
+
```
|
22
|
+
|
23
|
+
After add it, run `bundle install`.
|
24
|
+
|
25
|
+
Next you need to load migrations of this gem, so execute in your Rails application:
|
26
|
+
|
27
|
+
```
|
28
|
+
rake shareable_models:install:migrations
|
29
|
+
rake db:migrate
|
30
|
+
```
|
31
|
+
|
32
|
+
Last step is to set your classes as `sharer` or `shareable`. To do this include these methods at top of yout models. For example:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
# File app/models/author.rb (sharer)
|
36
|
+
class Author < ActiveRecord::Base
|
37
|
+
sharer
|
38
|
+
|
39
|
+
#...
|
40
|
+
end
|
41
|
+
|
42
|
+
# File app/models/book.rb (shareable)
|
43
|
+
class Book < ActiveRecord::Base
|
44
|
+
# Owner define the creator of the book, to check edit/read permissions.
|
45
|
+
# See next sections
|
46
|
+
shareable owner: :author
|
47
|
+
|
48
|
+
#...
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
# Models
|
53
|
+
A model can be `sharer` or `shareable`. This methods include some relations and added new methods to your class.
|
54
|
+
|
55
|
+
## Sharer
|
56
|
+
Set a model as sharer. A sharer can share and receive resources. It has edit permissions on a resource if:
|
57
|
+
|
58
|
+
* It creates the resource (see [Shareable](#shareable))
|
59
|
+
* Another sharer shares the resource with him and edit permission is true.
|
60
|
+
|
61
|
+
Importants methods defined by sharer (see sharer.rb for full documentation):
|
62
|
+
|
63
|
+
* `share(resource, to, edit)`: share a resource with another model (to). You can set edit permissions (false by default).
|
64
|
+
* `share_with_me(resource, from, edit)`: share a resource **from** another sharer to me.
|
65
|
+
* `can_edit?(resource)`: check if a model can edit a resource.
|
66
|
+
* `can_read?(resource)`: check if a model can read a resource.
|
67
|
+
* `throw_out(resource, sharer)`: throw out a sharer from a resource.
|
68
|
+
* `leave(resource)`: to leave resource. A creator/owner of a shareable resource can't leave it.
|
69
|
+
* `allow_edit?(resource, to)`: allow a model to edit a resource. If resource was never shared with model, a new relation is created.
|
70
|
+
* `prevent_edit?(resource, to)`: disable an user to edit a resource. If resource was never shared with model, relation won't be created.
|
71
|
+
|
72
|
+
## Shareable
|
73
|
+
Set a model as shareable. You need to specify what's the name of the relation to find 'owner' or 'creator' of this shareable model:
|
74
|
+
```ruby
|
75
|
+
class User < ActiveRecord::Base
|
76
|
+
shareable owner: :user
|
77
|
+
# ...
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
Shareable models can be shared between sharers. Importants methods defined by sharer (see sharer.rb for full documentation):
|
82
|
+
|
83
|
+
* `share_it(from, to, edit)`: share the resource with **from** a model **to** another. You can set edit permissions (false by default).
|
84
|
+
* `editable_by?(from)`: check if resource is editable by given model.
|
85
|
+
* `readable_by?(from)`: check if resource is readable by given model.
|
86
|
+
* `throw_out(from, to)`: a sharer (**from**) throw out another (**to**) from a resource.
|
87
|
+
* `leave(sharer)`: sharer leaves resource. A creator/owner of a shareable resource can't leave it.
|
88
|
+
* `allow_edit?(from, to)`: a sharer (**from**) allow another (**to**) to edit a resource. If resource was never shared with model, a new relation is created.
|
89
|
+
* `prevent_edit?(from, to)`: a sharer (**from**) disable another (**to**) to edit a resource. If resource was never shared with model, relation won't be created.
|
90
|
+
|
91
|
+
# Example of usage
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# We start at scenario described at introduction: Author, Book and Group. Instanced models:
|
95
|
+
# * author1: it creates book1
|
96
|
+
# * author2: another author. Anyone shared book1 with it.
|
97
|
+
|
98
|
+
author1.can_read? book1 # -> true
|
99
|
+
author1.can_edit? book1 # -> true
|
100
|
+
author2.can_read? book1 # -> false
|
101
|
+
author2.can_edit? book1 # -> false
|
102
|
+
|
103
|
+
author1.share(book1, author2) # -> true
|
104
|
+
author2.can_read? book1 # -> true
|
105
|
+
author2.can_edit? book1 # -> false
|
106
|
+
|
107
|
+
author1.allow_edit(book1, author2) # -> true
|
108
|
+
author2.can_read? book1 # -> true
|
109
|
+
author2.can_edit? book1 # -> true
|
110
|
+
```
|
111
|
+
|
112
|
+
# Contribute
|
113
|
+
|
114
|
+
To contribute Shareable Models:
|
115
|
+
|
116
|
+
* Create an issue with the contribution: bug, enhancement, feature...
|
117
|
+
* Fork the repository and make all changes you need
|
118
|
+
* Write test on new changes
|
119
|
+
* Create a pull request when you finish
|
120
|
+
|
121
|
+
# License
|
122
|
+
|
123
|
+
ShareableModels gem is released under the Affero GPL license. Copyright [redBorder](http://redborder.net)
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
# Load dummy app
|
9
|
+
APP_RAKEFILE = File.expand_path('../spec/dummy/Rakefile', __FILE__)
|
10
|
+
load 'rails/tasks/engine.rake'
|
11
|
+
|
12
|
+
Bundler::GemHelper.install_tasks
|
13
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each { |f| load f }
|
14
|
+
|
15
|
+
require 'rspec/core'
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
|
18
|
+
desc 'Run all specs in spec directory (excluding plugin specs)'
|
19
|
+
|
20
|
+
# Load database
|
21
|
+
ENV['RAILS_ENV'] ||= 'test'
|
22
|
+
RSpec::Core::RakeTask.new(spec: 'app:db:test:prepare')
|
23
|
+
|
24
|
+
# Set default rake task to run rspec
|
25
|
+
task default: :spec
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#
|
2
|
+
# Class to connect shared resources with another models. Resources are every
|
3
|
+
# class include shareable module. Shared_to can be any model with share_receiver
|
4
|
+
# module included.
|
5
|
+
#
|
6
|
+
class ShareModel < ActiveRecord::Base
|
7
|
+
# Resource it's shared
|
8
|
+
belongs_to :resource, polymorphic: true
|
9
|
+
# Model with resource is shared
|
10
|
+
belongs_to :shared_to, polymorphic: true
|
11
|
+
# User who share resources
|
12
|
+
belongs_to :shared_from, polymorphic: true
|
13
|
+
|
14
|
+
validates :resource, :shared_to, :shared_from, presence: true
|
15
|
+
# Every resource only can be shared with one person from same sharer one time.
|
16
|
+
validates :resource_id, uniqueness: { scope: [:resource_type, :shared_to_id,
|
17
|
+
:shared_to_type] }
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#
|
2
|
+
# Engine to add to main rails app
|
3
|
+
#
|
4
|
+
module ShareableModels
|
5
|
+
# Set the base engine to connect with rails
|
6
|
+
class Engine < ::Rails::Engine
|
7
|
+
engine_name 'shareable_models'
|
8
|
+
|
9
|
+
config.generators do |g|
|
10
|
+
g.test_framework :rspec, fixture: true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
#
|
2
|
+
# Namespace of Shareable.
|
3
|
+
#
|
4
|
+
module ShareableModels
|
5
|
+
#
|
6
|
+
# Modules to include in models that will use shareable options
|
7
|
+
#
|
8
|
+
module Models
|
9
|
+
#
|
10
|
+
# Define a model as shareable. A shareable model can be shared between
|
11
|
+
# other models called sharers. Imagine a platform with privates articles,
|
12
|
+
# you could want to share your awesome article with other person, so
|
13
|
+
# make it shareable.
|
14
|
+
#
|
15
|
+
module Shareable
|
16
|
+
#
|
17
|
+
# Method to determine if a model can be shared. It always true because
|
18
|
+
# the class includes this module.
|
19
|
+
#
|
20
|
+
# == Returns
|
21
|
+
# true
|
22
|
+
#
|
23
|
+
def shareable?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Return the shareable owner
|
29
|
+
#
|
30
|
+
def shareable_owner
|
31
|
+
return nil if shareable_options.nil? || shareable_options[:owner].nil?
|
32
|
+
send(shareable_options[:owner])
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Share current element with a sharer model.
|
37
|
+
#
|
38
|
+
# == Parameters:
|
39
|
+
# from::
|
40
|
+
# Instance of model with share this resource
|
41
|
+
# to::
|
42
|
+
# Instance of model to share with
|
43
|
+
# edit::
|
44
|
+
# True if the user has edit permission. False by default
|
45
|
+
#
|
46
|
+
# == Returns:
|
47
|
+
# true if its saved.
|
48
|
+
#
|
49
|
+
def share_it(from, to, edit = false)
|
50
|
+
from.share(self, to, edit)
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Throw out an user from this resource
|
55
|
+
#
|
56
|
+
# == Parameters:
|
57
|
+
# from::
|
58
|
+
# Sharer that want to leave resource.
|
59
|
+
# to::
|
60
|
+
# Sharer that want to leave resource.
|
61
|
+
#
|
62
|
+
# == Returns:
|
63
|
+
# True if it's ok
|
64
|
+
#
|
65
|
+
def throw_out(from, to)
|
66
|
+
from.throw_out(self, to)
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Allow given sharer to leave the resource
|
71
|
+
#
|
72
|
+
# == Parameters:
|
73
|
+
# sharer::
|
74
|
+
# Sharer that want to leave resource.
|
75
|
+
#
|
76
|
+
# == Returns:
|
77
|
+
# True if it's ok
|
78
|
+
#
|
79
|
+
def leave_by(sharer)
|
80
|
+
sharer.leave(self)
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Permissions
|
85
|
+
# ----------------------------------------------------
|
86
|
+
|
87
|
+
#
|
88
|
+
# Check if a given sharer can edit this resource
|
89
|
+
#
|
90
|
+
# == Parameters:
|
91
|
+
# from::
|
92
|
+
# Sharer that wants to edit the user.
|
93
|
+
#
|
94
|
+
# == Returns:
|
95
|
+
# true if the sharer can edit it
|
96
|
+
#
|
97
|
+
def editable_by?(from)
|
98
|
+
from.can_edit?(self)
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Check if a given sharer can see this resource
|
103
|
+
#
|
104
|
+
# == Parameters:
|
105
|
+
# from::
|
106
|
+
# Sharer that wants to see the user.
|
107
|
+
#
|
108
|
+
# == Returns:
|
109
|
+
# true if the sharer can see it
|
110
|
+
#
|
111
|
+
def readable_by?(from)
|
112
|
+
from.can_read?(self)
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Allow a sharer to edit the resource
|
117
|
+
#
|
118
|
+
# == Parameters:
|
119
|
+
# from::
|
120
|
+
# Sharer that want to allow another to edit resource
|
121
|
+
# to::
|
122
|
+
# Sharer that will be able to edit the resource
|
123
|
+
#
|
124
|
+
# == Returns:
|
125
|
+
# True if it's ok
|
126
|
+
#
|
127
|
+
def allow_edit(from, to)
|
128
|
+
from.allow_edit(self, to)
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Prevent a sharer to edit the resource. First parameter is to set same
|
133
|
+
# format of allow_edit.
|
134
|
+
#
|
135
|
+
# == Parameters:
|
136
|
+
# from::
|
137
|
+
# Sharer that want to allow another to edit resource
|
138
|
+
# to::
|
139
|
+
# Sharer that will be able to edit the resource
|
140
|
+
#
|
141
|
+
# == Returns:
|
142
|
+
# True if it's ok
|
143
|
+
#
|
144
|
+
def prevent_edit(from, to)
|
145
|
+
from.prevent_edit(self, to)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
#
|
2
|
+
# Namespace of Shareable.
|
3
|
+
#
|
4
|
+
module ShareableModels
|
5
|
+
#
|
6
|
+
# Modules to include in models that will use shareable options
|
7
|
+
#
|
8
|
+
module Models
|
9
|
+
#
|
10
|
+
# Define a model as shareable. A shareable model can be shared between
|
11
|
+
# other models called sharers. Imagine a platform with privates articles,
|
12
|
+
# you could want to share your awesome article with other person, so
|
13
|
+
# make it shareable.
|
14
|
+
#
|
15
|
+
module Sharer
|
16
|
+
#
|
17
|
+
# Method to determine if a model can share and receive elements. It always
|
18
|
+
# true because the class include this module.
|
19
|
+
#
|
20
|
+
# == Returns
|
21
|
+
# true
|
22
|
+
#
|
23
|
+
def sharer?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Share a given resource with a sharer model.
|
29
|
+
#
|
30
|
+
# == Parameters:
|
31
|
+
# resource::
|
32
|
+
# Resource to share. It must includes shareable module
|
33
|
+
# to::
|
34
|
+
# Model to share the resource. It must includes sharer module
|
35
|
+
# edit::
|
36
|
+
# Boolean indicating if it has permissions to edit shared resources.
|
37
|
+
# False by default.
|
38
|
+
#
|
39
|
+
# == Returns:
|
40
|
+
# True if it's saved
|
41
|
+
#
|
42
|
+
def share(resource, to, edit = false)
|
43
|
+
check_resource(resource)
|
44
|
+
check_sharer(to)
|
45
|
+
return false unless can_edit?(resource)
|
46
|
+
# Save new share
|
47
|
+
shared_resources.build(
|
48
|
+
shared_to: to,
|
49
|
+
resource: resource,
|
50
|
+
edit: edit
|
51
|
+
)
|
52
|
+
save!
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Share the given resource with this model. You can understand this method
|
57
|
+
# as inverse of share.
|
58
|
+
#
|
59
|
+
# == Parameters:
|
60
|
+
# resource::
|
61
|
+
# Resource to share. It must includes shareable module
|
62
|
+
# from::
|
63
|
+
# Model who share the resource. It must includes sharer module
|
64
|
+
# edit::
|
65
|
+
# Boolean indicating if it has permissions to edit shared resources.
|
66
|
+
# False by default.
|
67
|
+
#
|
68
|
+
# == Returns:
|
69
|
+
# True if it's saved
|
70
|
+
#
|
71
|
+
def share_with_me(resource, from, edit = false)
|
72
|
+
check_resource(resource)
|
73
|
+
check_sharer(from)
|
74
|
+
# Save new share
|
75
|
+
from.share(resource, self, edit)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Stop sharing a shareable model with a sharer. You can throw out creator
|
80
|
+
# of shareable model.
|
81
|
+
#
|
82
|
+
# == Parameters:
|
83
|
+
# resource::
|
84
|
+
# Resource to throw out the sharer.
|
85
|
+
# sharer::
|
86
|
+
# Sharer model to disable share.
|
87
|
+
# edit::
|
88
|
+
# Check if sharer has permissions to edit before throw out. It's true
|
89
|
+
# by default, but if an user try to leave a resource we must not check
|
90
|
+
# this.
|
91
|
+
#
|
92
|
+
# == Returns:
|
93
|
+
# True if it's ok
|
94
|
+
#
|
95
|
+
def throw_out(resource, sharer, edit = true)
|
96
|
+
check_resource(resource)
|
97
|
+
check_sharer(sharer)
|
98
|
+
return false if (edit && !self.can_edit?(resource)) ||
|
99
|
+
resource.shareable_owner == sharer
|
100
|
+
relation = resource.shared_with.find_by(shared_to: sharer)
|
101
|
+
relation.nil? ? true : relation.destroy.destroyed?
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Current sharer leaves a shareable object.
|
106
|
+
#
|
107
|
+
# == Parameters:
|
108
|
+
# resource::
|
109
|
+
# Resource to throw out the sharer.
|
110
|
+
#
|
111
|
+
# == Returns:
|
112
|
+
# True if it's ok
|
113
|
+
#
|
114
|
+
def leave(resource)
|
115
|
+
throw_out(resource, self, false)
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Permissions
|
120
|
+
# ----------------------------------------------------
|
121
|
+
|
122
|
+
#
|
123
|
+
# Check if the current sharer can edit a given resource. We need to check
|
124
|
+
# if the user share this element or someone share it with him.
|
125
|
+
#
|
126
|
+
# == Parameters:
|
127
|
+
# resource::
|
128
|
+
# Resource model sharer wants to check
|
129
|
+
#
|
130
|
+
# == Returns:
|
131
|
+
# True or false based on permission
|
132
|
+
#
|
133
|
+
def can_edit?(resource)
|
134
|
+
check_resource(resource)
|
135
|
+
resource.shareable_owner == self ||
|
136
|
+
shared_with_me.where(edit: true).exists?(edit: true, resource: resource)
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Check if the current sharer can read a given resource. We need to check if
|
141
|
+
# the user share this element or someone share it with him.
|
142
|
+
#
|
143
|
+
# == Parameters:
|
144
|
+
# resource::
|
145
|
+
# Resource model sharer wants to check
|
146
|
+
#
|
147
|
+
# == Returns:
|
148
|
+
# True or false
|
149
|
+
#
|
150
|
+
def can_read?(resource)
|
151
|
+
check_resource(resource)
|
152
|
+
resource.shareable_owner == self ||
|
153
|
+
shared_with_me.exists?(resource: resource)
|
154
|
+
end
|
155
|
+
|
156
|
+
#
|
157
|
+
# Allow a sharer to edit the resource
|
158
|
+
#
|
159
|
+
# == Parameters:
|
160
|
+
# resource::
|
161
|
+
# Resource model to allow edit the user
|
162
|
+
# to::
|
163
|
+
# Sharer that will be able to edit the resource
|
164
|
+
#
|
165
|
+
# == Returns:
|
166
|
+
# True if it's ok
|
167
|
+
#
|
168
|
+
def allow_edit(resource, to)
|
169
|
+
return false unless can_edit?(resource)
|
170
|
+
return true if to.can_edit?(resource)
|
171
|
+
share_resource = shared_resources.find_by(shared_to: to, resource: resource)
|
172
|
+
if share_resource.nil?
|
173
|
+
share(resource, to, true)
|
174
|
+
else
|
175
|
+
share_resource.update(edit: true)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Prevent a sharer to edit the resource. First parameter is to set same
|
181
|
+
# format of allow_edit.
|
182
|
+
#
|
183
|
+
# == Parameters:
|
184
|
+
# resource::
|
185
|
+
# Resource to prevent an user to edit
|
186
|
+
# to::
|
187
|
+
# Sharer that will be able to edit the resource
|
188
|
+
#
|
189
|
+
# == Returns:
|
190
|
+
# True if it's ok
|
191
|
+
#
|
192
|
+
def prevent_edit(resource, to)
|
193
|
+
return false unless can_edit?(resource)
|
194
|
+
share_resource = shared_resources.find_by(shared_to: to, resource: resource)
|
195
|
+
return true if share_resource.nil?
|
196
|
+
share_resource.update(edit: false)
|
197
|
+
end
|
198
|
+
|
199
|
+
private
|
200
|
+
|
201
|
+
#
|
202
|
+
# Check if from and destination models have included the modules
|
203
|
+
#
|
204
|
+
# == Parameters:
|
205
|
+
# resource::
|
206
|
+
# Instance of resource to share
|
207
|
+
#
|
208
|
+
# == Returns:
|
209
|
+
# Fail if there are a bad condition
|
210
|
+
#
|
211
|
+
def check_resource(resource)
|
212
|
+
fail "#{resource} class must include Shareable module" unless
|
213
|
+
resource.respond_to?(:shareable?)
|
214
|
+
end
|
215
|
+
|
216
|
+
#
|
217
|
+
# Check if given sharer include the correct module in the class.
|
218
|
+
#
|
219
|
+
# == Parameters:
|
220
|
+
# sharer::
|
221
|
+
# Instance of model to share with
|
222
|
+
#
|
223
|
+
# == Returns:
|
224
|
+
# Fail if the class doesn't include Sharer module
|
225
|
+
#
|
226
|
+
def check_sharer(sharer)
|
227
|
+
fail "#{sharer} class must include Sharer module" unless
|
228
|
+
sharer.respond_to?(:sharer?)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#
|
2
|
+
# Namespace of Shareable.
|
3
|
+
#
|
4
|
+
module ShareableModels
|
5
|
+
#
|
6
|
+
# It's define the options to set a model as sharer or shareable
|
7
|
+
#
|
8
|
+
module Share
|
9
|
+
#
|
10
|
+
# Define a model as shareable. A shareable model can be shared between
|
11
|
+
# other models called sharers. Imagine a platform with privates articles,
|
12
|
+
# you could want to share your awesome article with other person, so
|
13
|
+
# make it shareable.
|
14
|
+
#
|
15
|
+
# == Parameters:
|
16
|
+
# options:
|
17
|
+
# A hash with options to personalize the way to share the model.
|
18
|
+
# - owner: name of relation that point to the create of resource.
|
19
|
+
# For example, the author of an Article. Owners always can
|
20
|
+
# read and edit the resources.
|
21
|
+
#
|
22
|
+
def shareable(options = {})
|
23
|
+
include ShareableModels::Models::Shareable
|
24
|
+
|
25
|
+
# Add some relations
|
26
|
+
has_many :shared_with, as: :resource, class_name: 'ShareModel'
|
27
|
+
|
28
|
+
class_attribute :shareable_options
|
29
|
+
self.shareable_options = options
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Define a model as sharer. A sharer model can share another models that
|
34
|
+
# it has edit permissions or it is owner. Following the example of Articles,
|
35
|
+
# Sharer model will be Author model.
|
36
|
+
#
|
37
|
+
# A sharer can only receive shareable elements too. For example, you could
|
38
|
+
# define a Group model that englobe multiple authors. Group can be a sharer
|
39
|
+
# that only receive shares from Authors (another shares) to share Article
|
40
|
+
# with multiple authors.
|
41
|
+
#
|
42
|
+
def sharer
|
43
|
+
include ShareableModels::Models::Sharer
|
44
|
+
|
45
|
+
# Add some relations
|
46
|
+
has_many :shared_resources, as: :shared_from, class_name: 'ShareModel'
|
47
|
+
has_many :shared_with_me, as: :shared_to, class_name: 'ShareModel'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Require all files
|
2
|
+
require 'shareable_models/models/shareable'
|
3
|
+
require 'shareable_models/models/sharer'
|
4
|
+
require 'shareable_models/share'
|
5
|
+
require 'active_record'
|
6
|
+
|
7
|
+
# Extend active record with share options
|
8
|
+
ActiveRecord::Base.send :extend, ShareableModels::Share
|
9
|
+
|
10
|
+
# Only require if rails is available
|
11
|
+
require 'shareable_models/engine'
|
12
|
+
|
13
|
+
# Base module
|
14
|
+
module ShareableModels
|
15
|
+
end
|
data/spec/dummy/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
# Pick the frameworks you want:
|
4
|
+
require "active_record/railtie"
|
5
|
+
#require "sprockets/railtie"
|
6
|
+
# require "rails/test_unit/railtie"
|
7
|
+
|
8
|
+
Bundler.require(*Rails.groups)
|
9
|
+
|
10
|
+
module Dummy
|
11
|
+
class Application < Rails::Application
|
12
|
+
# Do not swallow errors in after_commit/after_rollback callbacks.
|
13
|
+
config.active_record.raise_in_transactional_callbacks = true
|
14
|
+
config.encoding = "utf-8"
|
15
|
+
config.eager_load = false
|
16
|
+
end
|
17
|
+
end
|