padrino-admin 0.6.3 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/README.rdoc +16 -0
  2. data/VERSION +1 -1
  3. data/lib/padrino-admin.rb +24 -5
  4. data/lib/padrino-admin/access_control.rb +75 -29
  5. data/lib/padrino-admin/{ext_js/column_store.rb → column_store.rb} +31 -15
  6. data/lib/padrino-admin/config.rb +36 -0
  7. data/lib/padrino-admin/generators/actions.rb +62 -7
  8. data/lib/padrino-admin/generators/admin_app.rb +27 -26
  9. data/lib/padrino-admin/generators/admin_page.rb +20 -18
  10. data/lib/padrino-admin/generators/admin_uploader.rb +83 -0
  11. data/lib/padrino-admin/generators/app/app.rb.tt +1 -3
  12. data/lib/padrino-admin/generators/app/controllers/accounts.rb +1 -1
  13. data/lib/padrino-admin/generators/app/public/flash/swfupload.swf +0 -0
  14. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/back.gif +0 -0
  15. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/background.png +0 -0
  16. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-content.png +0 -0
  17. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-hd-slate.png +0 -0
  18. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-hd.png +0 -0
  19. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-intro.png +0 -0
  20. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-login.png +0 -0
  21. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-menu-slate.png +0 -0
  22. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg-menu.png +0 -0
  23. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/bg.png +0 -0
  24. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/btn-login.png +0 -0
  25. data/lib/padrino-admin/generators/app/public/images/admin/cancel.gif +0 -0
  26. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/categories.gif +0 -0
  27. data/lib/padrino-admin/generators/app/public/images/admin/close.gif +0 -0
  28. data/lib/padrino-admin/generators/app/public/images/admin/close.png +0 -0
  29. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/delete.gif +0 -0
  30. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/download.gif +0 -0
  31. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/duplicate.gif +0 -0
  32. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/edit.gif +0 -0
  33. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/export.gif +0 -0
  34. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/hd-bg.gif +0 -0
  35. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/image.gif +0 -0
  36. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/loader.gif +0 -0
  37. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/logo-loader.png +0 -0
  38. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/logo-small.png +0 -0
  39. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/new.gif +0 -0
  40. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/no-image.png +0 -0
  41. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/preview.gif +0 -0
  42. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/print.gif +0 -0
  43. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/save.gif +0 -0
  44. data/lib/padrino-admin/generators/app/public/images/{backend → admin}/support.gif +0 -0
  45. data/lib/padrino-admin/generators/app/public/images/admin/up.gif +0 -0
  46. data/lib/padrino-admin/generators/app/public/javascripts/ext.js +4 -1
  47. data/lib/padrino-admin/generators/app/public/javascripts/swfupload.js +4 -0
  48. data/lib/padrino-admin/generators/app/public/stylesheets/admin.css +25 -43
  49. data/lib/padrino-admin/generators/app/public/stylesheets/login.css +3 -3
  50. data/lib/padrino-admin/generators/app/public/stylesheets/standard.css +53 -52
  51. data/lib/padrino-admin/generators/app/views/accounts/_form.haml +1 -0
  52. data/lib/padrino-admin/generators/app/views/base/index.haml +5 -7
  53. data/lib/padrino-admin/generators/app/views/javascripts/admin.js.erb +217 -297
  54. data/lib/padrino-admin/generators/app/views/sessions/new.haml +1 -1
  55. data/lib/padrino-admin/generators/templates/{controller.rb.tt → page/controller.rb.tt} +1 -1
  56. data/lib/padrino-admin/generators/templates/{db → page/db}/seeds.rb.tt +0 -0
  57. data/lib/padrino-admin/generators/templates/{views → page/views}/_form.haml.tt +0 -0
  58. data/lib/padrino-admin/generators/templates/{views → page/views}/edit.haml.tt +0 -0
  59. data/lib/padrino-admin/generators/templates/{views → page/views}/grid.js.erb.tt +1 -1
  60. data/lib/padrino-admin/generators/templates/{views → page/views}/new.haml.tt +0 -0
  61. data/lib/padrino-admin/generators/templates/{views → page/views}/store.jml.tt +0 -0
  62. data/lib/padrino-admin/generators/templates/uploader/controller.rb +24 -0
  63. data/lib/padrino-admin/generators/templates/uploader/lib/uploader.rb +54 -0
  64. data/lib/padrino-admin/generators/templates/uploader/views/grid.js.erb +56 -0
  65. data/lib/padrino-admin/generators/templates/uploader/views/store.jml +10 -0
  66. data/lib/padrino-admin/helpers/authentication.rb +30 -19
  67. data/lib/padrino-admin/helpers/view.rb +172 -35
  68. data/lib/padrino-admin/locale/admin/en.yml +1 -0
  69. data/lib/padrino-admin/middleware/flash_middleware.rb +36 -0
  70. data/lib/padrino-admin/orm.rb +33 -0
  71. data/lib/padrino-admin/orm/abstract.rb +94 -0
  72. data/lib/padrino-admin/{adapters/ar.rb → orm/activerecord.rb} +69 -36
  73. data/lib/padrino-admin/orm/datamapper.rb +214 -0
  74. data/lib/padrino-admin/{adapters/mm.rb → orm/mongomapper.rb} +36 -20
  75. data/lib/padrino-admin/utils/literal.rb +1 -1
  76. data/padrino-admin.gemspec +62 -51
  77. data/test/fixtures/active_record.rb +14 -2
  78. data/test/fixtures/data_mapper.rb +2 -1
  79. data/test/fixtures/mongo_mapper.rb +1 -1
  80. data/test/fixtures/test_column_store.jml +1 -0
  81. data/test/helper.rb +3 -2
  82. data/test/test_access_control.rb +1 -1
  83. data/test/test_active_record.rb +56 -1
  84. data/test/test_column_store.rb +56 -10
  85. data/test/test_data_mapper.rb +67 -1
  86. metadata +58 -47
  87. data/lib/padrino-admin/adapters.rb +0 -108
  88. data/lib/padrino-admin/adapters/dm.rb +0 -147
  89. data/lib/padrino-admin/ext_js/config.rb +0 -186
  90. data/test/fixtures/test_generic.jml +0 -7
  91. data/test/fixtures/test_javascript.jml +0 -81
@@ -39,6 +39,7 @@ en:
39
39
  remove: "Delete"
40
40
  back: "Back"
41
41
  save: "Save"
42
+ save_and_back: "Save & Back"
42
43
  close: "Close"
43
44
  select: "Select"
44
45
  help: "Help"
@@ -0,0 +1,36 @@
1
+ require 'rack/utils'
2
+
3
+ module Padrino
4
+ module Admin
5
+ module Middleware
6
+ ##
7
+ # FlashSessionCookieMiddleware
8
+ # passing your session in the URI, when it should be in the cookie
9
+ #
10
+ # This code only works in following cases:
11
+ # - passing the session as the variable for 'session_key' (it is best to set this to your app's session cookie name
12
+ # - the value is URI escaped once, (don't do it explicetally if using rails helpers, they do it for you)
13
+ # - Loading this middleware before session_store middleware
14
+ #
15
+ # Note, this could work also after session_store middleware (or others).
16
+ # However, these could already have modified the cooky values, and this module
17
+ # could become unstable because of that, and it's functioning can not be
18
+ # guaranteed.
19
+ #
20
+ class FlashMiddleware
21
+ def initialize(app, session_key = 'session_id')
22
+ @app = app
23
+ @session_key = session_key.to_s
24
+ end
25
+
26
+ def call(env)
27
+ if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
28
+ params = ::Rack::Request.new(env).params
29
+ env['rack.session'][@session_key.to_sym] = params[@session_key] unless params[@session_key].nil?
30
+ end
31
+ @app.call(env)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,33 @@
1
+ module Padrino
2
+ module Admin
3
+
4
+ module Orm
5
+
6
+ class ExtSearch < Struct.new(:count, :records); end
7
+
8
+ ##
9
+ # Method used for register the orm extensions.
10
+ #
11
+ def self.register!
12
+ ::DataMapper::Model.append_inclusions(Padrino::Admin::Orm::DataMapper::Base) if defined?(::DataMapper)
13
+ ::ActiveRecord::Base.send(:include, Padrino::Admin::Orm::ActiveRecord::Base) if defined?(::ActiveRecord)
14
+ ::MongoMapper::Document.append_inclusions(Padrino::Admin::Orm::MongoMapper::Base) if defined?(::MongoMapper)
15
+ # Extend also account model
16
+ self.extend_account!
17
+ end
18
+
19
+ ##
20
+ # This method it's used for extend Account Model (if present)
21
+ #
22
+ def self.extend_account!
23
+ if defined?(Account) && Account.respond_to?(:orm)
24
+ case Account.orm
25
+ when :activerecord then Account.send(:include, Padrino::Admin::Orm::ActiveRecord::Account)
26
+ when :datamapper then Account.send(:include, Padrino::Admin::Orm::DataMapper::Account)
27
+ when :mongomapper then Account.send(:include, Padrino::Admin::Orm::MongoMapper::Account)
28
+ end
29
+ end
30
+ end
31
+ end # Orm
32
+ end # Admin
33
+ end # Padrino
@@ -0,0 +1,94 @@
1
+ module Padrino
2
+ module Admin
3
+ class OrmError < StandardError; end
4
+
5
+ module Orm
6
+ module Abstract
7
+ ##
8
+ # In this module we have shared function that are shared
9
+ # with all orm.
10
+ #
11
+ module Base
12
+ def self.included(base) #:nodoc:
13
+ base.send :include, InstanceMethods
14
+ base.extend ClassMethods
15
+ end
16
+
17
+ module InstanceMethods
18
+ end
19
+
20
+ module ClassMethods
21
+ ##
22
+ # This method generate store and column config.
23
+ # for lazinies hands instead supply:
24
+ #
25
+ # Model.column_store("./../views/model/store.jml")
26
+ #
27
+ # you can:
28
+ #
29
+ # Model.column_store(options.views, "models/store")
30
+ def column_store(*args)
31
+ path = File.join(*args)
32
+ path = Dir[path + ".{jml,jaml}"].first.to_s if path !~ /(\.jml|\.jaml)$/
33
+ config = YAML.load_file(path)
34
+ Padrino::Admin::ColumnStore.new(self, config)
35
+ end
36
+ end
37
+ end # Abstract
38
+
39
+ ##
40
+ # Accoun Extensions
41
+ #
42
+ # An Account must have these column/fields:
43
+ #
44
+ # role:: String
45
+ # email:: String (used as login)
46
+ # crypted_password:: String
47
+ # salt:: String
48
+ #
49
+ # We add to Account model some methods:
50
+ #
51
+ # Account.authenticate(email, password):: check if exist an account for the given email and return it if password match
52
+ # account.password_clean:: return the decrypted password of an Account instance
53
+ #
54
+ module Account
55
+
56
+ def self.included(base) #:nodoc:
57
+ base.extend ClassMethods
58
+ base.send :include, InstanceMethods
59
+ end
60
+
61
+ module ClassMethods
62
+ ##
63
+ # This method it's for authentication purpose
64
+ #
65
+ def authenticate(email, password)
66
+ account = first(:conditions => { :email => email }) if email.present?
67
+ account && account.password_clean == password ? account : nil
68
+ end
69
+ end
70
+
71
+ module InstanceMethods
72
+ ##
73
+ # This method it's used for retrive the original password.
74
+ #
75
+ def password_clean
76
+ crypted_password.decrypt(salt)
77
+ end
78
+
79
+ private
80
+ def generate_password
81
+ return if password.blank?
82
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{email}--") if new_record?
83
+ self.crypted_password = password.encrypt(self.salt)
84
+ end
85
+
86
+ def password_required
87
+ crypted_password.blank? || !password.blank?
88
+ end
89
+ end
90
+ end # Account
91
+ end # Abstract
92
+ end # Orm
93
+ end # Admin
94
+ end # Padrino
@@ -1,54 +1,79 @@
1
1
  module Padrino
2
2
  module Admin
3
- module Adapters
4
- module Ar
5
-
3
+ module Orm
4
+ module ActiveRecord
5
+ ##
6
6
  # Here basic functions for interact with ActiveRecord
7
+ #
7
8
  module Base
8
- def self.included(base)
9
- base.send :include, Padrino::Admin::Adapters::Base
9
+
10
+ def self.included(base) #:nodoc:
11
+ base.send :include, Padrino::Admin::Orm::Abstract::Base
10
12
  base.send :include, InstanceMethods
11
13
  base.extend ClassMethods
12
14
  end
13
15
 
14
16
  module InstanceMethods
15
-
17
+ ##
16
18
  # Method for get only fields with errors
19
+ #
17
20
  def errors_keys
18
21
  errors.map { |k,v| k.to_sym }.uniq
19
22
  end
20
- end
23
+ end # InstanceMethods
21
24
 
22
25
  module ClassMethods
23
- # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
26
+ ##
27
+ # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name".
28
+ #
29
+ # Example:
24
30
  # Person.human_attribute_name("first_name") # => "First name"
25
- # This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
26
- # module now.
31
+ #
27
32
  # Specify +options+ with additional translating options.
28
- def human_attribute_name(attribute_key_name, options = {})
29
- defaults = self_and_descendants.map do |klass|
30
- :"#{klass.name.underscore}.#{attribute_key_name}"
31
- end
32
- defaults << options[:default] if options[:default]
33
- defaults.flatten!
34
- defaults << attribute_key_name.to_s.humanize
35
- options[:count] ||= 1
36
- I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:model, :attributes]))
33
+ #
34
+ def human_attribute_name(field, options = {})
35
+ options.reverse_merge!(:count => 1, :default => field.to_s.humanize, :scope => [:model, :attributes])
36
+ I18n.translate("#{self.name.underscore}.#{field}", options)
37
37
  end
38
38
 
39
- # Alias method for get columns
39
+ ##
40
+ # Alias method for get columns names
41
+ #
40
42
  def properties
41
43
  columns
42
44
  end
43
45
 
46
+ ##
47
+ # Return :activerecord
48
+ #
49
+ def orm
50
+ :activerecord
51
+ end
52
+
53
+ ##
54
+ # Method for perorm a full text search / sorting in ExtJS grids.
55
+ #
56
+ # For build a query you can provide for +params+:
57
+ #
58
+ # query:: word do search will be converted to "%word%"
59
+ # fields:: where you want search
60
+ # sort:: field to sort
61
+ # dir:: one of ASC/DESC
62
+ # limit:: limit your results
63
+ # start:: offset of your resluts
64
+ #
65
+ # For +query+ we mean standard adapter options such as +include+, +joins+ ...
66
+ #
67
+ # So a +ext_search+ can be:
68
+ #
69
+ # Account.ext_search({:query => "foo", fileds="name,surname,categories.name", :sort => "name",
70
+ # :dir => "asc", :limit => 50, :offset => 10 }, { :joins => :categories })
71
+ #
44
72
  def ext_search(params, query={})
45
73
 
46
74
  # We build a base struct for have some good results
47
75
  result = ExtSearch.new(0, [])
48
76
 
49
- # We need a basic query
50
- query = {}
51
-
52
77
  # Search conditions
53
78
  if params[:query].present? && params[:fields].present?
54
79
  filters = params[:fields].split(",").collect { |f| "#{f} LIKE ?" }.compact
@@ -62,23 +87,30 @@ module Padrino
62
87
 
63
88
  # Now time to limit/offset it
64
89
  query[:limit] = params[:limit].to_i if params[:limit].to_i > 0
65
- query[:offset] = params[:offset].to_i if params[:start].to_i > 0
90
+ query[:offset] = params[:start].to_i if params[:start].to_i > 0
66
91
 
67
92
  result.records = all(query)
68
93
  result
69
94
  end
70
95
 
71
- end
72
- end
96
+ end # ClassMethods
97
+ end # Base
73
98
 
74
- # Here extension for account for ActiveRecord
99
+ ##
100
+ # Here extension for Account for ActiveRecord
101
+ #
102
+ # Basically we need only to perform:
103
+ #
104
+ # * Validations (email, password, role)
105
+ # * Generate crypted_password on save
106
+ #
75
107
  module Account
76
- # Extend our class when included
77
- def self.included(base)
78
- base.send :include, Padrino::Admin::Adapters::AccountUtils
108
+
109
+ def self.included(base) #:nodoc:
110
+ base.send :include, Padrino::Admin::Orm::Abstract::Account
79
111
  base.send :attr_accessor, :password
80
112
  # Validations
81
- base.validates_presence_of :email
113
+ base.validates_presence_of :email, :role
82
114
  base.validates_presence_of :password, :if => :password_required
83
115
  base.validates_presence_of :password_confirmation, :if => :password_required
84
116
  base.validates_length_of :password, :within => 4..40, :if => :password_required
@@ -86,11 +118,12 @@ module Padrino
86
118
  base.validates_length_of :email, :within => 3..100
87
119
  base.validates_uniqueness_of :email, :case_sensitive => false
88
120
  base.validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
121
+ base.validates_format_of :role, :with => /[A-Za-z]/
89
122
  # Callbacks
90
123
  base.before_save :generate_password
91
124
  end
92
- end
93
- end
94
- end
95
- end
96
- end
125
+ end # Account
126
+ end # ActiveRecord
127
+ end # Orm
128
+ end # Admin
129
+ end # Padrino
@@ -0,0 +1,214 @@
1
+ module Padrino
2
+ module Admin
3
+ module Orm
4
+ module DataMapper
5
+ ##
6
+ # Here basic functions for interact with DataMapper
7
+ #
8
+ module Base
9
+
10
+ def self.included(base) #:nodoc:
11
+ base.send :include, Padrino::Admin::Orm::Abstract::Base
12
+ base.send :include, InstanceMethods
13
+ base.extend ClassMethods
14
+ end
15
+
16
+ module InstanceMethods
17
+ ##
18
+ # This is an alias method to allow us to don't see deprecations
19
+ # and keep compatibility with new DataMapper versions
20
+ #
21
+ def new_record?; new?; end
22
+
23
+ ##
24
+ # Returns a String, which Padrino uses for constructing an URL to this object.
25
+ # The default implementation returns this record‘s id as a String,
26
+ # or nil if this record‘s unsaved.
27
+ #
28
+ def to_param
29
+ # We can't use alias_method here, because method 'id' optimizes itself on the fly.
30
+ (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
31
+ end
32
+
33
+ ##
34
+ # Update attributes is deprecated but for compatibility with other ORM we support them.
35
+ #
36
+ def update_attributes(attributes = {})
37
+ update(attributes)
38
+ end
39
+
40
+ ##
41
+ # Method for get only fields with errors
42
+ #
43
+ def errors_keys
44
+ errors.keys
45
+ end
46
+
47
+ end # InstanceMethods
48
+
49
+ module ClassMethods
50
+ ##
51
+ # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name".
52
+ #
53
+ # Example:
54
+ # Person.human_attribute_name("first_name") # => "First name"
55
+ #
56
+ # Specify +options+ with additional translating options.
57
+ #
58
+ def human_attribute_name(field, options = {})
59
+ options.reverse_merge!(:count => 1, :default => field.to_s.humanize, :scope => [:model, :attributes])
60
+ I18n.translate("#{self.name.underscore}.#{field}", options)
61
+ end
62
+
63
+ ##
64
+ # Return the name of the Table
65
+ #
66
+ # Because in some unkown circumstances +storage_names[:default]+ give us an wrong value
67
+ # we use the class name for get the value
68
+ #
69
+ def table_name
70
+ self.name.underscore.pluralize
71
+ end
72
+
73
+ ##
74
+ # Return :activerecord
75
+ #
76
+ def orm
77
+ :datamapper
78
+ end
79
+
80
+ ##
81
+ # Method for perorm a full text search / sorting in ExtJS grids.
82
+ #
83
+ # For build a query you can provide for +params+:
84
+ #
85
+ # query:: word do search will be converted to "%word%"
86
+ # fields:: where you want search
87
+ # sort:: field to sort
88
+ # dir:: one of ASC/DESC
89
+ # limit:: limit your results
90
+ # start:: offset of your resluts
91
+ #
92
+ # For +query+ we mean standard adapter options such as +links+ ...
93
+ #
94
+ # So a +ext_search+ can be:
95
+ #
96
+ # Account.ext_search({:query => "foo", fileds="name,surname,categories.name", :sort => "name",
97
+ # :dir => "asc", :limit => 50, :offset => 10 }, { :links => :categories })
98
+ #
99
+ def ext_search(params, query={})
100
+
101
+ # We build a base struct for have some good results
102
+ result = ExtSearch.new(0, [])
103
+
104
+ if params[:query].present? && params[:fields].present?
105
+ # Here we build some like: ["name LIKE ?", "surname LIKE ?"]
106
+ fields = params[:fields].split(",").collect { |f| "#{f.strip.downcase} LIKE ?" }
107
+ # Here we build some like: ["name LIKE ? OR surname LIKE ?", "%foo%", "%foo%"]
108
+ query[:conditions] = [fields.join(" OR ")].concat(1.upto(fields.length).collect { "%#{params[:query]}%" })
109
+ end
110
+
111
+ # Now we can perform a count
112
+ result.count = count(query)
113
+
114
+ # First we need to sort our record but we have some problems if we sort for associated tables.
115
+ #
116
+ # see: http://www.mail-archive.com/datamapper@googlegroups.com/msg01310.html
117
+ #
118
+ # I can get these values:
119
+ #
120
+ # * accounts.name
121
+ # * accounts.posts.name
122
+ # * accounts.posts.categories.name
123
+ #
124
+ # We need to transform in some like [DataMapper::Query::Direction.new(Account.properties[:name], :asc)]
125
+ #
126
+ if params[:sort].present? && params[:dir].to_s =~ /^(asc|desc)$/i
127
+ values = params[:sort].to_s.sub(/^#{table_name}\./, "").split(".").collect(&:to_sym)
128
+ property = values.delete_at(-1) # the last value is always a property
129
+ relation = values.inject(self) do |relation, value|
130
+ raise "Unknow relation #{value} for #{relation}" unless relation.relationships[value]
131
+ relation.send(value)
132
+ end
133
+ target = relation.respond_to?(:model) ? relation.model.properties[property] : relation.properties[property]
134
+ query[:order] = [::DataMapper::Query::Direction.new(target, params[:dir].to_s.downcase.to_sym)]
135
+ end
136
+
137
+ # Now time to limit/offset it
138
+ query[:limit] = params[:limit].to_i if params[:limit].to_i > 0
139
+ query[:offset] = params[:start].to_i if params[:start].to_i > 0
140
+
141
+ # Now we can perform ording/limiting
142
+ result.records = all(query)
143
+ result
144
+ end
145
+
146
+ ##
147
+ # The aim of this method is act DM as AR, so when you define a many-to-many relation like:
148
+ #
149
+ # has n, :images
150
+ # has n, :images, :through => Resource
151
+ #
152
+ # you have two new methods:
153
+ #
154
+ # images_ids
155
+ # images_ids=(*ids)
156
+ #
157
+ def has(cardinality, name, *args)
158
+ relationship = super(cardinality, name, *args)
159
+ if relationship.is_a?(::DataMapper::Associations::ManyToMany::Relationship) ||
160
+ relationship.is_a?(::DataMapper::Associations::OneToMany::Relationship)
161
+
162
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
163
+ def #{name.to_s.singular}_ids
164
+ #{name}.collect(&:id)
165
+ end
166
+
167
+ def #{name.to_s.singular}_ids=(*ids)
168
+ self.#{name} = #{relationship.child_model.name}.all(:id => ids.flatten)
169
+ end
170
+ RUBY
171
+ end
172
+ relationship
173
+ end
174
+ end # ClassMethods
175
+ end # Base
176
+
177
+ ##
178
+ # Here extension for Account for MongoMapper
179
+ #
180
+ # Basically we need only to perform:
181
+ #
182
+ # * Validations (email, password, role)
183
+ # * Generate crypted_password on save
184
+ #
185
+ module Account
186
+
187
+ def self.included(base) #:nodoc:
188
+ super
189
+ base.send :include, Padrino::Admin::Orm::Abstract::Account
190
+ base.send :include, ::DataMapper::Validate
191
+ base.send :attr_accessor, :password, :password_confirmation
192
+ # Properties
193
+ base.property :email, String
194
+ base.property :crypted_password, String
195
+ base.property :salt, String
196
+ base.property :role, String
197
+ # Validations
198
+ base.validates_present :email, :role
199
+ base.validates_present :password, :if => :password_required
200
+ base.validates_present :password_confirmation, :if => :password_required
201
+ base.validates_length :password, :min => 4, :max => 40, :if => :password_required
202
+ base.validates_is_confirmed :password, :if => :password_required
203
+ base.validates_length :email, :min => 3, :max => 100
204
+ base.validates_is_unique :email, :case_sensitive => false
205
+ base.validates_format :email, :with => :email_address
206
+ base.validates_format :role, :with => /[A-Za-z]/
207
+ # Callbacks
208
+ base.before :save, :generate_password
209
+ end
210
+ end # Account
211
+ end # DataMapper
212
+ end # Orm
213
+ end # Admin
214
+ end # Padrino