obitum-rails_admin 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/Gemfile +3 -0
  2. data/README.md +19 -69
  3. data/Rakefile +6 -1
  4. data/app/assets/javascripts/rails_admin/rails_admin.js.erb +10 -5
  5. data/app/controllers/rails_admin/main_controller.rb +9 -7
  6. data/config/initializers/mongoid_extensions.rb +4 -0
  7. data/lib/rails_admin/abstract_model.rb +55 -1
  8. data/lib/rails_admin/adapters/active_record.rb +20 -38
  9. data/lib/rails_admin/adapters/mongoid.rb +333 -0
  10. data/lib/rails_admin/adapters/mongoid/abstract_object.rb +32 -0
  11. data/lib/rails_admin/adapters/mongoid/extension.rb +27 -0
  12. data/lib/rails_admin/config/fields/base.rb +4 -4
  13. data/lib/rails_admin/config/fields/factories/belongs_to_association.rb +5 -2
  14. data/lib/rails_admin/config/fields/factories/serialized.rb +1 -1
  15. data/lib/rails_admin/config/fields/types.rb +2 -1
  16. data/lib/rails_admin/config/fields/types/all.rb +2 -0
  17. data/lib/rails_admin/config/fields/types/belongs_to_association.rb +1 -1
  18. data/lib/rails_admin/config/fields/types/bson_object_id.rb +42 -0
  19. data/lib/rails_admin/config/fields/types/enum.rb +2 -2
  20. data/lib/rails_admin/config/fields/types/mongoid_type.rb +25 -0
  21. data/lib/rails_admin/config/fields/types/polymorphic_association.rb +1 -1
  22. data/lib/rails_admin/config/fields/types/serialized.rb +1 -1
  23. data/lib/rails_admin/engine.rb +1 -0
  24. data/lib/rails_admin/version.rb +1 -1
  25. data/spec/dummy_app/Gemfile +2 -0
  26. data/spec/dummy_app/app/models/article.rb +9 -0
  27. data/spec/dummy_app/app/models/author.rb +6 -0
  28. data/spec/dummy_app/app/models/mongoid_field_test.rb +22 -0
  29. data/spec/dummy_app/app/models/tag.rb +7 -0
  30. data/spec/dummy_app/config/environments/development.rb +2 -2
  31. data/spec/dummy_app/config/mongoid.yml +17 -0
  32. data/spec/dummy_app/db/seeds.rb +7 -7
  33. data/spec/factories.rb +23 -0
  34. data/spec/integration/basic/create/rails_admin_basic_create_spec.rb +13 -0
  35. data/spec/integration/basic/update/rails_admin_basic_update_spec.rb +28 -0
  36. data/spec/spec_helper.rb +13 -0
  37. data/spec/support/tableless.rb +27 -0
  38. data/spec/unit/adapters/active_record_spec.rb +335 -37
  39. data/spec/unit/adapters/mongoid/abstract_object_spec.rb +30 -0
  40. data/spec/unit/adapters/mongoid_spec.rb +581 -0
  41. data/spec/unit/config/fields/base_spec.rb +9 -0
  42. metadata +280 -44
  43. data/app/assets/javascripts/rails_admin/jquery-ui-1.8.16.custom.js +0 -5271
data/Gemfile CHANGED
@@ -29,6 +29,8 @@ group :development, :test do
29
29
  end
30
30
  end
31
31
 
32
+ gem 'bson_ext'
33
+ gem 'mongoid'
32
34
  gem 'cancan'
33
35
  end
34
36
 
@@ -40,6 +42,7 @@ group :debug do
40
42
 
41
43
  platform :mri_19 do
42
44
  gem 'ruby-debug19'
45
+ gem 'simplecov', :require => false
43
46
  end
44
47
  end
45
48
 
data/README.md CHANGED
@@ -18,61 +18,6 @@ Hoeven][plukevdh], and [Rein Henrichs][reinh].
18
18
  [plukevdh]: https://github.com/plukevdh
19
19
  [reinh]: https://github.com/reinh
20
20
 
21
- ## <a name="announcements"></a>Announcements
22
-
23
- * Actions
24
-
25
- Custom actions are finally there. Default should stay put. To customize them, please have a look at:
26
-
27
-
28
- [Wiki: actions](https://github.com/sferik/rails_admin/wiki/Actions)<br>
29
- [Documented source code: Base Action class](https://github.com/sferik/rails_admin/blob/master/lib/rails_admin/config/actions/base.rb)
30
-
31
-
32
- * Translations
33
-
34
- Translations have been totally revamped (for custom actions)
35
-
36
- Translation keys for actions now look like:
37
-
38
- ```yaml
39
-
40
- en:
41
- admin:
42
- actions:
43
- <action_name>:
44
- title: "..."
45
- menu: "..."
46
- breadcrumb: "..."
47
-
48
- ```
49
-
50
- Other keys may have changed too.
51
-
52
- Old outdated translations have been removed, you must now fetch them from the wiki.
53
-
54
- See [Wiki](https://github.com/sferik/rails_admin/wiki/Translations) for more informations.
55
-
56
- Thanks a lot to all the translators sharing their work, sorry for the added worked (it was really needed).
57
-
58
- * History
59
-
60
- If you wish to continue using the old history feature, please add this to your initializer:
61
-
62
- ```ruby
63
- config.audit_with :history, User
64
- ```
65
-
66
- Alternatively, [PaperTrail](https://github.com/airblade/paper_trail) is now officially compatible. Install it, add `has_paper_trail` to the models you wish to track, and add this instead to your initializer:
67
-
68
- ```ruby
69
- config.audit_with :paper_trail, User
70
- ```
71
-
72
- Change `User` with the class you use with Devise.
73
-
74
- By default, there won't be any history shown.
75
-
76
21
  ## <a name="features"></a>Features
77
22
 
78
23
  * Display database tables
@@ -88,6 +33,7 @@ By default, there won't be any history shown.
88
33
  * User action history (internally or via [PaperTrail](https://github.com/airblade/paper_trail))
89
34
  * Supported ORMs
90
35
  * ActiveRecord
36
+ * Mongoid [new]
91
37
 
92
38
  ## <a name="demo"></a>Demo
93
39
 
@@ -119,9 +65,10 @@ It will modify your `config/routes.rb`, adding:
119
65
  mount RailsAdmin::Engine => '/admin', :as => 'rails_admin' # Feel free to change '/admin' to any namespace you need.
120
66
  ```
121
67
 
122
- It will add an intializer that will help you getting started. (head for config/initializers/rails_admin.rb)
68
+ It will also add an intializer that will help you getting started. (head for config/initializers/rails_admin.rb)
123
69
 
124
- Optionaly, you may want to set up [Cancan](https://github.com/ryanb/cancan), [PaperTrail](https://github.com/airblade/paper_trail), [CKeditor](https://github.com/galetahub/ckeditor)
70
+ Optionally, you may wish to set up [Cancan](https://github.com/ryanb/cancan),
71
+ [PaperTrail](https://github.com/airblade/paper_trail), [CKeditor](https://github.com/galetahub/ckeditor)
125
72
 
126
73
  More on that in the [Wiki](https://github.com/sferik/rails_admin/wiki)
127
74
 
@@ -138,21 +85,23 @@ You should now be able to administer your site at
138
85
  All configuration documentation has moved to the wiki: https://github.com/sferik/rails_admin/wiki
139
86
 
140
87
  ## <a name="support"></a>Support
141
- Please see [list of know issues](https://github.com/sferik/rails_admin/wiki/Known-issues) first.
88
+ Please see [list of known issues](https://github.com/sferik/rails_admin/wiki/Known-issues) first.
89
+
90
+ If you have a question, please check this README, the wiki, and the list of known issues.
142
91
 
143
- If you have a question, you can ask the [official RailsAdmin mailing
92
+ If you still have a question, you can ask the [official RailsAdmin mailing
144
93
  list](http://groups.google.com/group/rails_admin) or ping sferik on IRC in
145
94
  [#railsadmin on
146
95
  irc.freenode.net](http://webchat.freenode.net/?channels=railsadmin).
147
96
 
148
- Check this README and the wiki first.
149
-
150
97
  If you think you found a bug in RailsAdmin, you can [submit an
151
- issue](https://github.com/sferik/rails_admin#issues)
152
- No feature requests or questions please (the mailing list is active).
98
+ issue](https://github.com/sferik/rails_admin#issues).
99
+ No feature requests or questions please (the mailing list is
100
+ active and is the preferred venue for feature requests and questions).
153
101
 
154
102
  ## <a name="contributing"></a>Contributing
155
- In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html), **everyone** is encouraged to help improve this project.
103
+ In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html),
104
+ **everyone** is encouraged to help improve this project.
156
105
 
157
106
  Here are some ways *you* can contribute:
158
107
 
@@ -170,7 +119,7 @@ Here are some ways *you* can contribute:
170
119
 
171
120
  ## <a name="issues"></a>Submitting an Issue
172
121
  We use the [GitHub issue tracker](https://github.com/sferik/rails_admin/issues) to track bugs and
173
- features. Before submitting a bug report or feature request, check to make sure it hasn't already
122
+ features. Before submitting a bug report or feature request, please check to make sure it hasn't already
174
123
  been submitted. You can indicate support for an existing issue by voting it up. When submitting a
175
124
  bug report, please include a [Gist](https://gist.github.com/) that includes a stack trace and any
176
125
  details that may be necessary to reproduce the bug, including your gem version, Ruby version, and
@@ -179,17 +128,18 @@ operating system. Ideally, a bug report should include a pull request with faili
179
128
  ## <a name="pulls"></a>Submitting a Pull Request
180
129
  1. Fork the project.
181
130
  2. Create a topic branch.
182
- 3. Implement your feature or bug fix. *NOTE* - there's a small test app located in the spec/dummy_app directory that you can use to experiment with rails_admin.
131
+ 3. Implement your feature or bug fix. *NOTE* - there's a small test app located in the
132
+ spec/dummy_app directory that you can use to experiment with rails_admin.
183
133
  4. Add documentation for your feature or bug fix.
184
134
  5. Run `bundle exec rake doc:yard`. If your changes are not 100% documented, go back to step 4.
185
135
  6. Add specs for your feature or bug fix.
186
136
  7. Run `bundle exec rake spec`. If your changes are not 100% covered, go back to step 6.
187
137
  8. Commit and push your changes.
188
- 9. Submit a pull request. Please do not include changes to the gemspec, version, or history file. (If you want to create your own version for some reason, please do so in a separate commit.)
138
+ 9. Submit a pull request. Please do not include changes to the gemspec, version, or history file.
139
+ (If you want to create your own version for some reason, please do so in a separate commit.)
189
140
 
190
141
  ## <a name="versions"></a>Supported Ruby Versions
191
- This library aims to support and is [tested against][travis] the following Ruby
192
- implementations:
142
+ This library aims to support and is [tested against][travis] the following Ruby implementations:
193
143
 
194
144
  * Ruby 1.8.7
195
145
  * Ruby 1.9.2
data/Rakefile CHANGED
@@ -11,4 +11,9 @@ RSpec::Core::RakeTask.new(:spec)
11
11
 
12
12
  task :test => :spec
13
13
  task :default => :spec
14
-
14
+ namespace :spec do
15
+ task :coverage do
16
+ ENV['INVOKE_SIMPLECOV'] = 'true'
17
+ Rake::Task[:spec].invoke
18
+ end
19
+ end
@@ -2,19 +2,24 @@
2
2
  theme = ENV['RAILS_ADMIN_THEME'] || :default
3
3
  require_asset 'jquery'
4
4
  require_asset 'jquery_ujs'
5
- require_asset 'jquery_nested_form'
6
- require_asset 'rails_admin/ra.nested-form-hooks'
7
5
  require_asset 'jquery.remotipart'
8
- require_asset 'rails_admin/jquery.pjax'
9
- require_asset 'rails_admin/jquery-ui-1.8.16.custom.js'
10
- require_asset 'bootstrap'
6
+ require_asset 'jquery.effects.core'
7
+ require_asset 'jquery.ui.sortable'
8
+ require_asset 'jquery.ui.autocomplete'
9
+ require_asset 'jquery.ui.datepicker'
11
10
  require_asset 'rails_admin/jquery.ui.timepicker'
12
11
  require_asset 'rails_admin/ra.datetimepicker'
13
12
  require_asset 'rails_admin/ra.filter-box'
14
13
  require_asset 'rails_admin/ra.filtering-multiselect'
15
14
  require_asset 'rails_admin/ra.filtering-select'
16
15
  require_asset 'rails_admin/ra.remote-form'
16
+ require_asset 'rails_admin/jquery.pjax'
17
+ require_asset 'jquery_nested_form'
18
+ require_asset 'rails_admin/ra.nested-form-hooks'
19
+ require_asset 'bootstrap'
17
20
  require_asset 'rails_admin/ui'
18
21
  require_asset "rails_admin/themes/#{theme}/ui"
19
22
  require_asset 'rails_admin/custom/ui'
20
23
  %>
24
+
25
+
@@ -30,8 +30,10 @@ module RailsAdmin
30
30
  end
31
31
 
32
32
  def list_entries(model_config = @model_config, auth_scope_key = :index, additional_scope = get_association_scope_from_params, pagination = !(params[:associated_collection] || params[:all]))
33
- scope = @authorization_adapter && @authorization_adapter.query(auth_scope_key, model_config.abstract_model)
34
- scope = model_config.abstract_model.scoped.merge(scope)
33
+ scope = model_config.abstract_model.scoped
34
+ if auth_scope = @authorization_adapter && @authorization_adapter.query(auth_scope_key, model_config.abstract_model)
35
+ scope = scope.merge(auth_scope)
36
+ end
35
37
  scope = scope.instance_eval(&additional_scope) if additional_scope
36
38
 
37
39
  get_collection(model_config, scope, pagination)
@@ -53,23 +55,23 @@ module RailsAdmin
53
55
  field = model_config.list.fields.find{ |f| f.name.to_s == params[:sort] }
54
56
 
55
57
  column = if field.nil? || field.sortable == true # use params[:sort] on the base table
56
- "#{abstract_model.model.table_name}.#{params[:sort]}"
58
+ "#{abstract_model.table_name}.#{params[:sort]}"
57
59
  elsif field.sortable == false # use default sort, asked field is not sortable
58
- "#{abstract_model.model.table_name}.#{model_config.list.sort_by}"
60
+ "#{abstract_model.table_name}.#{model_config.list.sort_by}"
59
61
  elsif field.sortable.is_a?(String) && field.sortable.include?('.') # just provide sortable, don't do anything smart
60
62
  field.sortable
61
63
  elsif field.sortable.is_a?(Hash) # just join sortable hash, don't do anything smart
62
64
  "#{field.sortable.keys.first}.#{field.sortable.values.first}"
63
65
  elsif field.association? # use column on target table
64
- "#{field.associated_model_config.abstract_model.model.table_name}.#{field.sortable}"
66
+ "#{field.associated_model_config.abstract_model.table_name}.#{field.sortable}"
65
67
  else # use described column in the field conf.
66
- "#{abstract_model.model.table_name}.#{field.sortable}"
68
+ "#{abstract_model.table_name}.#{field.sortable}"
67
69
  end
68
70
 
69
71
  reversed_sort = (field ? field.sort_reverse? : model_config.list.sort_reverse?)
70
72
  {:sort => column, :sort_reverse => (params[:sort_reverse] == reversed_sort.to_s)}
71
73
  end
72
-
74
+
73
75
  def redirect_to_on_success
74
76
  notice = t("admin.flash.successful", :name => @model_config.label, :action => t("admin.actions.#{@action.key}.done"))
75
77
  if params[:_add_another]
@@ -0,0 +1,4 @@
1
+ if defined?(::Mongoid::Document)
2
+ require 'rails_admin/adapters/mongoid/extension'
3
+ Mongoid::Document.send(:include, RailsAdmin::Adapters::Mongoid::Extension)
4
+ end
@@ -20,15 +20,38 @@ module RailsAdmin
20
20
  rescue LoadError, NameError
21
21
  nil
22
22
  end
23
+
24
+ @@polymorphic_parents = {}
25
+
26
+ def polymorphic_parents(adapter, name)
27
+ @@polymorphic_parents[adapter.to_sym] ||= {}.tap do |hash|
28
+ all(adapter).each do |am|
29
+ am.associations.select{|r| r[:as] }.each do |association|
30
+ (hash[association[:as].to_sym] ||= []) << am.model
31
+ end
32
+ end
33
+ end
34
+ @@polymorphic_parents[adapter.to_sym][name.to_sym]
35
+ end
36
+
37
+ # For testing
38
+ def reset_polymorphic_parents
39
+ @@polymorphic_parents = {}
40
+ end
23
41
  end
24
42
 
25
43
  def initialize(m)
26
44
  @model_name = m.to_s
27
- # ActiveRecord
28
45
  if m.ancestors.map(&:to_s).include?('ActiveRecord::Base') && !m.abstract_class?
46
+ # ActiveRecord
29
47
  @adapter = :active_record
30
48
  require 'rails_admin/adapters/active_record'
31
49
  extend Adapters::ActiveRecord
50
+ elsif m.ancestors.map(&:to_s).include?('Mongoid::Document')
51
+ # Mongoid
52
+ @adapter = :mongoid
53
+ require 'rails_admin/adapters/mongoid'
54
+ extend Adapters::Mongoid
32
55
  end
33
56
  end
34
57
 
@@ -52,5 +75,36 @@ module RailsAdmin
52
75
  def pretty_name
53
76
  model.model_name.human
54
77
  end
78
+
79
+ private
80
+
81
+ def get_filtering_duration(operator, value)
82
+ date_format = I18n.t("admin.misc.filter_date_format", :default => I18n.t("admin.misc.filter_date_format", :locale => :en)).gsub('dd', '%d').gsub('mm', '%m').gsub('yy', '%Y')
83
+ case operator
84
+ when 'between'
85
+ start_date = value[1].present? ? (beginning_of_date(Date.strptime(value[1], date_format)) rescue false) : false
86
+ end_date = value[2].present? ? (Date.strptime(value[2], date_format).end_of_day rescue false) : false
87
+ when 'today'
88
+ start_date = beginning_of_date(Date.today)
89
+ end_date = Date.today.end_of_day
90
+ when 'yesterday'
91
+ start_date = beginning_of_date(Date.yesterday)
92
+ end_date = Date.yesterday.end_of_day
93
+ when 'this_week'
94
+ start_date = beginning_of_date(Date.today.beginning_of_week)
95
+ end_date = Date.today.end_of_week.end_of_day
96
+ when 'last_week'
97
+ start_date = beginning_of_date(1.week.ago.to_date.beginning_of_week)
98
+ end_date = 1.week.ago.to_date.end_of_week.end_of_day
99
+ else # default
100
+ start_date = (beginning_of_date(Date.strptime(Array.wrap(value).first, date_format)) rescue false)
101
+ end_date = (Date.strptime(Array.wrap(value).first, date_format).end_of_day rescue false)
102
+ end
103
+ [start_date, end_date]
104
+ end
105
+
106
+ def beginning_of_date(date)
107
+ date.beginning_of_day
108
+ end
55
109
  end
56
110
  end
@@ -7,11 +7,6 @@ module RailsAdmin
7
7
  DISABLED_COLUMN_TYPES = [:tsvector, :blob, :binary, :spatial]
8
8
  AR_ADAPTER = ::ActiveRecord::Base.configurations[Rails.env]['adapter']
9
9
  LIKE_OPERATOR = AR_ADAPTER == "postgresql" ? 'ILIKE' : 'LIKE'
10
- BEGINNING_OF_DAY = if AR_ADAPTER == "postgresql"
11
- lambda { |date| date.beginning_of_day }
12
- else
13
- lambda { |date| date.yesterday.end_of_day }
14
- end
15
10
 
16
11
  def new(params = {})
17
12
  AbstractObject.new(model.new(params))
@@ -90,6 +85,14 @@ module RailsAdmin
90
85
  end
91
86
  end
92
87
 
88
+ def table_name
89
+ model.table_name
90
+ end
91
+
92
+ def serialized_attributes
93
+ model.serialized_attributes.keys
94
+ end
95
+
93
96
  private
94
97
 
95
98
  def query_conditions(query, fields = config.list.fields.select(&:queryable?))
@@ -168,30 +171,12 @@ module RailsAdmin
168
171
  "%#{value}"
169
172
  when 'is', '='
170
173
  "#{value}"
174
+ else
175
+ return
171
176
  end
172
177
  ["(#{column} #{LIKE_OPERATOR} ?)", value]
173
178
  when :datetime, :timestamp, :date
174
- date_format = I18n.t("admin.misc.filter_date_format", :default => I18n.t("admin.misc.filter_date_format", :locale => :en)).gsub('dd', '%d').gsub('mm', '%m').gsub('yy', '%Y')
175
- case operator
176
- when 'between'
177
- start_date = value[1].present? ? (Date.strptime(value[1], date_format).instance_eval(&BEGINNING_OF_DAY) rescue false) : false
178
- end_date = value[2].present? ? (Date.strptime(value[2], date_format).end_of_day rescue false) : false
179
- when 'today'
180
- start_date = Date.today.instance_eval(&BEGINNING_OF_DAY)
181
- end_date = Date.today.end_of_day
182
- when 'yesterday'
183
- start_date = Date.yesterday.instance_eval(&BEGINNING_OF_DAY)
184
- end_date = Date.yesterday.end_of_day
185
- when 'this_week'
186
- start_date = Date.today.beginning_of_week.instance_eval(&BEGINNING_OF_DAY)
187
- end_date = Date.today.end_of_week.end_of_day
188
- when 'last_week'
189
- start_date = 1.week.ago.to_date.beginning_of_week.instance_eval(&BEGINNING_OF_DAY)
190
- end_date = 1.week.ago.to_date.end_of_week.end_of_day
191
- else # default
192
- start_date = (Date.strptime(Array.wrap(value).first, date_format).instance_eval(&BEGINNING_OF_DAY) rescue false)
193
- end_date = (Date.strptime(Array.wrap(value).first, date_format).end_of_day rescue false)
194
- end
179
+ start_date, end_date = get_filtering_duration(operator, value)
195
180
 
196
181
  if start_date && end_date
197
182
  ["(#{column} BETWEEN ? AND ?)", start_date, end_date]
@@ -206,22 +191,19 @@ module RailsAdmin
206
191
  end
207
192
  end
208
193
 
209
- @@polymorphic_parents = nil
210
-
211
- def self.polymorphic_parents(name)
212
- @@polymorphic_parents ||= {}.tap do |hash|
213
- RailsAdmin::AbstractModel.all(:active_record).each do |am|
214
- am.model.reflect_on_all_associations.select{|r| r.options[:as] }.each do |reflection|
215
- (hash[reflection.options[:as].to_sym] ||= []) << am.model
216
- end
217
- end
194
+ if AR_ADAPTER == "postgresql"
195
+ def beginning_of_date(date)
196
+ date.beginning_of_day
197
+ end
198
+ else
199
+ def beginning_of_date(date)
200
+ date.yesterday.end_of_day
218
201
  end
219
- @@polymorphic_parents[name.to_sym]
220
202
  end
221
203
 
222
204
  def association_model_lookup(association)
223
205
  if association.options[:polymorphic]
224
- RailsAdmin::Adapters::ActiveRecord.polymorphic_parents(association.name) || []
206
+ RailsAdmin::AbstractModel.polymorphic_parents(:active_record, association.name) || []
225
207
  else
226
208
  association.klass
227
209
  end
@@ -242,7 +224,7 @@ module RailsAdmin
242
224
  end
243
225
 
244
226
  def association_polymorphic_lookup(association)
245
- association.options[:polymorphic]
227
+ !!association.options[:polymorphic]
246
228
  end
247
229
 
248
230
  def association_primary_key_lookup(association)
@@ -0,0 +1,333 @@
1
+ require 'mongoid'
2
+ require 'rails_admin/config/sections/list'
3
+ require 'rails_admin/adapters/mongoid/abstract_object'
4
+
5
+ module RailsAdmin
6
+ module Adapters
7
+ module Mongoid
8
+ STRING_TYPE_COLUMN_NAMES = [:name, :title, :subject]
9
+
10
+ def new(params = {})
11
+ AbstractObject.new(model.new)
12
+ end
13
+
14
+ def get(id)
15
+ if object = model.where(:_id=>BSON::ObjectId(id)).first
16
+ AbstractObject.new object
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
22
+ def scoped
23
+ model.scoped
24
+ end
25
+
26
+ def first(options = {},scope=nil)
27
+ all(options, scope).first
28
+ end
29
+
30
+ def all(options = {},scope=nil)
31
+ scope ||= self.scoped
32
+ scope = scope.includes(options[:include]) if options[:include]
33
+ scope = scope.limit(options[:limit]) if options[:limit]
34
+ scope = scope.any_in(:_id => options[:bulk_ids]) if options[:bulk_ids]
35
+ scope = scope.where(query_conditions(options[:query])) if options[:query]
36
+ scope = scope.where(filter_conditions(options[:filters])) if options[:filters]
37
+ scope = scope.page(options[:page]).per(options[:per]) if options[:page] && options[:per]
38
+ scope = if options[:sort] && options[:sort_reverse]
39
+ scope.desc(options[:sort])
40
+ elsif options[:sort]
41
+ scope.asc(options[:sort])
42
+ else
43
+ scope
44
+ end
45
+ end
46
+
47
+ def count(options = {},scope=nil)
48
+ all(options.merge({:limit => false, :page => false}), scope).count
49
+ end
50
+
51
+ def destroy(objects)
52
+ Array.wrap(objects).each &:destroy
53
+ end
54
+
55
+ def primary_key
56
+ :_id
57
+ end
58
+
59
+ def associations
60
+ model.associations.values.map do |association|
61
+ {
62
+ :name => association.name.to_sym,
63
+ :pretty_name => association.name.to_s.tr('_', ' ').capitalize,
64
+ :type => association_type_lookup(association.macro),
65
+ :model_proc => Proc.new { association_model_proc_lookup(association) },
66
+ :primary_key_proc => Proc.new { association_primary_key_lookup(association) },
67
+ :foreign_key => association_foreign_key_lookup(association),
68
+ :foreign_type => association_foreign_type_lookup(association),
69
+ :as => association_as_lookup(association),
70
+ :polymorphic => association_polymorphic_lookup(association),
71
+ :inverse_of => association_inverse_of_lookup(association),
72
+ :read_only => nil,
73
+ :nested_form => nil
74
+ }
75
+ end
76
+ end
77
+
78
+ def properties
79
+ @properties if @properties
80
+ @properties = model.fields.map do |name,field|
81
+ ar_type =
82
+ if name == '_type'
83
+ { :type => :mongoid_type, :length => 1024 }
84
+ elsif field.type.to_s == 'String'
85
+ if (length = length_validation_lookup(name)) && length < 256
86
+ { :type => :string, :length => length }
87
+ elsif STRING_TYPE_COLUMN_NAMES.include?(name.to_sym)
88
+ { :type => :string, :length => 255 }
89
+ else
90
+ { :type => :text, :length => nil }
91
+ end
92
+ else
93
+ {
94
+ "Array" => { :type => :serialized, :length => nil },
95
+ "BigDecimal" => { :type => :string, :length => 1024 },
96
+ "Boolean" => { :type => :boolean, :length => nil },
97
+ "BSON::ObjectId" => { :type => :bson_object_id, :length => nil },
98
+ "Date" => { :type => :date, :length => nil },
99
+ "DateTime" => { :type => :datetime, :length => nil },
100
+ "Float" => { :type => :float, :length => nil },
101
+ "Hash" => { :type => :serialized, :length => nil },
102
+ "Integer" => { :type => :integer, :length => nil },
103
+ "Time" => { :type => :datetime, :length => nil },
104
+ "Object" => { :type => :bson_object_id, :length => nil },
105
+ }[field.type.to_s] or raise "Need to map field #{field.type.to_s} for field name #{name} in #{model.inspect}"
106
+ end
107
+
108
+ {
109
+ :name => field.name.to_sym,
110
+ :pretty_name => field.name.to_s.gsub('_', ' ').strip.capitalize,
111
+ :nullable? => true,
112
+ :serial? => false,
113
+ }.merge(ar_type)
114
+ end
115
+ end
116
+
117
+ def table_name
118
+ model.collection.name
119
+ end
120
+
121
+ def serialized_attributes
122
+ # Mongoid Array and Hash type columns are mapped to RA serialized type
123
+ # through type detection in self#properties.
124
+ []
125
+ end
126
+
127
+ private
128
+
129
+ def query_conditions(query, fields = config.list.fields.select(&:queryable?))
130
+ statements = []
131
+
132
+ fields.each do |field|
133
+ conditions_per_collection = {}
134
+ field.searchable_columns.flatten.each do |column_infos|
135
+ collection_name, column_name = column_infos[:column].split('.')
136
+ statement = build_statement(column_name, column_infos[:type], query, field.search_operator)
137
+ if statement
138
+ conditions_per_collection[collection_name] ||= []
139
+ conditions_per_collection[collection_name] << statement
140
+ end
141
+ end
142
+ statements.concat make_condition_for_current_collection(field, conditions_per_collection)
143
+ end
144
+
145
+ if statements.any?
146
+ { '$or' => statements }
147
+ else
148
+ {}
149
+ end
150
+ end
151
+
152
+ # filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
153
+ # "0055" is the filter index, no use here. o is the operator, v the value
154
+ def filter_conditions(filters, fields = config.list.fields.select(&:filterable?))
155
+ statements = []
156
+
157
+ filters.each_pair do |field_name, filters_dump|
158
+ filters_dump.each do |filter_index, filter_dump|
159
+ conditions_per_collection = {}
160
+ field = fields.find{|f| f.name.to_s == field_name}
161
+ next unless field
162
+ field.searchable_columns.each do |column_infos|
163
+ collection_name, column_name = column_infos[:column].split('.')
164
+ statement = build_statement(column_name, column_infos[:type], filter_dump[:v], (filter_dump[:o] || 'default'))
165
+ if statement
166
+ conditions_per_collection[collection_name] ||= []
167
+ conditions_per_collection[collection_name] << statement
168
+ end
169
+ end
170
+ if conditions_per_collection.any?
171
+ field_statements = make_condition_for_current_collection(field, conditions_per_collection)
172
+ if field_statements.length > 1
173
+ statements << { '$or' => field_statements }
174
+ else
175
+ statements << field_statements.first
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ if statements.any?
182
+ { '$and' => statements }
183
+ else
184
+ {}
185
+ end
186
+ end
187
+
188
+ def build_statement(column, type, value, operator)
189
+ # this operator/value has been discarded (but kept in the dom to override the one stored in the various links of the page)
190
+ return if operator == '_discard' || value == '_discard'
191
+
192
+ # filtering data with unary operator, not type dependent
193
+ if operator == '_blank' || value == '_blank'
194
+ return { column => {'$in' => [nil, '']} }
195
+ elsif operator == '_present' || value == '_present'
196
+ return { column => {'$nin' => [nil, '']} }
197
+ elsif operator == '_null' || value == '_null'
198
+ return { column => nil }
199
+ elsif operator == '_not_null' || value == '_not_null'
200
+ return { column => {'$ne' => nil} }
201
+ elsif operator == '_empty' || value == '_empty'
202
+ return { column => '' }
203
+ elsif operator == '_not_empty' || value == '_not_empty'
204
+ return { column => {'$ne' => ''} }
205
+ end
206
+ # now we go type specific
207
+ case type
208
+ when :boolean
209
+ return { column => false } if ['false', 'f', '0'].include?(value)
210
+ return { column => true } if ['true', 't', '1'].include?(value)
211
+ when :integer, :belongs_to_association
212
+ return if value.blank?
213
+ { column => value.to_i } if value.to_i.to_s == value
214
+ when :string, :text
215
+ return if value.blank?
216
+ value = case operator
217
+ when 'default', 'like'
218
+ Regexp.compile(Regexp.escape(value))
219
+ when 'starts_with'
220
+ Regexp.compile("^#{Regexp.escape(value)}")
221
+ when 'ends_with'
222
+ Regexp.compile("#{Regexp.escape(value)}$")
223
+ when 'is', '='
224
+ value.to_s
225
+ else
226
+ return
227
+ end
228
+ { column => value }
229
+ when :datetime, :timestamp, :date
230
+ start_date, end_date = get_filtering_duration(operator, value)
231
+
232
+ if start_date && end_date
233
+ { column => { '$gte' => start_date, '$lte' => end_date } }
234
+ elsif start_date
235
+ { column => { '$gte' => start_date } }
236
+ elsif end_date
237
+ { column => { '$lte' => end_date } }
238
+ end
239
+ when :enum
240
+ return if value.blank?
241
+ { column => { "$in" => Array.wrap(value) } }
242
+ end
243
+ end
244
+
245
+ def association_model_proc_lookup(association)
246
+ if association.polymorphic? && association.macro == :referenced_in
247
+ RailsAdmin::AbstractModel.polymorphic_parents(:mongoid, association.name) || []
248
+ else
249
+ association.klass
250
+ end
251
+ end
252
+
253
+ def association_foreign_type_lookup(association)
254
+ if association.polymorphic? && association.macro == :referenced_in
255
+ association.inverse_type.try(:to_sym) || :"#{association.name}_type"
256
+ end
257
+ end
258
+
259
+ def association_as_lookup(association)
260
+ association.as.try :to_sym
261
+ end
262
+
263
+ def association_polymorphic_lookup(association)
264
+ !!association.polymorphic? && association.macro == :referenced_in
265
+ end
266
+
267
+ def association_primary_key_lookup(association)
268
+ :_id # todo
269
+ end
270
+
271
+ def association_inverse_of_lookup(association)
272
+ association.inverse_of.try :to_sym
273
+ end
274
+
275
+ def association_foreign_key_lookup(association)
276
+ association.foreign_key.to_sym rescue nil
277
+ end
278
+
279
+ def association_type_lookup(macro)
280
+ case macro.to_sym
281
+ when :referenced_in, :embedded_in
282
+ :belongs_to
283
+ when :references_one, :embeds_one
284
+ :has_one
285
+ when :references_many, :embeds_many
286
+ :has_many
287
+ when :references_and_referenced_in_many
288
+ :has_and_belongs_to_many
289
+ else
290
+ raise "Unknown association type: #{macro.inspect}"
291
+ end
292
+ end
293
+
294
+ def length_validation_lookup(name)
295
+ shortest = model.validators.select do |validator|
296
+ validator.attributes.include?(name.to_sym) &&
297
+ validator.class == ActiveModel::Validations::LengthValidator
298
+ end.min{|a, b| a.options[:maximum] <=> b.options[:maximum] }
299
+ if shortest
300
+ shortest.options[:maximum]
301
+ else
302
+ false
303
+ end
304
+ end
305
+
306
+ def make_condition_for_current_collection(target_field, conditions_per_collection)
307
+ result =[]
308
+ conditions_per_collection.each do |collection_name, conditions|
309
+ if collection_name == table_name
310
+ # conditions referring current model column are passed directly
311
+ result.concat conditions
312
+ else
313
+ # otherwise, collect ids of documents that satisfy search condition
314
+ result.concat perform_search_on_associated_collection(target_field.name, conditions)
315
+ end
316
+ end
317
+ result
318
+ end
319
+
320
+ def perform_search_on_associated_collection(field_name, conditions)
321
+ target_association = associations.find{|a| a[:name] == field_name }
322
+ return [] unless target_association
323
+ model = target_association[:model_proc].call
324
+ case target_association[:type]
325
+ when :belongs_to, :has_and_belongs_to_many
326
+ [{ target_association[:foreign_key].to_s => { '$in' => model.where('$or' => conditions).all.map{|r| r.send(target_association[:primary_key_proc].call)} }}]
327
+ when :has_many
328
+ [{ target_association[:primary_key_proc].call.to_s => { '$in' => model.where('$or' => conditions).all.map{|r| r.send(target_association[:foreign_key])} }}]
329
+ end
330
+ end
331
+ end
332
+ end
333
+ end