shareable_models 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +661 -0
  3. data/README.md +123 -0
  4. data/Rakefile +25 -0
  5. data/app/models/share_model.rb +18 -0
  6. data/lib/shareable_models/engine.rb +13 -0
  7. data/lib/shareable_models/models/shareable.rb +149 -0
  8. data/lib/shareable_models/models/sharer.rb +232 -0
  9. data/lib/shareable_models/share.rb +50 -0
  10. data/lib/shareable_models/version.rb +7 -0
  11. data/lib/shareable_models.rb +15 -0
  12. data/spec/dummy/Rakefile +3 -0
  13. data/spec/dummy/app/models/resource.rb +8 -0
  14. data/spec/dummy/app/models/user.rb +8 -0
  15. data/spec/dummy/config/application.rb +17 -0
  16. data/spec/dummy/config/boot.rb +5 -0
  17. data/spec/dummy/config/database.yml +25 -0
  18. data/spec/dummy/config/environment.rb +5 -0
  19. data/spec/dummy/config/environments/test.rb +42 -0
  20. data/spec/dummy/config/secrets.yml +3 -0
  21. data/spec/dummy/config.ru +4 -0
  22. data/spec/dummy/db/development.sqlite3 +0 -0
  23. data/spec/dummy/db/migrate/20150831110800_create_users.rb +8 -0
  24. data/spec/dummy/db/migrate/20150831110900_create_resources.rb +9 -0
  25. data/spec/dummy/db/schema.rb +41 -0
  26. data/spec/dummy/db/test.sqlite3 +0 -0
  27. data/spec/dummy/log/development.log +750 -0
  28. data/spec/dummy/log/test.log +3476 -0
  29. data/spec/dummy/tmp/cache/assets/CC1/7E0/sprockets%2F90700621d32c3e2ae2a4f912a72253ab +0 -0
  30. data/spec/dummy/tmp/cache/assets/CCF/6B0/sprockets%2Fa4652cd2ae14bbb5631a5a6480584905 +0 -0
  31. data/spec/dummy/tmp/cache/assets/D1C/290/sprockets%2Fe7f8e6b08f4ae6b9d49245564016ad27 +0 -0
  32. data/spec/dummy/tmp/cache/assets/D22/3A0/sprockets%2F3810c34c6ed797768acf9533ac9f086c +0 -0
  33. data/spec/dummy/tmp/cache/assets/D40/C10/sprockets%2F13484019fee8aed93d4d7f0342c0fa66 +0 -0
  34. data/spec/dummy/tmp/cache/assets/D6C/9B0/sprockets%2F49fc1144e2a91d5a71afc8da6d16387b +0 -0
  35. data/spec/dummy/tmp/cache/assets/DFD/6A0/sprockets%2Fa3e950d0cbe582637af2dd8caec45ed6 +0 -0
  36. data/spec/dummy/tmp/cache/assets/EEE/C40/sprockets%2Fb9ca5bee4dfcaa86005ef95ffcca5edb +0 -0
  37. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/-KhweS4Wy6LONwXNAAX21MuCb6S5pmcc2At-PRZTnVQ.cache +1 -0
  38. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/2E0BHjS9ga5CVF8p1i0BD342dTagEy6vpdptNnz4yi0.cache +1 -0
  39. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/2SiJB6jwY0ykyL_L-2XcQS_URsqxH2jewKoEcaXdRQI.cache +1 -0
  40. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/2b4LnUroZnwjtmAZ9wAlYXERumGQqZ06CtgLLqLlFZ4.cache +3 -0
  41. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/4yzlzceZQUxV0k8KLBaMF73Bn_2-WuesKO0HeG3c7TI.cache +1 -0
  42. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/57y6b0y31Rr9fh-AS-C-53-Pi2yM7ckkgwYIQeDIvu8.cache +3 -0
  43. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/5BpxDBRXPSEVa8E4oyZEZUcbmyodKDh45wQLjs9j7LA.cache +3 -0
  44. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/6ZWgaX4_0TmoMClNlDesNKcO2HO-goRDGx5iocHbsKo.cache +1 -0
  45. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/7F6McBtYiHW6rgiNy0YCvNTPhfMIOUrq7tsUI5xult8.cache +0 -0
  46. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/7w0mCgW__e7l2wA7VXo4H1RR91pDMz1nJLnqNtCqGvo.cache +1 -0
  47. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/8h7VuyG1HXfABXa1B4--Oi7NfJQvchxYp4XzbT-8aWA.cache +0 -0
  48. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/BRS6CEJIYcJNLwfTJZCdYoBa9bJKVzo4o2X9PlbCDyw.cache +1 -0
  49. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/CV97-tikdhGY51qhiE8LIBXJPysB-0urwrF9u7wOgQA.cache +3 -0
  50. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/E3jkQYtPcnSLkXBBARnRBrW1lTIIVrHyTHO2s689urA.cache +0 -0
  51. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/EKftD-LyK0JTxeLPzIiA90IkUlw-Rfu8hf1K2kIqJpE.cache +2 -0
  52. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/F4fl2U72q21ysGgeNZOyLkVCfw2dvQPjoEghki_D_8A.cache +1 -0
  53. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/F6RTFgWsLUC52NYLMkg2FeNKjgEZhSQS5NV1N3T0rGw.cache +1 -0
  54. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/IPsZefXeGnBC1-g2_BOJHuaCBLP6E60ISZQvjOn2QGc.cache +0 -0
  55. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/J0CARfIsAx4nD1Pk3q1NhG9xCHPOwGj9PaGu8z-ah1g.cache +3 -0
  56. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/Jaayipvdnp-lwqhyJiWdoGDwz5czFOMZLwducjDmfHc.cache +1 -0
  57. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/K5wHnruZLsDmlCHhkWMCagLWPKPM5bxwB2dv037yO5o.cache +1 -0
  58. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/KgWn-QmwEYBvpqByQM41ifUzsmOl40LnAJrCoq_oZcQ.cache +1 -0
  59. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/LAGHT0mMgGhdBGTKU8fpHrmHSQmt5fIoHTTi4kIOUJw.cache +2 -0
  60. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/T3dERBPkMEnJr8fvShtPBsFScRkir0lzJZQkpvrrct0.cache +0 -0
  61. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/UTAd4qlYa57uaORsIWcp6VqcAUkRzixYzIGoEFCvCrY.cache +3 -0
  62. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/UoWufV0QdgwAuVnwDXNWOQfgjGN6U7KnO2_2nXC5gHg.cache +0 -0
  63. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/WpToyjdG3xfOFtl4HJYTpy4_w0U40f7qQWUm7d2SDxg.cache +1 -0
  64. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/XwozjE91sFB4cQGoJUgr2jV1ZuRlvN_HTWgpclrD59A.cache +3 -0
  65. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/YAzUmPkKnALIIJE_0Meb0N5qTlBvVE1ront_B8yecfo.cache +1 -0
  66. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/Yuxa44Ctnn_uPp1nr47NdNwltrmTEp0PleFL3ylXbtM.cache +4 -0
  67. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/ZEUypIim5xuBK679jyb340c39nHQ3EvcgqbmeiHlT0c.cache +2 -0
  68. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/ZQf7Hy4n2V3c-98b9kqTrZAcw3B2TDRF3IKPm9I7VjY.cache +0 -0
  69. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/_SIN6dUlHiJyeUSpOPWAgfLfMrtjGlZgx8ZLEbkjLuQ.cache +1 -0
  70. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/_TUGtF7Vi9blFNr3awt9kgCoid35NdmY8hSHmxuaWbg.cache +2 -0
  71. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/_eb4NkdcbQR63aZySRq7Yv5_w_JReWqG2Q5mYA5u62s.cache +0 -0
  72. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/bPiSNDFYlW5YDua0D-fC4uPRV8hYABbCor092KHNzHM.cache +0 -0
  73. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/d3_09TJ2Uoh7sdjvHWw4jPVF0bObKu065NmgG-EIWcA.cache +0 -0
  74. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/dQTSoOSXec5z_sf_i4UrehHxFRAn4HbRvfh8mUEK2ME.cache +1 -0
  75. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/fVIC-zRTWj6S5SLH_vJQJY-ny5le0IaRQda1YLQQr68.cache +0 -0
  76. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/hpNHCD7sn3wufa6PLUdFP95AS8nwrAaL_b9K4X9_eCE.cache +1 -0
  77. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/huDMU1soBHMNsbUDXnTPbLtqvAEYPe6E-tLL9phYOBY.cache +2 -0
  78. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/i43TdJOVKXtyejasCbHJP4U34FcJ5siCLFWpnPWxxU4.cache +1 -0
  79. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/i4gUJwKC59hdrcb7ZaO09fNtKexVsb67-y3MZ7twsyI.cache +2 -0
  80. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/iynGcjYbH69ZuxdS02RrsK2y33uPjFHCrYHRxxNVn_k.cache +0 -0
  81. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/jR1PEBCdtdy135gPG-zLQDg25XQDP9eDBew7pmhz34k.cache +0 -0
  82. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/jvNKAnuW3TSUu3LxRcqdtmSHgoGiukm1onfXqD6glQA.cache +1 -0
  83. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/kOBlorvGjGDVFhapT1HrAw48XziyeSqexuiKpVhDMac.cache +1 -0
  84. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/mS-IpVGnBSf2F0bGZ7OPpXNCcNMtHmxm7TI1hHVlvps.cache +2 -0
  85. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/pKsozyUJjXN2tIwiCbkGt9AzwLwdpHCYC1Cib-9GBIo.cache +3 -0
  86. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/pP2ApxUxxz-JMscfDHnlZ4Hq4DybYrG_9B4npoDMGAE.cache +2 -0
  87. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/r9dDy892QJeXi_CLvyTWZvGA9idLCbq9X0lnwxr9RZQ.cache +1 -0
  88. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rLeviNgrP2-MqSDlwBdtz3ciY9n32a5oY9NY3iQupsg.cache +1 -0
  89. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/rf04rP-66AEEH942fvU_IcXRtrpw1_xbvnaxHD2wZGA.cache +2 -0
  90. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/uSWRYxEuSTYl_g1MvbE90G-h747P1dyWDCSZY0z1yzA.cache +2 -0
  91. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/uifNaYvk6KYC76GaHQ9muL_ObOmw4C_CSl9lzOUfnDE.cache +3 -0
  92. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/yOVPU3Y8NJQjJYfkK-l7I_DPfd3T4mIccYc6gY6ODBQ.cache +1 -0
  93. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/yZA3xwYBnfhABCzMVYKNfJMWpJg4HEovQnr5t48Yrf0.cache +1 -0
  94. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/y_XqYDfeQ0fehcC3FNz-qYXXcwOxtSDfsJX1nStC1qU.cache +1 -0
  95. data/spec/dummy/tmp/cache/assets/test/sprockets/v3.0/z_w2fR4GzVyUeMzrOEZKQSykA7SSjpdpLtlBgXEgcUA.cache +5 -0
  96. data/spec/fixtures/resources.yml +14 -0
  97. data/spec/fixtures/share_models.yml +8 -0
  98. data/spec/fixtures/users.yml +11 -0
  99. data/spec/models/share_model_spec.rb +148 -0
  100. data/spec/spec_helper.rb +20 -0
  101. 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,7 @@
1
+ #
2
+ # Define the versions of the gem.
3
+ #
4
+ module ShareableModels
5
+ # Version of the gem
6
+ VERSION = '0.5.0'
7
+ 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
@@ -0,0 +1,3 @@
1
+ require File.expand_path('../config/application', __FILE__)
2
+
3
+ Dummy::Application.load_tasks
@@ -0,0 +1,8 @@
1
+ #
2
+ # Resource that users can share
3
+ #
4
+ class Resource < ActiveRecord::Base
5
+ shareable owner: :user
6
+ # Owner
7
+ belongs_to :user
8
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # Basic user model
3
+ #
4
+ class User < ActiveRecord::Base
5
+ sharer
6
+ # Resources user create
7
+ has_many :resources
8
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ # Set up gems listed in the Gemfile.
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3
+
4
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5
+ $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)