shareable_models 0.5.0
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.
- 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
|