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.
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__)