markable 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/README.md +199 -0
  2. data/Rakefile +38 -0
  3. data/lib/generators/markable/migration/migration_generator.rb +39 -0
  4. data/lib/generators/markable/migration/templates/active_record/migration.rb +17 -0
  5. data/lib/markable.rb +95 -0
  6. data/lib/markable/acts_as_markable.rb +155 -0
  7. data/lib/markable/acts_as_marker.rb +62 -0
  8. data/lib/markable/exceptions.rb +7 -0
  9. data/lib/markable/version.rb +3 -0
  10. data/lib/models/mark.rb +6 -0
  11. data/test/acts_as_markable_test.rb +219 -0
  12. data/test/acts_as_marker_test.rb +165 -0
  13. data/test/dummy/README.rdoc +261 -0
  14. data/test/dummy/Rakefile +7 -0
  15. data/test/dummy/app/assets/javascripts/application.js +15 -0
  16. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  17. data/test/dummy/app/controllers/application_controller.rb +3 -0
  18. data/test/dummy/app/helpers/application_helper.rb +2 -0
  19. data/test/dummy/app/models/admin.rb +4 -0
  20. data/test/dummy/app/models/drink.rb +3 -0
  21. data/test/dummy/app/models/food.rb +4 -0
  22. data/test/dummy/app/models/user.rb +4 -0
  23. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  24. data/test/dummy/config.ru +4 -0
  25. data/test/dummy/config/application.rb +56 -0
  26. data/test/dummy/config/boot.rb +10 -0
  27. data/test/dummy/config/database.yml +25 -0
  28. data/test/dummy/config/environment.rb +5 -0
  29. data/test/dummy/config/environments/development.rb +37 -0
  30. data/test/dummy/config/environments/production.rb +67 -0
  31. data/test/dummy/config/environments/test.rb +37 -0
  32. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/test/dummy/config/initializers/inflections.rb +15 -0
  34. data/test/dummy/config/initializers/mime_types.rb +5 -0
  35. data/test/dummy/config/initializers/secret_token.rb +7 -0
  36. data/test/dummy/config/initializers/session_store.rb +8 -0
  37. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  38. data/test/dummy/config/locales/en.yml +5 -0
  39. data/test/dummy/config/routes.rb +58 -0
  40. data/test/dummy/db/development.sqlite3 +0 -0
  41. data/test/dummy/db/migrate/20120214103734_create_users.rb +9 -0
  42. data/test/dummy/db/migrate/20120214103741_create_admins.rb +9 -0
  43. data/test/dummy/db/migrate/20120214105411_create_foods.rb +9 -0
  44. data/test/dummy/db/migrate/20120214105421_create_drinks.rb +9 -0
  45. data/test/dummy/db/migrate/20120214114957_markable_migration.rb +17 -0
  46. data/test/dummy/db/production.sqlite3 +0 -0
  47. data/test/dummy/db/schema.rb +52 -0
  48. data/test/dummy/db/test.sqlite3 +0 -0
  49. data/test/dummy/log/development.log +134 -0
  50. data/test/dummy/log/production.log +0 -0
  51. data/test/dummy/log/test.log +93646 -0
  52. data/test/dummy/public/404.html +26 -0
  53. data/test/dummy/public/422.html +26 -0
  54. data/test/dummy/public/500.html +25 -0
  55. data/test/dummy/public/favicon.ico +0 -0
  56. data/test/dummy/script/rails +6 -0
  57. data/test/dummy/test/fixtures/admins.yml +7 -0
  58. data/test/dummy/test/fixtures/drinks.yml +7 -0
  59. data/test/dummy/test/fixtures/foods.yml +7 -0
  60. data/test/dummy/test/fixtures/users.yml +7 -0
  61. data/test/dummy/test/unit/admin_test.rb +7 -0
  62. data/test/dummy/test/unit/drink_test.rb +7 -0
  63. data/test/dummy/test/unit/food_test.rb +7 -0
  64. data/test/dummy/test/unit/user_test.rb +7 -0
  65. data/test/support/get.rb +8 -0
  66. data/test/test_helper.rb +10 -0
  67. metadata +196 -0
@@ -0,0 +1,199 @@
1
+ # Markable
2
+
3
+ Do your users want to add food, drinks, books, movies, and many
4
+ other stuff to favorites?
5
+
6
+ _— How should I name this table — users_foods or foods_users?_<br/>
7
+ _— Damn, I forget to add ```:id => false``` to migration again!_<br/>
8
+ _— Holy sh*t! I have to create a lot of tables for this relations!_
9
+
10
+ Nope, you don't have to create relations or think about migrations — **markable** will handle this.
11
+
12
+ Should your users be able to add each other to friends? Or do you want to
13
+ make a twitter-like followers system?
14
+
15
+ _— Oh… Self-relation again…_
16
+
17
+ Nope, **markable** can handle it too!
18
+
19
+
20
+
21
+ ##Installation
22
+
23
+ Add to your Gemfile
24
+
25
+ ```
26
+ gem 'markable'
27
+ ```
28
+
29
+ Run
30
+
31
+ ```
32
+ bundle install
33
+ rails generate markable:migration
34
+ rake db:migrate
35
+ ```
36
+
37
+ ## Usage
38
+
39
+ At first you should define markers — these are models which will mark
40
+ other models.
41
+
42
+ If User can mark Food as favorite, then User — is a marker.
43
+
44
+ ``` ruby
45
+ class User < ActiveRecord::Base
46
+ acts_as_marker
47
+ end
48
+ ```
49
+
50
+ Then you should define markables
51
+
52
+ ``` ruby
53
+ class Food < ActiveRecord::Base
54
+ markable_as :favorite
55
+ end
56
+ ```
57
+
58
+ Thats it! Now you can mark pizza as a favorite food of your user
59
+
60
+ ``` ruby
61
+ user.set_mark :favorite, pizza
62
+ # or
63
+ user.mark_as_favorite pizza
64
+ # or
65
+ user.favorite_foods << pizza
66
+ # yes, I know that "food" doesn't have a plural form, but rails don't :(
67
+ ```
68
+
69
+ _— Also I hate broccoli!_
70
+
71
+ As you wish! Just a little change to your Food model
72
+
73
+ ``` ruby
74
+ class Food < ActiveRecord::Base
75
+ markable_as [ :hated, :favorite ]
76
+ end
77
+ ```
78
+
79
+ and The World will know, what kind of food do you hate.
80
+
81
+ ``` ruby
82
+ user.hated_foods << broccoli
83
+ ```
84
+
85
+ You can easily get list of all foods marked as favorite by your user
86
+
87
+ ``` ruby
88
+ user.foods_marked_as :favorite
89
+ # or
90
+ user.foods_marked_as_favorite
91
+ # or
92
+ user.favorite_foods
93
+ ```
94
+
95
+ Also you can get a list of all users, who loves pizza too!
96
+
97
+ ``` ruby
98
+ pizza.users_have_marked_as :favorite
99
+ # or
100
+ pizza.users_have_marked_as_favorite
101
+ ```
102
+
103
+ And all foods loved by users
104
+
105
+ ``` ruby
106
+ Food.marked_as :favorite
107
+ # or
108
+ Food.marked_as_favorite
109
+ ```
110
+
111
+ _— Hmm… What kind of food my friends likes?_
112
+
113
+ ``` ruby
114
+ Food.marked_as_favorite :by => [ user1, user2, user3 ]
115
+ ```
116
+
117
+ _— Hey! I have found a users who loves pizza too — I want to be friends with them!_
118
+
119
+ No problem! Just make User markable as friendly!
120
+
121
+ ``` ruby
122
+ class User < ActiveRecord::Base
123
+ acts_as_marker
124
+ markable_as :friendly, :by => :user
125
+ end
126
+ ```
127
+
128
+ And now you can add all pizza-lovers to your friends
129
+
130
+ ``` ruby
131
+ user.friendly_users << pizza.users_have_marked_as_favorite
132
+ ```
133
+
134
+ Piece of cake!
135
+
136
+ ## All Methods
137
+ ``` ruby
138
+ class User < ActiveRecord::Base
139
+ acts_as_marker
140
+ end
141
+ class Food < ActiveRecord::Base
142
+ markable_as :favorite
143
+ end
144
+ ```
145
+
146
+ ``` ruby
147
+ # Getters
148
+ user.favorite_foods # => [food1, food2, …]
149
+ user.foods_marked_as :favorite # => [food1, food2, …]
150
+ user.foods_marked_as_favorite # => [food1, food2, …]
151
+
152
+ food.users_have_marked_as :favorite # => [user1, user2, …]
153
+ food.users_have_marked_as_favorite # => [user1, user2, …]
154
+
155
+ Food.marked_as :favorite # => [food1, food2, …]
156
+ Food.marked_as :favorite, :by => user1 # => [food1, food2, …]
157
+ Food.marked_as_favorite # => [food1, food2, …]
158
+ Food.marked_as_favorite :by => user1 # => [food1, food2, …]
159
+
160
+ # Setters
161
+ user.favorite_foods << [food1, food2]
162
+ user.foods_marked_as(:favorite) << [food1, food2]
163
+ user.foods_marked_as_favorite << [food1, food2]
164
+ user.mark_as_favorite [food1, food2]
165
+ user.set_mark :favorite, [food1, food2]
166
+
167
+ food.users_have_marked_as(:favorite) << [user1, user2]
168
+ food.users_have_marked_as_favorite << [user1, user2]
169
+ food.mark_as :favorite, [user1, user2]
170
+
171
+ # Removals
172
+ food.unmark :favorite
173
+ food.unmark :favorite, :by => user1
174
+ food.users_have_marked_as(:favorite).delete [food1, food2]
175
+ food.users_have_marked_as_favorite.delete [food1, food2]
176
+ user.remove_mark :favorite, [food1, food2]
177
+ user.favorite_foods.delete [food1, food2]
178
+
179
+ # Check
180
+ food.marked_as? :favorite
181
+ food.marked_as_favorite?
182
+ ```
183
+
184
+ ##Usage examples
185
+
186
+ You can find some usage examples at wiki page: [Usage examples](https://github.com/chrome/markable/wiki/Usage-examples)
187
+
188
+ ## License
189
+
190
+ The MIT License
191
+
192
+ Copyright (c) 2012 Alex Chrome
193
+
194
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
195
+
196
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
197
+
198
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
199
+
@@ -0,0 +1,38 @@
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
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Markable'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.md')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,39 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module Markable
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ desc "Generates migration for Mark model"
9
+
10
+ def self.orm
11
+ Rails::Generators.options[:rails][:orm]
12
+ end
13
+
14
+ def self.source_root
15
+ File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
16
+ end
17
+
18
+ def self.orm_has_migration?
19
+ [:active_record].include? orm
20
+ end
21
+
22
+ def self.next_migration_number(dirname)
23
+ if ActiveRecord::Base.timestamped_migrations
24
+ migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
25
+ migration_number += 1
26
+ migration_number.to_s
27
+ else
28
+ "%.3d" % (current_migration_number(dirname) + 1)
29
+ end
30
+ end
31
+
32
+ def create_migration_file
33
+ if self.class.orm_has_migration?
34
+ migration_template 'migration.rb', 'db/migrate/markable_migration'
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,17 @@
1
+ class MarkableMigration < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :marks, :id => false do |t|
4
+ t.references :marker, :polymorphic => true
5
+ t.references :markable, :polymorphic => true
6
+ t.string :mark, :limit => 128
7
+ t.datetime :created_at
8
+ end
9
+
10
+ add_index :marks, [:markable_id, :markable_type, :mark]
11
+ add_index :marks, [:marker_id, :marker_type, :mark]
12
+ end
13
+
14
+ def self.down
15
+ drop_table :marks
16
+ end
17
+ end
@@ -0,0 +1,95 @@
1
+ require 'models/mark'
2
+
3
+
4
+ module Markable
5
+ mattr_accessor :markers, :markables, :models
6
+ @@markers = []
7
+ @@markables = []
8
+ @@models = []
9
+ @@marker_objects = []
10
+ @@markable_objects = []
11
+
12
+ protected
13
+
14
+ def self.set_models models
15
+ @@models = models unless @@models.count > 0
16
+ end
17
+
18
+ def self.add_markable markable
19
+ @@markable_objects.push markable
20
+ @@markables.push markable.name.to_sym
21
+ create_methods @@marker_objects, [ markable ]
22
+ end
23
+
24
+ def self.add_marker marker
25
+ @@marker_objects.push marker
26
+ @@markers.push marker.name.to_sym
27
+ create_methods [ marker ], @@markable_objects
28
+ end
29
+
30
+ def self.create_methods markers, markables
31
+ markables.try :each do |markable|
32
+ markers.try :each do |marker|
33
+ markable.markable_marks.each { |mark, options|
34
+ if options[:allowed_markers] == :all || options[:allowed_markers].include?(marker.marker_name)
35
+ markable_name = markable.name.downcase
36
+ method_name = "#{mark}_#{markable_name}".pluralize
37
+ marker.class_eval %(
38
+ def #{method_name}
39
+ #{markable.name}.marked_as :#{mark}, :by => self
40
+ end
41
+ def #{markable_name.pluralize}_marked_as mark
42
+ #{markable.name}.marked_as mark, :by => self
43
+ end
44
+ def #{markable_name.pluralize}_marked_as_#{mark}
45
+ #{markable.name}.marked_as :#{mark}, :by => self
46
+ end
47
+ )
48
+ unless marker.methods.include?("mark_as_#{mark}".to_sym)
49
+ marker.class_eval %(
50
+ def mark_as_#{mark}(objects)
51
+ self.set_mark :#{mark}, objects
52
+ end
53
+ )
54
+ end
55
+ markable.class_eval %(
56
+ def #{marker.marker_name.to_s.pluralize}_have_marked_as mark
57
+ self.have_marked_as_by(mark, #{marker.name})
58
+ end
59
+
60
+ def #{marker.marker_name.to_s.pluralize}_have_marked_as_#{mark}
61
+ self.have_marked_as_by(:#{mark}, #{marker.name})
62
+ end
63
+ )
64
+ end
65
+ }
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.can_mark_or_raise? marker_object, markables, mark
71
+ unless self.can_mark? marker_object, markables, mark
72
+ raise Markable::WrongMarkableType.new
73
+ end
74
+ true
75
+ end
76
+
77
+ def self.can_mark? markers, markables, mark
78
+ markables = [ markables ] unless markables.kind_of? Array
79
+ markers = [ markers ] unless markers.kind_of? Array
80
+ markers.all? { |marker_object| markables.all? { |markable| self.can_mark_object?(marker_object, markable, mark) } }
81
+ end
82
+
83
+ def self.can_mark_object? marker_object, markable_object, mark
84
+ marker_name = marker_object.class.name.to_sym
85
+ markable_name = markable_object.class.name.to_sym
86
+
87
+ @@markers.include?(marker_name) && @@markables.include?(markable_name) && markable_object.markable_marks.include?(mark) &&
88
+ (markable_object.markable_marks[mark][:allowed_markers] == :all || markable_object.markable_marks[mark][:allowed_markers].include?(marker_name.to_s.downcase.to_sym))
89
+ end
90
+ end
91
+
92
+ require 'markable/exceptions'
93
+ require 'markable/acts_as_marker'
94
+ require 'markable/acts_as_markable'
95
+
@@ -0,0 +1,155 @@
1
+ module Markable
2
+ module ActsAsMarkable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do |a|
6
+ end
7
+
8
+ module ClassMethods
9
+ def markable_as(marks, options = {})
10
+ Markable.set_models ActiveRecord::Base.connection.tables.collect{|t| t.classify rescue nil }.compact
11
+
12
+ cattr_accessor :markable_marks
13
+
14
+ if options[:by]
15
+ markers = options[:by].kind_of?(Array) ? options[:by].map { |i| i.to_sym } : [ options[:by].to_sym ]
16
+ else
17
+ markers = :all
18
+ end
19
+
20
+ self.markable_marks ||= {}
21
+ marks = [ marks ] unless marks.kind_of? Array
22
+ marks.each { |mark|
23
+ self.markable_marks[ mark.to_sym ] = {
24
+ :allowed_markers => markers
25
+ }
26
+ }
27
+
28
+ class_eval do
29
+ has_many :markable_marks, :class_name => 'Markable::Mark', :as => :markable
30
+ include Markable::ActsAsMarkable::MarkableInstanceMethods
31
+
32
+ def self.marked_as mark, options = {}
33
+ if options[:by]
34
+ result = self.joins(:markable_marks).where( :marks => { :mark => mark, :marker_id => options[:by].id, :marker_type => options[:by].class.name } )
35
+ markable = self
36
+ result.class_eval do
37
+ define_method :<< do |object|
38
+ if object.kind_of?(markable) || (object.kind_of?(Array) && object.all?{ |i| i.kind_of?(markable) })
39
+ options[:by].set_mark mark, object
40
+ else
41
+ raise Markable::WrongMarkableType.new
42
+ end
43
+ end
44
+ define_method :delete do |markable|
45
+ options[:by].remove_mark mark, markable
46
+ end
47
+ end
48
+ else
49
+ result = self.joins(:markable_marks).where( :marks => { :mark => mark } )
50
+ end
51
+ result
52
+ end
53
+ end
54
+
55
+ self.markable_marks.each { |mark, o|
56
+ class_eval %(
57
+ def self.marked_as_#{mark}(options = {})
58
+ self.marked_as :#{mark}, options
59
+ end
60
+
61
+ def marked_as_#{mark}? options = {}
62
+ self.marked_as? :#{mark}, options
63
+ end
64
+ )
65
+ }
66
+
67
+ Markable.add_markable self
68
+ end
69
+ end
70
+
71
+ module MarkableInstanceMethods
72
+ def mark_as(mark, markers)
73
+ markers = [ markers ] unless markers.kind_of? Array
74
+ markers.each { |marker|
75
+ Markable.can_mark_or_raise? marker, self, mark
76
+ params = {
77
+ :markable_id => self.id,
78
+ :markable_type => self.class.name,
79
+ :marker_id => marker.id,
80
+ :marker_type => marker.class.name,
81
+ :mark => mark
82
+ }
83
+ Markable::Mark.create( params ) unless Markable::Mark.exists?( params )
84
+ }
85
+ true
86
+ end
87
+
88
+ def marked_as?(mark, options = {})
89
+ if options[:by]
90
+ Markable.can_mark_or_raise? options[:by], self, mark
91
+ end
92
+ params = {
93
+ :markable_id => self.id,
94
+ :markable_type => self.class.name,
95
+ :mark => mark
96
+ }
97
+ if options[:by]
98
+ params[:marker_id] = options[:by].id
99
+ params[:marker_type] = options[:by].class.name
100
+ end
101
+ Markable::Mark.exists?( params )
102
+ end
103
+
104
+ def unmark mark, options = {}
105
+ if options[:by]
106
+ Markable.can_mark_or_raise? options[:by], self, mark
107
+ markers = options[:by].kind_of?(Array) ? options[:by] : [ options[:by] ]
108
+ markers.each { |marker|
109
+ params = {
110
+ :markable_id => self.id,
111
+ :markable_type => self.class.name,
112
+ :marker_id => marker.id,
113
+ :marker_type => marker.class.name,
114
+ :mark => mark
115
+ }
116
+ Markable::Mark.delete_all(params)
117
+ }
118
+ else
119
+ params = {
120
+ :markable_id => self.id,
121
+ :markable_type => self.class.name,
122
+ :mark => mark
123
+ }
124
+ Markable::Mark.delete_all(params)
125
+ end
126
+ true
127
+ end
128
+
129
+ def have_marked_as_by(mark, target)
130
+ result = target.joins(:marker_marks).where( :marks => { :mark => mark, :markable_id => self.id, :markable_type => self.class.name } )
131
+ markable = self
132
+ result.class_eval do
133
+ define_method :<< do |markers|
134
+ markers = [ markers ] unless markers.kind_of? Array
135
+ markers.each { |marker|
136
+ marker.set_mark mark, markable
137
+ }
138
+ self
139
+ end
140
+ define_method :delete do |markers|
141
+ Markable.can_mark_or_raise? markers, markable, mark
142
+ markers = [ markers ] unless markers.kind_of? Array
143
+ markers.each { |marker|
144
+ marker.remove_mark mark, markable
145
+ }
146
+ self
147
+ end
148
+ end
149
+ result
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ ActiveRecord::Base.send :include, Markable::ActsAsMarkable