Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote-tracking branch 'mshibuya/mongoid' into mongoid

Conflicts:
	app/controllers/rails_admin/main_controller.rb
	lib/rails_admin/adapters/active_record.rb
	lib/rails_admin/config/fields/factories/belongs_to_association.rb
  • Loading branch information...
commit 0b6ec58f1e09fa80fa62124bac5ff5c3fec3552d 2 parents e450630 + 3536cdc
@bbenezech bbenezech authored
Showing with 1,533 additions and 62 deletions.
  1. +2 −0  Gemfile
  2. +6 −1 Rakefile
  3. +9 −7 app/controllers/rails_admin/main_controller.rb
  4. +4 −0 config/initializers/mongoid_extensions.rb
  5. +24 −1 lib/rails_admin/abstract_model.rb
  6. +15 −16 lib/rails_admin/adapters/active_record.rb
  7. +333 −0 lib/rails_admin/adapters/mongoid.rb
  8. +32 −0 lib/rails_admin/adapters/mongoid/abstract_object.rb
  9. +27 −0 lib/rails_admin/adapters/mongoid/extension.rb
  10. +4 −4 lib/rails_admin/config/fields/base.rb
  11. +1 −1  lib/rails_admin/config/fields/factories/belongs_to_association.rb
  12. +2 −1  lib/rails_admin/config/fields/types.rb
  13. +4 −0 lib/rails_admin/config/fields/types/all.rb
  14. +23 −0 lib/rails_admin/config/fields/types/array.rb
  15. +1 −1  lib/rails_admin/config/fields/types/belongs_to_association.rb
  16. +35 −0 lib/rails_admin/config/fields/types/bson_object_id.rb
  17. +2 −2 lib/rails_admin/config/fields/types/enum.rb
  18. +23 −0 lib/rails_admin/config/fields/types/hash.rb
  19. +25 −0 lib/rails_admin/config/fields/types/mongoid_type.rb
  20. +1 −1  lib/rails_admin/config/fields/types/polymorphic_association.rb
  21. +2 −0  rails_admin.gemspec
  22. +2 −0  spec/dummy_app/Gemfile
  23. +8 −0 spec/dummy_app/app/models/article.rb
  24. +6 −0 spec/dummy_app/app/models/author.rb
  25. +22 −0 spec/dummy_app/app/models/mongoid_field_test.rb
  26. +17 −0 spec/dummy_app/config/mongoid.yml
  27. +14 −0 spec/factories.rb
  28. +28 −0 spec/integration/basic/update/rails_admin_basic_update_spec.rb
  29. +10 −0 spec/spec_helper.rb
  30. +336 −27 spec/unit/adapters/active_record_spec.rb
  31. +30 −0 spec/unit/adapters/mongoid/abstract_object_spec.rb
  32. +485 −0 spec/unit/adapters/mongoid_spec.rb
View
2  Gemfile
@@ -29,6 +29,8 @@ group :development, :test do
end
end
+ gem 'bson_ext'
+ gem 'mongoid'
gem 'cancan'
end
View
7 Rakefile
@@ -11,4 +11,9 @@ RSpec::Core::RakeTask.new(:spec)
task :test => :spec
task :default => :spec
-
+namespace :spec do
+ task :coverage do
+ ENV['INVOKE_SIMPLECOV'] = 'true'
+ Rake::Task[:spec].invoke
+ end
+end
View
16 app/controllers/rails_admin/main_controller.rb
@@ -30,8 +30,10 @@ def bulk_action
end
def list_entries(model_config = @model_config, auth_scope_key = :index, additional_scope = get_association_scope_from_params, pagination = !(params[:associated_collection] || params[:all]))
- scope = @authorization_adapter && @authorization_adapter.query(auth_scope_key, model_config.abstract_model)
- scope = model_config.abstract_model.scoped.merge(scope)
+ scope = model_config.abstract_model.scoped
+ if auth_scope = @authorization_adapter && @authorization_adapter.query(auth_scope_key, model_config.abstract_model)
+ scope = scope.merge(auth_scope)
+ end
scope = scope.instance_eval(&additional_scope) if additional_scope
get_collection(model_config, scope, pagination)
@@ -53,23 +55,23 @@ def get_sort_hash(model_config)
field = model_config.list.fields.find{ |f| f.name.to_s == params[:sort] }
column = if field.nil? || field.sortable == true # use params[:sort] on the base table
- "#{abstract_model.model.table_name}.#{params[:sort]}"
+ "#{abstract_model.table_name}.#{params[:sort]}"
elsif field.sortable == false # use default sort, asked field is not sortable
- "#{abstract_model.model.table_name}.#{model_config.list.sort_by}"
+ "#{abstract_model.table_name}.#{model_config.list.sort_by}"
elsif field.sortable.is_a?(String) && field.sortable.include?('.') # just provide sortable, don't do anything smart
field.sortable
elsif field.sortable.is_a?(Hash) # just join sortable hash, don't do anything smart
"#{field.sortable.keys.first}.#{field.sortable.values.first}"
elsif field.association? # use column on target table
- "#{field.associated_model_config.abstract_model.model.table_name}.#{field.sortable}"
+ "#{field.associated_model_config.abstract_model.table_name}.#{field.sortable}"
else # use described column in the field conf.
- "#{abstract_model.model.table_name}.#{field.sortable}"
+ "#{abstract_model.table_name}.#{field.sortable}"
end
reversed_sort = (field ? field.sort_reverse? : model_config.list.sort_reverse?)
{:sort => column, :sort_reverse => (params[:sort_reverse] == reversed_sort.to_s)}
end
-
+
def redirect_to_on_success
notice = t("admin.flash.successful", :name => @model_config.label, :action => t("admin.actions.#{@action.key}.done"))
if params[:_add_another]
View
4 config/initializers/mongoid_extensions.rb
@@ -0,0 +1,4 @@
+if defined?(::Mongoid::Document)
+ require 'rails_admin/adapters/mongoid/extension'
+ Mongoid::Document.send(:include, RailsAdmin::Adapters::Mongoid::Extension)
+end
View
25 lib/rails_admin/abstract_model.rb
@@ -20,15 +20,38 @@ def new(m)
rescue LoadError, NameError
nil
end
+
+ @@polymorphic_parents = {}
+
+ def polymorphic_parents(adapter, name)
+ @@polymorphic_parents[adapter.to_sym] ||= {}.tap do |hash|
+ all(adapter).each do |am|
+ am.associations.select{|r| r[:as] }.each do |association|
+ (hash[association[:as].to_sym] ||= []) << am.model
+ end
+ end
+ end
+ @@polymorphic_parents[adapter.to_sym][name.to_sym]
+ end
+
+ # For testing
+ def reset_polymorphic_parents
+ @@polymorphic_parents = {}
+ end
end
def initialize(m)
@model_name = m.to_s
- # ActiveRecord
if m.ancestors.map(&:to_s).include?('ActiveRecord::Base') && !m.abstract_class?
+ # ActiveRecord
@adapter = :active_record
require 'rails_admin/adapters/active_record'
extend Adapters::ActiveRecord
+ elsif m.ancestors.map(&:to_s).include?('Mongoid::Document')
+ # Mongoid
+ @adapter = :mongoid
+ require 'rails_admin/adapters/mongoid'
+ extend Adapters::Mongoid
end
end
View
31 lib/rails_admin/adapters/active_record.rb
@@ -80,6 +80,14 @@ def properties
end
end
+ def table_name
+ model.table_name
+ end
+
+ def serialized_attributes
+ model.serialized_attributes
+ end
+
private
def query_conditions(query, fields = config.list.fields.select(&:queryable?))
@@ -158,6 +166,8 @@ def build_statement(column, type, value, operator)
"%#{value}"
when 'is', '='
"#{value}"
+ else
+ return
end
["(#{column} #{LIKE_OPERATOR} ?)", value]
when :datetime, :timestamp, :date
@@ -173,13 +183,15 @@ def build_statement(column, type, value, operator)
[1.week.ago.to_date.beginning_of_week.beginning_of_day, 1.week.ago.to_date.end_of_week.end_of_day]
when 'less_than'
return if value.blank?
- [value.to_i.days.ago, DateTime.now]
+ return ["(#{column} >= ?)", value.to_i.days.ago]
when 'more_than'
return if value.blank?
- [2000.years.ago, value.to_i.days.ago]
+ return ["(#{column} <= ?)", value.to_i.days.ago]
when 'mmddyyyy'
return if (value.blank? || value.match(/([0-9]{8})/).nil?)
[Date.strptime(value.match(/([0-9]{8})/)[1], '%m%d%Y').beginning_of_day, Date.strptime(value.match(/([0-9]{8})/)[1], '%m%d%Y').end_of_day]
+ else
+ return
end
["(#{column} BETWEEN ? AND ?)", *values]
when :enum
@@ -188,22 +200,9 @@ def build_statement(column, type, value, operator)
end
end
- @@polymorphic_parents = nil
-
- def self.polymorphic_parents(name)
- @@polymorphic_parents ||= {}.tap do |hash|
- RailsAdmin::AbstractModel.all(:active_record).each do |am|
- am.model.reflect_on_all_associations.select{|r| r.options[:as] }.each do |reflection|
- (hash[reflection.options[:as].to_sym] ||= []) << am.model
- end
- end
- end
- @@polymorphic_parents[name.to_sym]
- end
-
def association_model_lookup(association)
if association.options[:polymorphic]
- RailsAdmin::Adapters::ActiveRecord.polymorphic_parents(association.name) || []
+ RailsAdmin::AbstractModel.polymorphic_parents(:active_record, association.name) || []
else
association.klass
end
View
333 lib/rails_admin/adapters/mongoid.rb
@@ -0,0 +1,333 @@
+require 'mongoid'
+require 'rails_admin/config/sections/list'
+require 'rails_admin/adapters/mongoid/abstract_object'
+
+module RailsAdmin
+ module Adapters
+ module Mongoid
+ STRING_TYPE_COLUMN_NAMES = [:name, :title, :subject]
+
+ def new(params = {})
+ AbstractObject.new(model.new)
+ end
+
+ def get(id)
+ if object = model.where(:_id=>BSON::ObjectId(id)).first
+ AbstractObject.new object
+ else
+ nil
+ end
+ end
+
+ def scoped
+ model.scoped
+ end
+
+ def first(options = {},scope=nil)
+ all(options, scope).first
+ end
+
+ def all(options = {},scope=nil)
+ scope ||= self.scoped
+ scope = scope.includes(options[:include]) if options[:include]
+ scope = scope.limit(options[:limit]) if options[:limit]
+ scope = scope.any_in(:_id => options[:bulk_ids]) if options[:bulk_ids]
+ scope = scope.where(query_conditions(options[:query])) if options[:query]
+ scope = scope.where(filter_conditions(options[:filters])) if options[:filters]
+ scope = scope.page(options[:page]).per(options[:per]) if options[:page] && options[:per]
+ scope = if options[:sort] && options[:sort_reverse]
+ scope.desc(options[:sort])
+ elsif options[:sort]
+ scope.asc(options[:sort])
+ else
+ scope
+ end
+ end
+
+ def count(options = {},scope=nil)
+ all(options.merge({:limit => false, :page => false}), scope).count
+ end
+
+ def destroy(objects)
+ Array.wrap(objects).each &:destroy
+ end
+
+ def associations
+ model.associations.values.map do |association|
+ {
+ :name => association.name.to_sym,
+ :pretty_name => association.name.to_s.tr('_', ' ').capitalize,
+ :type => association_type_lookup(association.macro),
+ :parent_model_proc => Proc.new { association_parent_model_lookup(association) },
+ :parent_key => association_parent_key_lookup(association),
+ :child_model_proc => Proc.new { association_child_model_lookup(association) },
+ :child_key => association_child_key_lookup(association),
+ :foreign_type => association_foreign_type_lookup(association),
+ :as => association_as_lookup(association),
+ :polymorphic => association_polymorphic_lookup(association),
+ :inverse_of => association_inverse_of_lookup(association),
+ :read_only => nil,
+ :nested_form => nil
+ }
+ end
+ end
+
+ def properties
+ @properties if @properties
+ @properties = model.fields.map do |name,field|
+ ar_type =
+ if name == '_type'
+ { :type => :mongoid_type, :length => 1024 }
+ elsif field.type.to_s == 'String'
+ if (length = length_validation_lookup(name)) && length < 256
+ { :type => :string, :length => length }
+ elsif STRING_TYPE_COLUMN_NAMES.include?(name.to_sym)
+ { :type => :string, :length => 255 }
+ else
+ { :type => :text, :length => nil }
+ end
+ else
+ {
+ "Array" => { :type => :array, :length => nil },
+ "BigDecimal" => { :type => :string, :length => 1024 },
+ "Boolean" => { :type => :boolean, :length => nil },
+ "BSON::ObjectId" => { :type => :bson_object_id, :length => nil },
+ "Date" => { :type => :date, :length => nil },
+ "DateTime" => { :type => :datetime, :length => nil },
+ "Float" => { :type => :float, :length => nil },
+ "Hash" => { :type => :hash, :length => nil },
+ "Integer" => { :type => :integer, :length => nil },
+ "Time" => { :type => :datetime, :length => nil },
+ "Object" => { :type => :bson_object_id, :length => nil },
+ }[field.type.to_s] or raise "Need to map field #{field.type.to_s} for field name #{name} in #{model.inspect}"
+ end
+
+ {
+ :name => field.name.to_sym,
+ :pretty_name => field.name.to_s.gsub('_', ' ').strip.capitalize,
+ :nullable? => true,
+ :serial? => false,
+ }.merge(ar_type)
+ end
+ end
+
+ def table_name
+ model.name.tableize
+ end
+
+ def serialized_attributes
+ @serialized_attributes ||= Hash[model.fields.map do |name, field|
+ if ['Array', 'Hash'].include? field.type.to_s
+ [name.to_s, nil] # TODO: support Coder
+ else
+ nil
+ end
+ end.compact]
+ end
+
+ private
+
+ def query_conditions(query, fields = config.list.fields.select(&:queryable?))
+ statements = []
+
+ fields.each do |field|
+ field.searchable_columns.flatten.each do |column_infos|
+ statement = build_statement(column_infos[:column], column_infos[:type], query, field.search_operator)
+ statements << statement if statement
+ end
+ end
+
+ if statements.any?
+ { '$or' => statements }
+ else
+ {}
+ end
+ end
+
+ # filters example => {"string_field"=>{"0055"=>{"o"=>"like", "v"=>"test_value"}}, ...}
+ # "0055" is the filter index, no use here. o is the operator, v the value
+ def filter_conditions(filters, fields = config.list.fields.select(&:filterable?))
+ statements = []
+
+ filters.each_pair do |field_name, filters_dump|
+ filters_dump.each do |filter_index, filter_dump|
+ field_statements = []
+ fields.find{|f| f.name.to_s == field_name}.searchable_columns.each do |column_infos|
+ statement = build_statement(column_infos[:column], column_infos[:type], filter_dump[:v], (filter_dump[:o] || 'default'))
+ field_statements << statement if statement.present?
+ end
+ if field_statements.any?
+ if field_statements.length > 1
+ statements << { '$or' => field_statements }
+ else
+ statements << field_statements.first
+ end
+ end
+ end
+ end
+
+ if statements.any?
+ { '$and' => statements }
+ else
+ {}
+ end
+ end
+
+ def build_statement(column, type, value, operator)
+ # remove table_name
+ table_prefix = "#{table_name}."
+ if column[0, table_prefix.length] == table_prefix
+ column = column[table_prefix.length..-1]
+ end
+
+ # this operator/value has been discarded (but kept in the dom to override the one stored in the various links of the page)
+ return if operator == '_discard' || value == '_discard'
+
+ # filtering data with unary operator, not type dependent
+ if operator == '_blank' || value == '_blank'
+ return { column => {'$in' => [nil, '']} }
+ elsif operator == '_present' || value == '_present'
+ return { column => {'$nin' => [nil, '']} }
+ elsif operator == '_null' || value == '_null'
+ return { column => nil }
+ elsif operator == '_not_null' || value == '_not_null'
+ return { column => {'$ne' => nil} }
+ elsif operator == '_empty' || value == '_empty'
+ return { column => '' }
+ elsif operator == '_not_empty' || value == '_not_empty'
+ return { column => {'$ne' => ''} }
+ end
+ # now we go type specific
+ case type
+ when :boolean
+ return { column => false } if ['false', 'f', '0'].include?(value)
+ return { column => true } if ['true', 't', '1'].include?(value)
+ when :integer, :belongs_to_association
+ return if value.blank?
+ { column => value.to_i } if value.to_i.to_s == value
+ when :string, :text
+ return if value.blank?
+ value = case operator
+ when 'default', 'like'
+ Regexp.compile(Regexp.escape(value))
+ when 'starts_with'
+ Regexp.compile("^#{Regexp.escape(value)}")
+ when 'ends_with'
+ Regexp.compile("#{Regexp.escape(value)}$")
+ when 'is', '='
+ value.to_s
+ else
+ return
+ end
+ { column => value }
+ when :datetime, :timestamp, :date
+ return unless operator != 'default'
+ values = case operator
+ when 'today'
+ [Date.today.beginning_of_day, Date.today.end_of_day]
+ when 'yesterday'
+ [Date.yesterday.beginning_of_day, Date.yesterday.end_of_day]
+ when 'this_week'
+ [Date.today.beginning_of_week.beginning_of_day, Date.today.end_of_week.end_of_day]
+ when 'last_week'
+ [1.week.ago.to_date.beginning_of_week.beginning_of_day, 1.week.ago.to_date.end_of_week.end_of_day]
+ when 'less_than'
+ return if value.blank?
+ return { column => { '$gte' => value.to_i.days.ago } }
+ when 'more_than'
+ return if value.blank?
+ return { column => { '$lte' => value.to_i.days.ago } }
+ when 'mmddyyyy'
+ return if (value.blank? || value.match(/([0-9]{8})/).nil?)
+ [Date.strptime(value.match(/([0-9]{8})/)[1], '%m%d%Y').beginning_of_day, Date.strptime(value.match(/([0-9]{8})/)[1], '%m%d%Y').end_of_day]
+ else
+ return
+ end
+ { column => { '$gte' => values[0], '$lte' => values[1] } }
+ when :enum
+ return if value.blank?
+ { column => { "$in" => Array.wrap(value) } }
+ end
+ end
+
+ def association_parent_model_lookup(association)
+ case association.macro
+ when :referenced_in
+ if association.polymorphic?
+ RailsAdmin::AbstractModel.polymorphic_parents(:mongoid, association.name) || []
+ else
+ association.klass
+ end
+ when :references_one, :references_many, :references_and_referenced_in_many
+ association.inverse_klass
+ else
+ raise "Unknown association type: #{association.macro.inspect}"
+ end
+ end
+
+ def association_foreign_type_lookup(association)
+ if association.polymorphic?
+ association.inverse_type.try(:to_sym) || :"#{association.name}_type"
+ end
+ end
+
+ def association_as_lookup(association)
+ association.as.try :to_sym
+ end
+
+ def association_polymorphic_lookup(association)
+ association.polymorphic?
+ end
+
+ def association_parent_key_lookup(association)
+ [:_id]
+ end
+
+ def association_inverse_of_lookup(association)
+ association.inverse_of.try :to_sym
+ end
+
+ def association_child_model_lookup(association)
+ case association.macro
+ when :referenced_in, :embedded_in
+ association.inverse_klass
+ when :references_one, :references_many, :references_and_referenced_in_many, :embeds_one, :embeds_many
+ association.klass
+ else
+ raise "Unknown association type: #{association.macro.inspect}"
+ end
+ end
+
+ def association_child_key_lookup(association)
+ association.foreign_key.to_sym rescue nil
+ end
+
+ def association_type_lookup(macro)
+ case macro.to_sym
+ when :referenced_in, :embedded_in
+ :belongs_to
+ when :references_one, :embeds_one
+ :has_one
+ when :references_many, :embeds_many
+ :has_many
+ when :references_and_referenced_in_many
+ :has_and_belongs_to_many
+ else
+ raise "Unknown association type: #{macro.inspect}"
+ end
+ end
+
+ def length_validation_lookup(name)
+ shortest = model.validators.select do |validator|
+ validator.attributes.include?(name.to_sym) &&
+ validator.class == ActiveModel::Validations::LengthValidator
+ end.min{|a, b| a.options[:maximum] <=> b.options[:maximum] }
+ if shortest
+ shortest.options[:maximum]
+ else
+ false
+ end
+ end
+ end
+ end
+end
View
32 lib/rails_admin/adapters/mongoid/abstract_object.rb
@@ -0,0 +1,32 @@
+require 'rails_admin/adapters/active_record/abstract_object'
+module RailsAdmin
+ module Adapters
+ module Mongoid
+ class AbstractObject < RailsAdmin::Adapters::ActiveRecord::AbstractObject
+ def initialize(object)
+ super
+ object.associations.each do |name, association|
+ if association.macro == :references_many
+ instance_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{name.to_s.singularize}_ids
+ #{name}.map{|item| item.id }
+ end
+
+ def #{name.to_s.singularize}_ids=(items)
+ self.#{name} = items.
+ map{|item_id| self.#{name}.klass.find(item_id) rescue nil }.
+ compact
+ end
+RUBY
+ end
+ end
+ end
+
+ def destroy
+ object.destroy
+ object
+ end
+ end
+ end
+ end
+end
View
27 lib/rails_admin/adapters/mongoid/extension.rb
@@ -0,0 +1,27 @@
+module RailsAdmin
+ module Adapters
+ module Mongoid
+ module Extension
+ extend ActiveSupport::Concern
+
+ included do
+ def self.rails_admin(&block)
+ RailsAdmin::Config.model(self, &block)
+ end
+ end
+
+ def rails_admin_default_object_label_method
+ self.new_record? ? "new #{self.class.to_s}" : "#{self.class.to_s} ##{self.id}"
+ end
+
+ def safe_send(value)
+ if self.attributes.find{ |k,v| k.to_s == value.to_s }
+ self.read_attribute(value)
+ else
+ self.send(value)
+ end
+ end
+ end
+ end
+ end
+end
View
8 lib/rails_admin/config/fields/base.rb
@@ -73,11 +73,11 @@ def virtual?
register_instance_option :searchable_columns do
@searchable_columns ||= case self.searchable
when true
- [{ :column => "#{self.abstract_model.model.table_name}.#{self.name}", :type => self.type }]
+ [{ :column => "#{self.abstract_model.table_name}.#{self.name}", :type => self.type }]
when false
[]
when :all # valid only for associations
- table_name = self.associated_model_config.abstract_model.model.table_name
+ table_name = self.associated_model_config.abstract_model.table_name
self.associated_model_config.list.fields.map { |f| { :column => "#{table_name}.#{f.name}", :type => f.type } }
else
[self.searchable].flatten.map do |f|
@@ -86,13 +86,13 @@ def virtual?
type = nil
elsif f.is_a?(Hash) # <Model|table_name> => <attribute|column>
am = f.keys.first.is_a?(Class) && AbstractModel.new(f.keys.first)
- table_name = am && am.model.table_name || f.keys.first
+ table_name = am && am.table_name || f.keys.first
column = f.values.first
property = am && am.properties.find{ |p| p[:name] == f.values.first.to_sym }
type = property && property[:type]
else # <attribute|column>
am = (self.association? ? self.associated_model_config.abstract_model : self.abstract_model)
- table_name = am.model.table_name
+ table_name = am.table_name
column = f
property = am.properties.find{ |p| p[:name] == f.to_sym }
type = property && property[:type]
View
2  lib/rails_admin/config/fields/factories/belongs_to_association.rb
@@ -4,7 +4,7 @@
RailsAdmin::Config::Fields.register_factory do |parent, properties, fields|
if association = parent.abstract_model.associations.find {|a| a[:foreign_key] == properties[:name] }
- field = RailsAdmin::Config::Fields::Types.load("#{association[:polymorphic] ? :polymorphic : :belongs_to}_association").new(parent, association[:name], association)
+ field = RailsAdmin::Config::Fields::Types.load("#{association[:polymorphic] ? :polymorphic : association[:type]}_association").new(parent, association[:name], association)
fields << field
child_columns = []
View
3  lib/rails_admin/config/fields/types.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/string/inflections'
require 'rails_admin/config/fields'
+require 'rails_admin/config/fields/association'
module RailsAdmin
module Config
@@ -8,7 +9,7 @@ module Types
@@registry = {}
def self.load(type)
- @@registry[type.to_sym] or logger.info "Unsupported field datatype: #{type}"
+ @@registry[type.to_sym] or raise "Unsupported field datatype: #{type}"
end
def self.register(type, klass = nil)
View
4 lib/rails_admin/config/fields/types/all.rb
@@ -1,5 +1,7 @@
+require 'rails_admin/config/fields/types/array'
require 'rails_admin/config/fields/types/belongs_to_association'
require 'rails_admin/config/fields/types/boolean'
+require 'rails_admin/config/fields/types/bson_object_id'
require 'rails_admin/config/fields/types/date'
require 'rails_admin/config/fields/types/datetime'
require 'rails_admin/config/fields/types/decimal'
@@ -12,7 +14,9 @@
require 'rails_admin/config/fields/types/has_and_belongs_to_many_association'
require 'rails_admin/config/fields/types/has_many_association'
require 'rails_admin/config/fields/types/has_one_association'
+require 'rails_admin/config/fields/types/hash'
require 'rails_admin/config/fields/types/integer'
+require 'rails_admin/config/fields/types/mongoid_type'
require 'rails_admin/config/fields/types/password'
require 'rails_admin/config/fields/types/polymorphic_association'
require 'rails_admin/config/fields/types/string'
View
23 lib/rails_admin/config/fields/types/array.rb
@@ -0,0 +1,23 @@
+require 'rails_admin/config/fields/types/text'
+
+module RailsAdmin
+ module Config
+ module Fields
+ module Types
+ class Array < RailsAdmin::Config::Fields::Types::Text
+ # Register field type for the type loader
+ RailsAdmin::Config::Fields::Types::register(self)
+
+ @view_helper = :text_area
+
+ register_instance_option(:html_default_value) do
+ YAML.dump(value)
+ end
+ end
+ end
+ end
+ end
+end
+
+
+
View
2  lib/rails_admin/config/fields/types/belongs_to_association.rb
@@ -12,7 +12,7 @@ class BelongsToAssociation < RailsAdmin::Config::Fields::Association
end
register_instance_option :sortable do
- @sortable ||= associated_model_config.abstract_model.properties.map{ |p| p[:name] }.include?(associated_model_config.object_label_method) ? associated_model_config.object_label_method : {self.abstract_model.model.table_name => self.method_name}
+ @sortable ||= associated_model_config.abstract_model.properties.map{ |p| p[:name] }.include?(associated_model_config.object_label_method) ? associated_model_config.object_label_method : {self.abstract_model.table_name => self.method_name}
end
register_instance_option :searchable do
View
35 lib/rails_admin/config/fields/types/bson_object_id.rb
@@ -0,0 +1,35 @@
+require 'rails_admin/config/fields/types/string'
+
+module RailsAdmin
+ module Config
+ module Fields
+ module Types
+ class BsonObjectId < RailsAdmin::Config::Fields::Types::String
+ # Register field type for the type loader
+ RailsAdmin::Config::Fields::Types::register(self)
+
+ register_instance_option(:label) do
+ label = ((@label ||= {})[::I18n.locale] ||= abstract_model.model.human_attribute_name name)
+ label = "_id" if label == ''
+ label
+ end
+
+ register_instance_option(:help) do
+ "BSON::ObjectId"
+ end
+
+ register_instance_option(:read_only) do
+ true
+ end
+
+ register_instance_option(:visible?) do
+ @name.to_s != '_id'
+ end
+ end
+ end
+ end
+ end
+end
+
+
+
View
4 lib/rails_admin/config/fields/types/enum.rb
@@ -20,9 +20,9 @@ class Enum < RailsAdmin::Config::Fields::Base
end
register_instance_option(:pretty_value) do
- if enum.is_a?(Hash)
+ if enum.is_a?(::Hash)
enum.reject{|k,v| v.to_s != value.to_s}.keys.first.to_s.presence || value.presence || ' - '
- elsif enum.is_a?(Array) && enum.first.is_a?(Array)
+ elsif enum.is_a?(::Array) && enum.first.is_a?(::Array)
enum.find{|e|e[1].to_s == value.to_s}.try(:first).to_s.presence || value.presence || ' - '
else
value.presence || ' - '
View
23 lib/rails_admin/config/fields/types/hash.rb
@@ -0,0 +1,23 @@
+require 'rails_admin/config/fields/types/text'
+
+module RailsAdmin
+ module Config
+ module Fields
+ module Types
+ class Hash < RailsAdmin::Config::Fields::Types::Text
+ # Register field type for the type loader
+ RailsAdmin::Config::Fields::Types::register(self)
+
+ @view_helper = :text_area
+
+ register_instance_option(:html_default_value) do
+ YAML.dump(value)
+ end
+ end
+ end
+ end
+ end
+end
+
+
+
View
25 lib/rails_admin/config/fields/types/mongoid_type.rb
@@ -0,0 +1,25 @@
+require 'rails_admin/config/fields/types/string'
+
+module RailsAdmin
+ module Config
+ module Fields
+ module Types
+ class MongoidType < RailsAdmin::Config::Fields::Types::String
+ # Register field type for the type loader
+ RailsAdmin::Config::Fields::Types::register(self)
+
+ register_instance_option(:label) do
+ "Type"
+ end
+
+ register_instance_option(:visible) do
+ false
+ end
+ end
+ end
+ end
+ end
+end
+
+
+
View
2  lib/rails_admin/config/fields/types/polymorphic_association.rb
@@ -60,7 +60,7 @@ def polymorphic_type_urls
[config.abstract_model.model.name, config.abstract_model.to_param]
end
- Hash[*types.collect { |v|
+ ::Hash[*types.collect { |v|
[v[0], bindings[:view].index_path(v[1])]
}.flatten]
end
View
2  rails_admin.gemspec
@@ -24,6 +24,8 @@ Gem::Specification.new do |gem|
gem.add_development_dependency 'mini_magick'
gem.add_development_dependency 'paperclip'
gem.add_development_dependency 'rspec-rails'
+ gem.add_development_dependency 'simplecov'
+ gem.add_development_dependency 'timecop'
gem.authors = ["Erik Michaels-Ober", "Bogdan Gaza", "Petteri Kaapa", "Benoit Benezech"]
gem.description = %q{RailsAdmin is a Rails engine that provides an easy-to-use interface for managing your data.}
gem.email = ['sferik@gmail.com', 'bogdan@cadmio.org', 'petteri.kaapa@gmail.com']
View
2  spec/dummy_app/Gemfile
@@ -2,6 +2,8 @@ source 'https://rubygems.org'
gem 'rails', '~> 3.2'
gem 'devise', '~> 2.0'
+gem 'bson_ext'
+gem 'mongoid'
gem 'rails_admin', :path => '../../'
gem 'mlb', '~> 0.5'
gem 'paperclip', '~> 2.4'
View
8 spec/dummy_app/app/models/article.rb
@@ -0,0 +1,8 @@
+class Article
+ include Mongoid::Document
+
+ field :title, :type => String
+ field :body, :type => String
+
+ referenced_in :author
+end
View
6 spec/dummy_app/app/models/author.rb
@@ -0,0 +1,6 @@
+class Author
+ include Mongoid::Document
+
+ field :name, :type => String
+ references_many :articles
+end
View
22 spec/dummy_app/app/models/mongoid_field_test.rb
@@ -0,0 +1,22 @@
+class MongoidFieldTest
+ include Mongoid::Document
+
+ field :name, :type => String
+ field :title, :type => String
+ field :subject, :type => String
+ field :description, :type => String
+ field :short_text, :type => String
+ field :array_field, :type => Array
+ field :big_decimal_field, :type => BigDecimal
+ field :boolean_field, :type => Boolean
+ field :bson_object_id_field, :type => BSON::ObjectId
+ field :date_field, :type => Date
+ field :date_time_field, :type => DateTime
+ field :float_field, :type => Float
+ field :hash_field, :type => Hash
+ field :integer_field, :type => Integer
+ field :time_field, :type => Time
+ field :object_field, :type => Object
+
+ validates :short_text, :length => {:maximum => 255}
+end
View
17 spec/dummy_app/config/mongoid.yml
@@ -0,0 +1,17 @@
+defaults: &defaults
+ host: localhost
+ autocreate_indexes: false
+ allow_dynamic_fields: true
+ include_root_in_json: false
+ parameterize_keys: true
+ persist_in_safe_mode: false
+ raise_not_found_error: true
+ reconnect_time: 3
+
+development:
+ <<: *defaults
+ database: dummy_app_development
+
+test:
+ <<: *defaults
+ database: dummy_app_test
View
14 spec/factories.rb
@@ -63,4 +63,18 @@
factory :hardball do
color('blue')
end
+
+ factory :article do
+ sequence(:title) { |n| "Article #{n}" }
+ end
+
+ factory :author do
+ sequence(:name) { |n| "Author #{n}" }
+ end
+
+ factory :mongoid_field_test do
+ sequence(:name) { |n| "Mongoid Field Test #{n}" }
+ array_field([1])
+ hash_field({:a => 1})
+ end
end
View
28 spec/integration/basic/update/rails_admin_basic_update_spec.rb
@@ -154,6 +154,34 @@
end
end
+ describe "update with serialized objects of Mongoid" do
+ before(:each) do
+ @field_test = FactoryGirl.create :mongoid_field_test
+
+ visit edit_path(:model_name => "mongoid_field_test", :id => @field_test.id)
+ end
+
+ it "should save the serialized data" do
+ fill_in "mongoid_field_test[array_field]", :with => "[4, 2]"
+ fill_in "mongoid_field_test[hash_field]", :with => "{ a: 6, b: 2 }"
+ click_button "Save"
+
+ @field_test.reload
+ @field_test.array_field.should eql([4, 2])
+ @field_test.hash_field.should eql({ "a" => 6, "b" => 2 })
+ end
+
+ it "should clear data when empty string is passed" do
+ fill_in "mongoid_field_test[array_field]", :with => ""
+ fill_in "mongoid_field_test[hash_field]", :with => ""
+ click_button "Save"
+
+ @field_test.reload
+ @field_test.array_field.should eql(nil)
+ @field_test.hash_field.should eql(nil)
+ end
+ end
+
describe "update with overridden to_param" do
before(:each) do
@ball = FactoryGirl.create :ball
View
10 spec/spec_helper.rb
@@ -1,6 +1,12 @@
# Configure Rails Envinronment
ENV["RAILS_ENV"] = "test"
ENV['SKIP_RAILS_ADMIN_INITIALIZER'] = 'true'
+
+if ENV['INVOKE_SIMPLECOV']
+ require 'simplecov'
+ SimpleCov.start 'rails'
+end
+
require File.expand_path('../dummy_app/config/environment', __FILE__)
require 'rspec/rails'
@@ -61,6 +67,8 @@ def password_digest(password)
Team.delete_all
User.delete_all
FieldTest.delete_all
+ Author.delete_all
+ Article.delete_all
login_as User.create(
:email => "username@example.com",
:password => "password"
@@ -70,4 +78,6 @@ def password_digest(password)
config.after(:each) do
Warden.test_reset!
end
+
+ config.seed = ENV['SEED'] if ENV['SEED']
end
View
363 spec/unit/adapters/active_record_spec.rb
@@ -1,53 +1,362 @@
require 'spec_helper'
+require 'timecop'
require 'rails_admin/adapters/active_record'
describe RailsAdmin::Adapters::ActiveRecord do
+ before do
+ @like = ::ActiveRecord::Base.configurations[Rails.env]['adapter'] == "postgresql" ? 'ILIKE' : 'LIKE'
+ end
+
+ describe '#associations' do
+ before :all do
+ RailsAdmin::AbstractModel.reset_polymorphic_parents
+
+ class ARBlog < ActiveRecord::Base
+ has_many :a_r_posts
+ has_many :a_r_comments, :as => :commentable
+ end
+
+ class ARPost < ActiveRecord::Base
+ belongs_to :a_r_blog
+ has_and_belongs_to_many :a_r_categories
+ has_many :a_r_comments, :as => :commentable
+ end
+
+ class ARCategory < ActiveRecord::Base
+ has_and_belongs_to_many :a_r_posts
+ end
- before :all do
+ class ARUser < ActiveRecord::Base
+ has_one :a_r_profile
+ end
- class ARBlog < ActiveRecord::Base
- has_many :a_r_posts
- has_many :a_r_comments, :as => :commentable
+ class ARProfile < ActiveRecord::Base
+ belongs_to :a_r_user
+ end
+
+ class ARComment < ActiveRecord::Base
+ belongs_to :commentable, :polymorphic => true
+ end
+
+ @blog = RailsAdmin::AbstractModel.new(ARBlog)
+ @post = RailsAdmin::AbstractModel.new(ARPost)
+ @category = RailsAdmin::AbstractModel.new(ARCategory)
+ @user = RailsAdmin::AbstractModel.new(ARUser)
+ @profile = RailsAdmin::AbstractModel.new(ARProfile)
+ @comment = RailsAdmin::AbstractModel.new(ARComment)
end
- class ARPost < ActiveRecord::Base
- belongs_to :a_r_blog
- has_and_belongs_to_many :a_r_categories
- has_many :a_r_comments, :as => :commentable
+ after :all do
+ RailsAdmin::AbstractModel.reset_polymorphic_parents
end
- class ARCategory < ActiveRecord::Base
- has_and_belongs_to_many :a_r_posts
+ it 'lists associations' do
+ @post.associations.map{|a|a[:name].to_s}.sort.should == ['a_r_blog', 'a_r_categories', 'a_r_comments']
end
- class ARUser < ActiveRecord::Base
- has_one :a_r_profile
+ it 'list associations types in supported [:belongs_to, :has_and_belongs_to_many, :has_many, :has_one]' do
+ (@post.associations + @blog.associations + @user.associations).map{|a|a[:type]}.uniq.map(&:to_s).sort.should == ['belongs_to', 'has_and_belongs_to_many', 'has_many', 'has_one']
end
- class ARProfile < ActiveRecord::Base
- belongs_to :a_r_user
+ it "has correct parameter of belongs_to association" do
+ param = @post.associations.select{|a| a[:name] == :a_r_blog}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:a_r_blog,
+ :pretty_name=>"A r blog",
+ :type=>:belongs_to,
+ :parent_key=>[:id],
+ :child_key=>:a_r_blog_id,
+ :foreign_type=>nil,
+ :as=>nil,
+ :polymorphic=>nil,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == ARPost
+ param[:parent_model_proc].call.should == ARBlog
end
- class ARComment < ActiveRecord::Base
- belongs_to :commentable, :polymorphic => true
+ it "has correct parameter of has_many association" do
+ param = @blog.associations.select{|a| a[:name] == :a_r_posts}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:a_r_posts,
+ :pretty_name=>"A r posts",
+ :type=>:has_many,
+ :parent_key=>[:id],
+ :child_key=>:ar_blog_id,
+ :foreign_type=>nil,
+ :as=>nil,
+ :polymorphic=>nil,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == ARPost
+ param[:parent_model_proc].call.should == ARBlog
end
- @blog = RailsAdmin::AbstractModel.new(ARBlog)
- @post = RailsAdmin::AbstractModel.new(ARPost)
- @category = RailsAdmin::AbstractModel.new(ARCategory)
- @user = RailsAdmin::AbstractModel.new(ARUser)
- @profile = RailsAdmin::AbstractModel.new(ARProfile)
- @comment = RailsAdmin::AbstractModel.new(ARComment)
+ it "has correct parameter of has_and_belongs_to_many association" do
+ param = @post.associations.select{|a| a[:name] == :a_r_categories}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:a_r_categories,
+ :pretty_name=>"A r categories",
+ :type=>:has_and_belongs_to_many,
+ :parent_key=>[:id],
+ :child_key=>:ar_post_id,
+ :foreign_type=>nil,
+ :as=>nil,
+ :polymorphic=>nil,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == ARCategory
+ param[:parent_model_proc].call.should == ARPost
+ end
+
+ it "has correct parameter of polymorphic belongs_to association" do
+ RailsAdmin::Config.stub!(:models_pool).and_return(["ARBlog", "ARPost", "ARCategory", "ARUser", "ARProfile", "ARComment"])
+ param = @comment.associations.select{|a| a[:name] == :commentable}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:commentable,
+ :pretty_name=>"Commentable",
+ :type=>:belongs_to,
+ :parent_key=>[:id],
+ :child_key=>:commentable_id,
+ :foreign_type=>:commentable_type,
+ :as=>nil,
+ :polymorphic=>true,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == ARComment
+ param[:parent_model_proc].call.should == [ARBlog, ARPost]
+ end
end
- describe '#associations' do
- it 'lists associations' do
- @post.associations.map{|a|a[:name].to_s}.sort.should == ['a_r_blog', 'a_r_categories', 'a_r_comments']
+ describe "#properties" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Player')
end
- it 'list associations types in supported [:belongs_to, :has_and_belongs_to_many, :has_many, :has_one]' do
- (@post.associations + @blog.associations + @user.associations).map{|a|a[:type]}.uniq.map(&:to_s).sort.should == ['belongs_to', 'has_and_belongs_to_many', 'has_many', 'has_one']
+ it "returns parameters of string-type field" do
+ @abstract_model.properties.select{|f| f[:name] == :name}.should ==
+ [{:name => :name, :pretty_name => "Name", :type => :string, :length => 100, :nullable? => false, :serial? => false}]
+ end
+ end
+
+ describe "data access method" do
+ before do
+ @players = FactoryGirl.create_list(:player, 3)
+ @abstract_model = RailsAdmin::AbstractModel.new('Player')
+ end
+
+ it "#new returns instance of AbstractObject" do
+ @abstract_model.new.object.should be_instance_of(Player)
+ end
+
+ it "#get returns instance of AbstractObject" do
+ @abstract_model.get(@players.first.id).object.should == @players.first
+ end
+
+ it "#get returns nil when id does not exist" do
+ @abstract_model.get('abc').should be_nil
+ end
+
+ it "#first returns first item" do
+ @abstract_model.first.should == @players.first
+ end
+
+ it "#count returns count of items" do
+ @abstract_model.count.should == @players.count
+ end
+
+ it "#destroy destroys multiple items" do
+ @abstract_model.destroy(@players[0..1])
+ Player.all.should == @players[2..2]
+ end
+
+ describe "#all" do
+ it "works without options" do
+ @abstract_model.all.sort.should == @players.sort
+ end
+
+ it "supports eager loading" do
+ @abstract_model.all(:include => :team).includes_values.should == [:team]
+ end
+
+ it "supports limiting" do
+ @abstract_model.all(:limit => 2).count.should == 2
+ end
+
+ it "supports retrieval by bulk_ids" do
+ @abstract_model.all(:bulk_ids => @players[0..1].map{|player| player.id }).
+ sort.should == @players[0..1].sort
+ end
+
+ it "supports pagination" do
+ @abstract_model.all(:page => 2, :per => 1).should == @players[1..1]
+ end
+
+ it "supports ordering" do
+ @abstract_model.all(:sort => "id", :sort_reverse => true).should == @players.sort
+ end
+
+ it "supports querying" do
+ @abstract_model.all(:query => @players[1].name).should == @players[1..1]
+ end
+
+ it "supports filtering" do
+ @abstract_model.all(:filters => {"name" => {"0000" => {:o=>"is", :v=>@players[1].name}}}).should == @players[1..1]
+ end
+ end
+ end
+
+ describe "#query_conditions" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Ball')
+ end
+
+ it "returns query statement" do
+ @abstract_model.send(:query_conditions, "word").should == ["(balls.color LIKE ?) OR (balls.type LIKE ?)", "%word%", "%word%"]
+ end
+ end
+
+ describe "#filter_conditions" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Team')
+ end
+
+ it "returns filter statement" do
+ @abstract_model.send(
+ :filter_conditions,
+ {"name" => {"0000" => {:o=>"is", :v=>"Jets"}},
+ "division" => {"0001" => {:o=>"like", :v=>"1"}}}
+ ).should == ["((teams.name #{@like} ?)) AND ((divisions.name #{@like} ?) OR (teams.division_id = ?))", "Jets", "%1%", 1]
+ end
+ end
+
+ describe "#build_statement" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Team')
+ end
+
+ it "ignores '_discard' operator or value" do
+ [["_discard", ""], ["", "_discard"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should be_nil
+ end
+ end
+
+ it "supports '_blank' operator" do
+ [["_blank", ""], ["", "_blank"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == ["(name IS NULL OR name = '')"]
+ end
+ end
+
+ it "supports '_present' operator" do
+ [["_present", ""], ["", "_present"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == ["(name IS NOT NULL AND name != '')"]
+ end
+ end
+
+ it "supports '_null' operator" do
+ [["_null", ""], ["", "_null"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == ["(name IS NULL)"]
+ end
+ end
+
+ it "supports '_not_null' operator" do
+ [["_not_null", ""], ["", "_not_null"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == ["(name IS NOT NULL)"]
+ end
+ end
+
+ it "supports '_empty' operator" do
+ [["_empty", ""], ["", "_empty"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == ["(name = '')"]
+ end
+ end
+
+ it "supports '_not_empty' operator" do
+ [["_not_empty", ""], ["", "_not_empty"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == ["(name != '')"]
+ end
+ end
+
+ it "supports boolean type query" do
+ ['false', 'f', '0'].each do |value|
+ @abstract_model.send(:build_statement, :field, :boolean, value, nil).should == ["(field IS NULL OR field = ?)", false]
+ end
+ ['true', 't', '1'].each do |value|
+ @abstract_model.send(:build_statement, :field, :boolean, value, nil).should == ["(field = ?)", true]
+ end
+ @abstract_model.send(:build_statement, :field, :boolean, 'word', nil).should be_nil
+ end
+
+ it "supports integer type query" do
+ @abstract_model.send(:build_statement, :field, :integer, "1", nil).should == ["(field = ?)", 1]
+ @abstract_model.send(:build_statement, :field, :integer, 'word', nil).should be_nil
+ end
+
+ it "supports string type query" do
+ @abstract_model.send(:build_statement, :field, :string, "", nil).should be_nil
+ @abstract_model.send(:build_statement, :field, :string, "foo", "was").should be_nil
+ @abstract_model.send(:build_statement, :field, :string, "foo", "default").should == ["(field #{@like} ?)", "%foo%"]
+ @abstract_model.send(:build_statement, :field, :string, "foo", "like").should == ["(field #{@like} ?)", "%foo%"]
+ @abstract_model.send(:build_statement, :field, :string, "foo", "starts_with").should == ["(field #{@like} ?)", "foo%"]
+ @abstract_model.send(:build_statement, :field, :string, "foo", "ends_with").should == ["(field #{@like} ?)", "%foo"]
+ @abstract_model.send(:build_statement, :field, :string, "foo", "is").should == ["(field #{@like} ?)", "foo"]
+ end
+
+ [:datetime, :timestamp, :date].each do |type|
+ it "supports #{type} query" do
+ @abstract_model.send(:build_statement, :field, type, "", "default").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "", "is").should be_nil
+ Timecop.freeze(Time.utc(2012,1,15,12,0,0)) do
+ @abstract_model.send(:build_statement, :field, type, "", "today").to_s.should ==
+ '["(field BETWEEN ? AND ?)", Sun, 15 Jan 2012 00:00:00 UTC +00:00, Sun, 15 Jan 2012 23:59:59 UTC +00:00]'
+ @abstract_model.send(:build_statement, :field, type, "", "yesterday").to_s.should ==
+ '["(field BETWEEN ? AND ?)", Sat, 14 Jan 2012 00:00:00 UTC +00:00, Sat, 14 Jan 2012 23:59:59 UTC +00:00]'
+ @abstract_model.send(:build_statement, :field, type, "", "this_week").to_s.should ==
+ '["(field BETWEEN ? AND ?)", Mon, 09 Jan 2012 00:00:00 UTC +00:00, Sun, 15 Jan 2012 23:59:59 UTC +00:00]'
+ @abstract_model.send(:build_statement, :field, type, "", "last_week").to_s.should ==
+ '["(field BETWEEN ? AND ?)", Mon, 02 Jan 2012 00:00:00 UTC +00:00, Sun, 08 Jan 2012 23:59:59 UTC +00:00]'
+ @abstract_model.send(:build_statement, :field, type, "", "less_than").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "1", "less_than").to_s.should ==
+ '["(field >= ?)", Sat, 14 Jan 2012 12:00:00 UTC +00:00]'
+ @abstract_model.send(:build_statement, :field, type, "1", "more_than").to_s.should ==
+ '["(field <= ?)", Sat, 14 Jan 2012 12:00:00 UTC +00:00]'
+ end
+ @abstract_model.send(:build_statement, :field, type, "", "mmddyyyy").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "201105", "mmddyyyy").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "12312011", "mmddyyyy").to_s.should ==
+ '["(field BETWEEN ? AND ?)", Sat, 31 Dec 2011 00:00:00 UTC +00:00, Sat, 31 Dec 2011 23:59:59 UTC +00:00]'
+ end
+ end
+
+ it "supports enum type query" do
+ @abstract_model.send(:build_statement, :field, :enum, "1", nil).should == ["(field IN (?))", ["1"]]
+ end
+ end
+
+ describe "model attribute method" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Player')
+ end
+
+ it "#scoped returns relation object" do
+ @abstract_model.scoped.should be_instance_of(ActiveRecord::Relation)
+ end
+
+ it "#table_name works" do
+ @abstract_model.table_name.should == 'players'
+ end
+
+ it "#serialized_attributes works" do
+ RailsAdmin::AbstractModel.new('User').serialized_attributes.keys.should == ["roles"]
end
end
end
View
30 spec/unit/adapters/mongoid/abstract_object_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+require 'rails_admin/adapters/mongoid/abstract_object'
+
+describe "Mongoid::AbstractObject" do
+ before(:each) do
+ @articles = FactoryGirl.create_list :article, 3
+ @author = RailsAdmin::Adapters::Mongoid::AbstractObject.new FactoryGirl.create :author
+ end
+
+ describe "references_many association" do
+ it "supports retrieval of ids through foo_ids" do
+ @author.article_ids.should == []
+ article = FactoryGirl.create :article, :author => @author
+ @author.article_ids.should == [article.id]
+ end
+
+ it "supports assignment of items through foo_ids=" do
+ @author.articles.should == []
+ @author.article_ids = @articles.map(&:id)
+ @author.reload
+ @author.articles.sort.should == @articles.sort
+ end
+
+ it "skips invalid id on assignment through foo_ids=" do
+ @author.article_ids = @articles.map{|item| item.id.to_s }.unshift('4f431021dcf2310db7000006')
+ @author.reload
+ @author.articles.sort.should == @articles.sort
+ end
+ end
+end
View
485 spec/unit/adapters/mongoid_spec.rb
@@ -0,0 +1,485 @@
+require 'spec_helper'
+require 'timecop'
+require 'rails_admin/adapters/mongoid'
+
+describe RailsAdmin::Adapters::Mongoid do
+ describe '#associations' do
+ before :all do
+ RailsAdmin::AbstractModel.reset_polymorphic_parents
+
+ class MongoBlog
+ include Mongoid::Document
+ references_many :mongo_posts
+ references_many :mongo_comments, :as => :commentable
+ end
+
+ class MongoPost
+ include Mongoid::Document
+ referenced_in :mongo_blog
+ has_and_belongs_to_many :mongo_categories
+ references_many :mongo_comments, :as => :commentable
+ end
+
+ class MongoCategory
+ include Mongoid::Document
+ has_and_belongs_to_many :mongo_posts
+ end
+
+ class MongoUser
+ include Mongoid::Document
+ references_one :mongo_profile
+ field :name, :type => String
+ field :message, :type => String
+ field :short_text, :type => String
+
+ validates :short_text, :length => {:maximum => 255}
+ end
+
+ class MongoProfile
+ include Mongoid::Document
+ referenced_in :mongo_user
+ end
+
+ class MongoComment
+ include Mongoid::Document
+ referenced_in :commentable, :polymorphic => true
+ end
+
+ @blog = RailsAdmin::AbstractModel.new(MongoBlog)
+ @post = RailsAdmin::AbstractModel.new(MongoPost)
+ @category = RailsAdmin::AbstractModel.new(MongoCategory)
+ @user = RailsAdmin::AbstractModel.new(MongoUser)
+ @profile = RailsAdmin::AbstractModel.new(MongoProfile)
+ @comment = RailsAdmin::AbstractModel.new(MongoComment)
+ end
+
+ after :all do
+ RailsAdmin::AbstractModel.reset_polymorphic_parents
+ end
+
+ it 'lists associations' do
+ @post.associations.map{|a|a[:name]}.should == [:mongo_blog, :mongo_categories, :mongo_comments]
+ end
+
+ it 'reads correct and know types in [:belongs_to, :has_and_belongs_to_many, :has_many, :has_one]' do
+ (@post.associations + @blog.associations + @user.associations).map{|a|a[:type]}.uniq.sort.should == [:belongs_to, :has_and_belongs_to_many, :has_many, :has_one]
+ end
+
+ it "has correct parameter of belongs_to association" do
+ param = @post.associations.select{|a| a[:name] == :mongo_blog}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:mongo_blog,
+ :pretty_name=>"Mongo blog",
+ :type=>:belongs_to,
+ :parent_key=>[:_id],
+ :child_key=>:mongo_blog_id,
+ :foreign_type=>nil,
+ :as=>nil,
+ :polymorphic=>false,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == MongoPost
+ param[:parent_model_proc].call.should == MongoBlog
+ end
+
+ it "has correct parameter of has_many association" do
+ param = @blog.associations.select{|a| a[:name] == :mongo_posts}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:mongo_posts,
+ :pretty_name=>"Mongo posts",
+ :type=>:has_many,
+ :parent_key=>[:_id],
+ :child_key=>:mongo_blog_id,
+ :foreign_type=>nil,
+ :as=>nil,
+ :polymorphic=>false,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == MongoPost
+ param[:parent_model_proc].call.should == MongoBlog
+ end
+
+ it "has correct parameter of has_and_belongs_to_many association" do
+ param = @post.associations.select{|a| a[:name] == :mongo_categories}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:mongo_categories,
+ :pretty_name=>"Mongo categories",
+ :type=>:has_and_belongs_to_many,
+ :parent_key=>[:_id],
+ :child_key=>:mongo_category_ids,
+ :foreign_type=>nil,
+ :as=>nil,
+ :polymorphic=>false,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == MongoCategory
+ param[:parent_model_proc].call.should == MongoPost
+ end
+
+ it "has correct parameter of polymorphic belongs_to association" do
+ RailsAdmin::Config.stub!(:models_pool).and_return(["MongoBlog", "MongoPost", "MongoCategory", "MongoUser", "MongoProfile", "MongoComment"])
+ param = @comment.associations.select{|a| a[:name] == :commentable}.first
+ param.reject{|k, v| [:child_model_proc, :parent_model_proc].include? k }.should == {
+ :name=>:commentable,
+ :pretty_name=>"Commentable",
+ :type=>:belongs_to,
+ :parent_key=>[:_id],
+ :child_key=>:commentable_id,
+ :foreign_type=>:commentable_type,
+ :as=>nil,
+ :polymorphic=>true,
+ :inverse_of=>nil,
+ :read_only=>nil,
+ :nested_form=>nil
+ }
+ param[:child_model_proc].call.should == MongoComment
+ param[:parent_model_proc].call.should == [MongoBlog, MongoPost]
+ end
+ end
+
+ describe "#properties" do
+ before :all do
+ @abstract_model = RailsAdmin::AbstractModel.new(MongoidFieldTest)
+ end
+
+ it "maps Mongoid column types to RA types" do
+ @abstract_model.properties.sort{|a,b| a[:name] <=> b[:name] }.should == [
+ { :name => :_id,
+ :pretty_name => "Id",
+ :nullable? => true,
+ :serial? => false,
+ :type => :bson_object_id,
+ :length => nil },
+ { :name => :_type,
+ :pretty_name => "Type",
+ :nullable? => true,
+ :serial? => false,
+ :type => :mongoid_type,
+ :length => 1024 },
+ { :name => :array_field,
+ :pretty_name => "Array field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :array,
+ :length => nil },
+ { :name => :big_decimal_field,
+ :pretty_name => "Big decimal field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :string,
+ :length => 1024 },
+ { :name => :boolean_field,
+ :pretty_name => "Boolean field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :boolean,
+ :length => nil },
+ { :name => :bson_object_id_field,
+ :pretty_name => "Bson object id field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :bson_object_id,
+ :length => nil },
+ { :name => :date_field,
+ :pretty_name => "Date field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :date,
+ :length => nil },
+ { :name => :date_time_field,
+ :pretty_name => "Date time field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :datetime,
+ :length => nil },
+ { :name => :description,
+ :pretty_name => "Description",
+ :nullable? => true,
+ :serial? => false,
+ :type => :text,
+ :length => nil },
+ { :name => :float_field,
+ :pretty_name => "Float field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :float,
+ :length => nil },
+ { :name => :hash_field,
+ :pretty_name => "Hash field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :hash,
+ :length => nil },
+ { :name => :integer_field,
+ :pretty_name => "Integer field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :integer,
+ :length => nil },
+ { :name => :name,
+ :pretty_name => "Name",
+ :nullable? => true,
+ :serial? => false,
+ :type => :string,
+ :length => 255 },
+ { :name => :object_field,
+ :pretty_name => "Object field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :bson_object_id,
+ :length => nil },
+ { :name => :short_text,
+ :pretty_name => "Short text",
+ :nullable? => true,
+ :serial? => false,
+ :type => :string,
+ :length => 255 },
+ { :name => :subject,
+ :pretty_name => "Subject",
+ :nullable? => true,
+ :serial? => false,
+ :type => :string,
+ :length => 255 },
+ { :name => :time_field,
+ :pretty_name => "Time field",
+ :nullable? => true,
+ :serial? => false,
+ :type => :datetime,
+ :length => nil },
+ { :name => :title,
+ :pretty_name => "Title",
+ :nullable? => true,
+ :serial? => false,
+ :type => :string,
+ :length => 255 }
+ ]
+ end
+ end
+
+ describe "data access method" do
+ before do
+ @articles = FactoryGirl.create_list(:article, 3)
+ @abstract_model = RailsAdmin::AbstractModel.new('Article')
+ end
+
+ it "#new returns instance of AbstractObject" do
+ @abstract_model.new.object.should be_instance_of(Article)
+ end
+
+ it "#get returns instance of AbstractObject" do
+ @abstract_model.get(@articles.first.id.to_s).object.should == @articles.first
+ end
+
+ it "#get returns nil when id does not exist" do
+ @abstract_model.get('4f4f0824dcf2315093000000').should be_nil
+ end
+
+ it "#first returns first item" do
+ @abstract_model.first.should == @articles.first
+ end
+
+ it "#count returns count of items" do
+ @abstract_model.count.should == @articles.count
+ end
+
+ it "#destroy destroys multiple items" do
+ @abstract_model.destroy(@articles[0..1])
+ Article.all.should == @articles[2..2]
+ end
+
+ describe "#all" do
+ it "works without options" do
+ @abstract_model.all.sort.should == @articles.sort
+ end
+
+ it "supports eager loading" do
+ @abstract_model.all(:include => :author).inclusions.map{|i| i.class_name}.should == ["Author"]
+ end
+
+ it "supports limiting" do
+ @abstract_model.all(:limit => 2).to_a.count.should == 2
+ end
+
+ it "supports retrieval by bulk_ids" do
+ @abstract_model.all(:bulk_ids => @articles[0..1].map{|article| article.id.to_s }).
+ sort.should == @articles[0..1].sort
+ end
+
+ it "supports pagination" do
+ @abstract_model.all(:page => 2, :per => 1).should == @articles[1..1]
+ end
+
+ it "supports ordering" do
+ @abstract_model.all(:sort => "id", :sort_reverse => true).should == @articles.sort
+ end
+
+ it "supports querying" do
+ @abstract_model.all(:query => @articles[1].title).should == @articles[1..1]
+ end
+
+ it "supports filtering" do
+ @abstract_model.all(:filters => {"title" => {"0000" => {:o=>"is", :v=>@articles[1].title}}}).should == @articles[1..1]
+ end
+ end
+ end
+
+ describe "#query_conditions" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Article')
+ end
+
+ it "returns query statement" do
+ @abstract_model.send(:query_conditions, "word").should ==
+ {"$or"=>[{"title"=>/word/}, {"body"=>/word/}]}
+ end
+ end
+
+ describe "#filter_conditions" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Article')
+ end
+
+ it "returns filter statement" do
+ pending "Alternative for SQL join query should be implemented"
+ @abstract_model.send(
+ :filter_conditions,
+ {"title" => {"0000" => {:o=>"is", :v=>"foo"}},
+ "author" => {"0001" => {:o=>"like", :v=>"1"}}}
+ ).should == {"$and"=>[{"title"=>"foo"}, {"$or"=>[{"authors.name"=>/1/}, {"author_id"=>"1"}]}]}
+ end
+ end
+
+ describe "#build_statement" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Article')
+ end
+
+ it "ignores '_discard' operator or value" do
+ [["_discard", ""], ["", "_discard"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should be_nil
+ end
+ end
+
+ it "supports '_blank' operator" do
+ [["_blank", ""], ["", "_blank"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == {:name=>{"$in"=>[nil, ""]}}
+ end
+ end
+
+ it "supports '_present' operator" do
+ [["_present", ""], ["", "_present"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == {:name=>{"$nin"=>[nil, ""]}}
+ end
+ end
+
+ it "supports '_null' operator" do
+ [["_null", ""], ["", "_null"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == {:name=>nil}
+ end
+ end
+
+ it "supports '_not_null' operator" do
+ [["_not_null", ""], ["", "_not_null"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == {:name=>{"$ne"=>nil}}
+ end
+ end
+
+ it "supports '_empty' operator" do
+ [["_empty", ""], ["", "_empty"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == {:name=>""}
+ end
+ end
+
+ it "supports '_not_empty' operator" do
+ [["_not_empty", ""], ["", "_not_empty"]].each do |value, operator|
+ @abstract_model.send(:build_statement, :name, :string, value, operator).should == {:name=>{"$ne"=>""}}
+ end
+ end
+
+ it "supports boolean type query" do
+ ['false', 'f', '0'].each do |value|
+ @abstract_model.send(:build_statement, :field, :boolean, value, nil).should == {:field => false}
+ end
+ ['true', 't', '1'].each do |value|
+ @abstract_model.send(:build_statement, :field, :boolean, value, nil).should == {:field => true}
+ end
+ @abstract_model.send(:build_statement, :field, :boolean, 'word', nil).should be_nil
+ end
+
+ it "supports integer type query" do
+ @abstract_model.send(:build_statement, :field, :integer, "1", nil).should == {:field => 1}
+ @abstract_model.send(:build_statement, :field, :integer, 'word', nil).should be_nil
+ end
+
+ it "supports string type query" do
+ @abstract_model.send(:build_statement, :field, :string, "", nil).should be_nil
+ @abstract_model.send(:build_statement, :field, :string, "foo", "was").should be_nil
+ @abstract_model.send(:build_statement, :field, :string, "foo", "default").should == {:field=>/foo/}
+ @abstract_model.send(:build_statement, :field, :string, "foo", "like").should == {:field=>/foo/}
+ @abstract_model.send(:build_statement, :field, :string, "foo", "starts_with").should == {:field=>/^foo/}
+ @abstract_model.send(:build_statement, :field, :string, "foo", "ends_with").should == {:field=>/foo$/}
+ @abstract_model.send(:build_statement, :field, :string, "foo", "is").should == {:field=>'foo'}
+ end
+
+ [:datetime, :timestamp, :date].each do |type|
+ it "supports #{type} query" do
+ @abstract_model.send(:build_statement, :field, type, "", "default").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "", "is").should be_nil
+ Timecop.freeze(Time.utc(2012,1,15,12,0,0)) do
+ @abstract_model.send(:build_statement, :field, type, "", "today").to_s.should ==
+ '{:field=>{"$gte"=>Sun, 15 Jan 2012 00:00:00 UTC +00:00, "$lte"=>Sun, 15 Jan 2012 23:59:59 UTC +00:00}}'
+ @abstract_model.send(:build_statement, :field, type, "", "yesterday").to_s.should ==
+ '{:field=>{"$gte"=>Sat, 14 Jan 2012 00:00:00 UTC +00:00, "$lte"=>Sat, 14 Jan 2012 23:59:59 UTC +00:00}}'
+ @abstract_model.send(:build_statement, :field, type, "", "this_week").to_s.should ==
+ '{:field=>{"$gte"=>Mon, 09 Jan 2012 00:00:00 UTC +00:00, "$lte"=>Sun, 15 Jan 2012 23:59:59 UTC +00:00}}'
+ @abstract_model.send(:build_statement, :field, type, "", "last_week").to_s.should ==
+ '{:field=>{"$gte"=>Mon, 02 Jan 2012 00:00:00 UTC +00:00, "$lte"=>Sun, 08 Jan 2012 23:59:59 UTC +00:00}}'
+ @abstract_model.send(:build_statement, :field, type, "", "less_than").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "1", "less_than").to_s.should ==
+ '{:field=>{"$gte"=>Sat, 14 Jan 2012 12:00:00 UTC +00:00}}'
+ @abstract_model.send(:build_statement, :field, type, "1", "more_than").to_s.should ==
+ '{:field=>{"$lte"=>Sat, 14 Jan 2012 12:00:00 UTC +00:00}}'
+ end
+ @abstract_model.send(:build_statement, :field, type, "", "mmddyyyy").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "201105", "mmddyyyy").should be_nil
+ @abstract_model.send(:build_statement, :field, type, "12312011", "mmddyyyy").to_s.should ==
+ '{:field=>{"$gte"=>Sat, 31 Dec 2011 00:00:00 UTC +00:00, "$lte"=>Sat, 31 Dec 2011 23:59:59 UTC +00:00}}'
+ end
+ end
+
+ it "supports enum type query" do
+ @abstract_model.send(:build_statement, :field, :enum, "1", nil).should == {:field => {"$in" => ["1"]}}
+ end
+ end
+
+ describe "model attribute method" do
+ before do
+ @abstract_model = RailsAdmin::AbstractModel.new('Article')
+ end
+
+ it "#scoped returns relation object" do
+ @abstract_model.scoped.should be_instance_of(Mongoid::Criteria)
+ end
+
+ it "#table_name works" do
+ @abstract_model.table_name.should == 'articles'
+ end
+
+ it "#serialized_attributes works" do
+ class MongoUser
+ include Mongoid::Document
+ field :name, :type => String
+ field :array_field, :type => Array
+ field :hash_field, :type => Hash
+ end
+
+ RailsAdmin::AbstractModel.new('MongoUser').serialized_attributes.keys.should == ["array_field", "hash_field"]
+ end
+ end
+end

0 comments on commit 0b6ec58

Please sign in to comment.
Something went wrong with that request. Please try again.