diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-10-22 17:37:16 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2014-10-22 17:37:16 +0000 |
commit | 2d1866d966d94c688f9cb87c5bf3f096dffac844 (patch) | |
tree | 7a733c1cc51448ab69b3f892285305dbfb0ae15e /app/models | |
parent | a6ec78a4dc658e3517ed682792016b6530458696 (diff) | |
download | redmine-2d1866d966d94c688f9cb87c5bf3f096dffac844.tar.gz redmine-2d1866d966d94c688f9cb87c5bf3f096dffac844.zip |
Merged rails-4.1 branch (#14534).
git-svn-id: http://svn.redmine.org/redmine/trunk@13482 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app/models')
50 files changed, 283 insertions, 189 deletions
diff --git a/app/models/attachment.rb b/app/models/attachment.rb index f36f7cccf..9d7e03247 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -27,6 +27,7 @@ class Attachment < ActiveRecord::Base validates_length_of :disk_filename, :maximum => 255 validates_length_of :description, :maximum => 255 validate :validate_max_file_size + attr_protected :id acts_as_event :title => :filename, :url => Proc.new {|o| {:controller => 'attachments', :action => 'download', :id => o.id, :filename => o.filename}} @@ -34,16 +35,16 @@ class Attachment < ActiveRecord::Base acts_as_activity_provider :type => 'files', :permission => :view_files, :author_key => :author_id, - :find_options => {:select => "#{Attachment.table_name}.*", - :joins => "LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " + - "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )"} + :scope => select("#{Attachment.table_name}.*"). + joins("LEFT JOIN #{Version.table_name} ON #{Attachment.table_name}.container_type='Version' AND #{Version.table_name}.id = #{Attachment.table_name}.container_id " + + "LEFT JOIN #{Project.table_name} ON #{Version.table_name}.project_id = #{Project.table_name}.id OR ( #{Attachment.table_name}.container_type='Project' AND #{Attachment.table_name}.container_id = #{Project.table_name}.id )") acts_as_activity_provider :type => 'documents', :permission => :view_documents, :author_key => :author_id, - :find_options => {:select => "#{Attachment.table_name}.*", - :joins => "LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " + - "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id"} + :scope => select("#{Attachment.table_name}.*"). + joins("LEFT JOIN #{Document.table_name} ON #{Attachment.table_name}.container_type='Document' AND #{Document.table_name}.id = #{Attachment.table_name}.container_id " + + "LEFT JOIN #{Project.table_name} ON #{Document.table_name}.project_id = #{Project.table_name}.id") cattr_accessor :storage_path @@storage_path = Redmine::Configuration['attachments_storage_path'] || File.join(Rails.root, "files") @@ -74,7 +75,7 @@ class Attachment < ActiveRecord::Base if @temp_file.size > 0 if @temp_file.respond_to?(:original_filename) self.filename = @temp_file.original_filename - self.filename.force_encoding("UTF-8") if filename.respond_to?(:force_encoding) + self.filename.force_encoding("UTF-8") end if @temp_file.respond_to?(:content_type) self.content_type = @temp_file.content_type.to_s.chomp diff --git a/app/models/auth_source.rb b/app/models/auth_source.rb index 1494e161a..aa2506e5b 100644 --- a/app/models/auth_source.rb +++ b/app/models/auth_source.rb @@ -29,6 +29,7 @@ class AuthSource < ActiveRecord::Base validates_presence_of :name validates_uniqueness_of :name validates_length_of :name, :maximum => 60 + attr_protected :id def authenticate(login, password) end diff --git a/app/models/board.rb b/app/models/board.rb index 387792cb4..2cbeb0a8a 100644 --- a/app/models/board.rb +++ b/app/models/board.rb @@ -18,8 +18,8 @@ class Board < ActiveRecord::Base include Redmine::SafeAttributes belongs_to :project - has_many :topics, :class_name => 'Message', :conditions => "#{Message.table_name}.parent_id IS NULL", :order => "#{Message.table_name}.created_on DESC" - has_many :messages, :dependent => :destroy, :order => "#{Message.table_name}.created_on DESC" + has_many :topics, lambda {where("#{Message.table_name}.parent_id IS NULL").order("#{Message.table_name}.created_on DESC")}, :class_name => 'Message' + has_many :messages, lambda {order("#{Message.table_name}.created_on DESC")}, :dependent => :destroy belongs_to :last_message, :class_name => 'Message', :foreign_key => :last_message_id acts_as_tree :dependent => :nullify acts_as_list :scope => '(project_id = #{project_id} AND parent_id #{parent_id ? "= #{parent_id}" : "IS NULL"})' @@ -29,9 +29,12 @@ class Board < ActiveRecord::Base validates_length_of :name, :maximum => 30 validates_length_of :description, :maximum => 255 validate :validate_board + attr_protected :id scope :visible, lambda {|*args| - includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + joins(:project). + references(:project). + where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) } safe_attributes 'name', 'description', 'parent_id', 'move_to' diff --git a/app/models/change.rb b/app/models/change.rb index 6ef56e0de..b24c1bd54 100644 --- a/app/models/change.rb +++ b/app/models/change.rb @@ -21,6 +21,7 @@ class Change < ActiveRecord::Base validates_presence_of :changeset_id, :action, :path before_save :init_path before_validation :replace_invalid_utf8_of_path + attr_protected :id def relative_path changeset.repository.relative_path(path) diff --git a/app/models/changeset.rb b/app/models/changeset.rb index cf58c6e07..b12ea6778 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -35,20 +35,23 @@ class Changeset < ActiveRecord::Base :url => Proc.new {|o| {:controller => 'repositories', :action => 'revision', :id => o.repository.project, :repository_id => o.repository.identifier_param, :rev => o.identifier}} acts_as_searchable :columns => 'comments', - :include => {:repository => :project}, + :scope => preload(:repository => :project), :project_key => "#{Repository.table_name}.project_id", :date_column => 'committed_on' acts_as_activity_provider :timestamp => "#{table_name}.committed_on", :author_key => :user_id, - :find_options => {:include => [:user, {:repository => :project}]} + :scope => preload(:user, {:repository => :project}) validates_presence_of :repository_id, :revision, :committed_on, :commit_date validates_uniqueness_of :revision, :scope => :repository_id validates_uniqueness_of :scmid, :scope => :repository_id, :allow_nil => true + attr_protected :id scope :visible, lambda {|*args| - includes(:repository => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args)) + joins(:repository => :project). + references(:repository => :project). + where(Project.allowed_to_condition(args.shift || User.current, :view_changesets, *args)) } after_create :scan_for_issues diff --git a/app/models/comment.rb b/app/models/comment.rb index 02d9a3b36..9893ad664 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -21,6 +21,7 @@ class Comment < ActiveRecord::Base belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' validates_presence_of :commented, :author, :comments + attr_protected :id after_create :send_notification diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index 48ef2c7cc..f3829bf70 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -29,6 +29,7 @@ class CustomField < ActiveRecord::Base validates_length_of :name, :maximum => 30 validates_inclusion_of :field_format, :in => Proc.new { Redmine::FieldFormat.available_formats } validate :validate_custom_field + attr_protected :id before_validation :set_searchable before_save do |field| @@ -117,7 +118,7 @@ class CustomField < ActiveRecord::Base values = read_attribute(:possible_values) if values.is_a?(Array) values.each do |value| - value.force_encoding('UTF-8') if value.respond_to?(:force_encoding) + value.force_encoding('UTF-8') end values else @@ -218,7 +219,7 @@ class CustomField < ActiveRecord::Base # to move in project_custom_field def self.for_all - where(:is_for_all => true).order('position').all + where(:is_for_all => true).order('position').to_a end def type_name diff --git a/app/models/custom_value.rb b/app/models/custom_value.rb index 952646311..ab2eee744 100644 --- a/app/models/custom_value.rb +++ b/app/models/custom_value.rb @@ -18,6 +18,7 @@ class CustomValue < ActiveRecord::Base belongs_to :custom_field belongs_to :customized, :polymorphic => true + attr_protected :id def initialize(attributes=nil, *args) super diff --git a/app/models/document.rb b/app/models/document.rb index 4609d8602..c195f64ca 100644 --- a/app/models/document.rb +++ b/app/models/document.rb @@ -21,19 +21,23 @@ class Document < ActiveRecord::Base belongs_to :category, :class_name => "DocumentCategory", :foreign_key => "category_id" acts_as_attachable :delete_permission => :delete_documents - acts_as_searchable :columns => ['title', "#{table_name}.description"], :include => :project + acts_as_searchable :columns => ['title', "#{table_name}.description"], + :scope => preload(:project) acts_as_event :title => Proc.new {|o| "#{l(:label_document)}: #{o.title}"}, :author => Proc.new {|o| o.attachments.reorder("#{Attachment.table_name}.created_on ASC").first.try(:author) }, :url => Proc.new {|o| {:controller => 'documents', :action => 'show', :id => o.id}} - acts_as_activity_provider :find_options => {:include => :project} + acts_as_activity_provider :scope => preload(:project) validates_presence_of :project, :title, :category validates_length_of :title, :maximum => 60 + attr_protected :id after_create :send_notification scope :visible, lambda {|*args| - includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_documents, *args)) + joins(:project). + references(:project). + where(Project.allowed_to_condition(args.shift || User.current, :view_documents, *args)) } safe_attributes 'category_id', 'title', 'description' diff --git a/app/models/enabled_module.rb b/app/models/enabled_module.rb index 1cc84aaa5..baf6cdd11 100644 --- a/app/models/enabled_module.rb +++ b/app/models/enabled_module.rb @@ -21,6 +21,7 @@ class EnabledModule < ActiveRecord::Base validates_presence_of :name validates_uniqueness_of :name, :scope => :project_id + attr_protected :id after_create :module_enabled diff --git a/app/models/enumeration.rb b/app/models/enumeration.rb index b4b64b7fc..8c82a0dc8 100644 --- a/app/models/enumeration.rb +++ b/app/models/enumeration.rb @@ -18,7 +18,7 @@ class Enumeration < ActiveRecord::Base include Redmine::SubclassFactory - default_scope :order => "#{Enumeration.table_name}.position ASC" + default_scope lambda {order("#{Enumeration.table_name}.position ASC")} belongs_to :project diff --git a/app/models/group.rb b/app/models/group.rb index 7b82d1c1f..b9d4d9ef4 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -28,6 +28,7 @@ class Group < Principal validates_presence_of :lastname validates_uniqueness_of :lastname, :case_sensitive => false validates_length_of :lastname, :maximum => 255 + attr_protected :id before_destroy :remove_references_before_destroy @@ -81,7 +82,8 @@ class Group < Principal def user_removed(user) members.each do |member| MemberRole. - includes(:member). + joins(:member). + references(:member). where("#{Member.table_name}.user_id = ? AND #{MemberRole.table_name}.inherited_from IN (?)", user.id, member.member_role_ids). each(&:destroy) end @@ -95,10 +97,6 @@ class Group < Principal super(attr_name, *args) end - def self.builtin_id(arg) - (arg.anonymous? ? GroupAnonymous : GroupNonMember).instance_id - end - def self.anonymous GroupAnonymous.load_instance end diff --git a/app/models/group_anonymous.rb b/app/models/group_anonymous.rb index c3c821bb2..a6d01853d 100644 --- a/app/models/group_anonymous.rb +++ b/app/models/group_anonymous.rb @@ -23,8 +23,4 @@ class GroupAnonymous < GroupBuiltin def builtin_type "anonymous" end - - def self.instance_id - @@instance_id ||= load_instance.id - end end diff --git a/app/models/group_builtin.rb b/app/models/group_builtin.rb index 71ecc06ab..538a89eea 100644 --- a/app/models/group_builtin.rb +++ b/app/models/group_builtin.rb @@ -37,7 +37,7 @@ class GroupBuiltin < Group class << self def load_instance return nil if self == GroupBuiltin - instance = first(:order => 'id') || create_instance + instance = order('id').first || create_instance end def create_instance diff --git a/app/models/group_non_member.rb b/app/models/group_non_member.rb index 8b3dfd4aa..9f78f0b46 100644 --- a/app/models/group_non_member.rb +++ b/app/models/group_non_member.rb @@ -23,8 +23,4 @@ class GroupNonMember < GroupBuiltin def builtin_type "non_member" end - - def self.instance_id - @@instance_id ||= load_instance.id - end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 1c1c99dec..bb45a6f58 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -31,15 +31,12 @@ class Issue < ActiveRecord::Base has_many :journals, :as => :journalized, :dependent => :destroy has_many :visible_journals, + lambda {where(["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false])}, :class_name => 'Journal', - :as => :journalized, - :conditions => Proc.new { - ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false] - }, - :readonly => true + :as => :journalized has_many :time_entries, :dependent => :destroy - has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC" + has_and_belongs_to_many :changesets, lambda {order("#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC")} has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all @@ -49,14 +46,19 @@ class Issue < ActiveRecord::Base acts_as_customizable acts_as_watchable acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"], - :include => [:project, :visible_journals], # sort by id so that limited eager loading doesn't break with postgresql - :order_column => "#{table_name}.id" + :order_column => "#{table_name}.id", + :scope => lambda { joins(:project). + joins("LEFT OUTER JOIN #{Journal.table_name} ON #{Journal.table_name}.journalized_type='Issue'" + + " AND #{Journal.table_name}.journalized_id = #{Issue.table_name}.id" + + " AND (#{Journal.table_name}.private_notes = #{connection.quoted_false}" + + " OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))") } + acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"}, :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}}, :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') } - acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]}, + acts_as_activity_provider :scope => preload(:project, :author, :tracker), :author_key => :author_id DONE_RATIO_OPTIONS = %w(issue_field issue_status) @@ -72,19 +74,26 @@ class Issue < ActiveRecord::Base validates :start_date, :date => true validates :due_date, :date => true validate :validate_issue, :validate_required_fields + attr_protected :id scope :visible, lambda {|*args| - includes(:project).where(Issue.visible_condition(args.shift || User.current, *args)) + joins(:project). + references(:project). + where(Issue.visible_condition(args.shift || User.current, *args)) } scope :open, lambda {|*args| is_closed = args.size > 0 ? !args.first : false - includes(:status).where("#{IssueStatus.table_name}.is_closed = ?", is_closed) + joins(:status). + references(:status). + where("#{IssueStatus.table_name}.is_closed = ?", is_closed) } scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") } scope :on_active_project, lambda { - includes(:status, :project, :tracker).where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE) + joins(:project). + references(:project). + where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE) } scope :fixed_version, lambda {|versions| ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v} @@ -107,7 +116,7 @@ class Issue < ActiveRecord::Base # Returns a SQL conditions string used to find all issues visible by the specified user def self.visible_condition(user, options={}) Project.allowed_to_condition(user, :view_issues, options) do |role, user| - if user.logged? + if user.id && user.logged? case role.issues_visibility when 'all' nil @@ -351,6 +360,10 @@ class Issue < ActiveRecord::Base # Do not redefine alias chain on reload (see #4838) alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first) + def attributes=(new_attributes) + assign_attributes new_attributes + end + def estimated_hours=(h) write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h) end @@ -423,7 +436,7 @@ class Issue < ActiveRecord::Base def safe_attributes=(attrs, user=User.current) return unless attrs.is_a?(Hash) - attrs = attrs.dup + attrs = attrs.deep_dup # Project and Tracker must be set before since new_statuses_allowed_to depends on it. if (p = attrs.delete('project_id')) && safe_attribute?('project_id') @@ -458,14 +471,12 @@ class Issue < ActiveRecord::Base if attrs['custom_field_values'].present? editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s} - # TODO: use #select when ruby1.8 support is dropped - attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| !editable_custom_field_ids.include?(k.to_s)} + attrs['custom_field_values'].select! {|k, v| editable_custom_field_ids.include?(k.to_s)} end if attrs['custom_fields'].present? editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s} - # TODO: use #select when ruby1.8 support is dropped - attrs['custom_fields'] = attrs['custom_fields'].reject {|c| !editable_custom_field_ids.include?(c['id'].to_s)} + attrs['custom_fields'].select! {|c| editable_custom_field_ids.include?(c['id'].to_s)} end # mass-assignment security bypass @@ -733,7 +744,7 @@ class Issue < ActiveRecord::Base def assignable_versions return @assignable_versions if @assignable_versions - versions = project.shared_versions.open.all + versions = project.shared_versions.open.to_a if fixed_version if fixed_version_id_changed? # nothing to do @@ -879,10 +890,14 @@ class Issue < ActiveRecord::Base if issues.any? issue_ids = issues.map(&:id) # Relations with issue_from in given issues and visible issue_to - relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all + relations_from = IssueRelation.joins(:issue_to => :project). + references(:issue_to => :project). + where(visible_condition(user)).where(:issue_from_id => issue_ids).to_a # Relations with issue_to in given issues and visible issue_from - relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all - + relations_to = IssueRelation.joins(:issue_from => :project). + references(:issue_from => :project). + where(visible_condition(user)). + where(:issue_to_id => issue_ids).to_a issues.each do |issue| relations = relations_from.select {|relation| relation.issue_from_id == issue.id} + @@ -1121,6 +1136,7 @@ class Issue < ActiveRecord::Base def parent_issue_id=(arg) s = arg.to_s.strip.presence if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1])) + @parent_issue.id @invalid_parent_issue_id = nil elsif s.blank? @parent_issue = nil @@ -1349,7 +1365,7 @@ class Issue < ActiveRecord::Base self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id) cond = ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt] self.class.base_class.select('id').lock(true).where(cond) - offset = right_most_bound + 1 - lft + offset = rdm_right_most_bound + 1 - lft Issue.where(cond). update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset]) end @@ -1367,6 +1383,14 @@ class Issue < ActiveRecord::Base recalculate_attributes_for(former_parent_id) if former_parent_id end + def rdm_right_most_bound + right_most_node = + self.class.base_class.unscoped. + order("#{quoted_right_column_full_name} desc").limit(1).lock(true).first + right_most_node ? (right_most_node[right_column_name] || 0 ) : 0 + end + private :rdm_right_most_bound + def update_parent_attributes recalculate_attributes_for(parent_id) if parent_id end @@ -1395,7 +1419,7 @@ class Issue < ActiveRecord::Base end done = p.leaves.joins(:status). sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " + - "* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f + "* (CASE WHEN is_closed = #{self.class.connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f progress = done / (average * leaves_count) p.done_ratio = progress.round end @@ -1415,7 +1439,8 @@ class Issue < ActiveRecord::Base def self.update_versions(conditions=nil) # Only need to update issues with a fixed_version from # a different project and that is not systemwide shared - Issue.includes(:project, :fixed_version). + Issue.joins(:project, :fixed_version). + references(:version, :fixed_version). where("#{Issue.table_name}.fixed_version_id IS NOT NULL" + " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" + " AND #{Version.table_name}.sharing <> 'system'"). diff --git a/app/models/issue_category.rb b/app/models/issue_category.rb index 7bee6f234..e5e508861 100644 --- a/app/models/issue_category.rb +++ b/app/models/issue_category.rb @@ -24,6 +24,7 @@ class IssueCategory < ActiveRecord::Base validates_presence_of :name validates_uniqueness_of :name, :scope => [:project_id] validates_length_of :name, :maximum => 30 + attr_protected :id safe_attributes 'name', 'assigned_to_id' diff --git a/app/models/issue_custom_field.rb b/app/models/issue_custom_field.rb index a1d16333d..42695e461 100644 --- a/app/models/issue_custom_field.rb +++ b/app/models/issue_custom_field.rb @@ -32,7 +32,7 @@ class IssueCustomField < CustomField sql = super id_column ||= id tracker_condition = "#{Issue.table_name}.tracker_id IN (SELECT tracker_id FROM #{table_name_prefix}custom_fields_trackers#{table_name_suffix} WHERE custom_field_id = #{id_column})" - project_condition = "EXISTS (SELECT 1 FROM #{CustomField.table_name} ifa WHERE ifa.is_for_all = #{connection.quoted_true} AND ifa.id = #{id_column})" + + project_condition = "EXISTS (SELECT 1 FROM #{CustomField.table_name} ifa WHERE ifa.is_for_all = #{self.class.connection.quoted_true} AND ifa.id = #{id_column})" + " OR #{Issue.table_name}.project_id IN (SELECT project_id FROM #{table_name_prefix}custom_fields_projects#{table_name_suffix} WHERE custom_field_id = #{id_column})" "((#{sql}) AND (#{tracker_condition}) AND (#{project_condition}))" diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb index c9115100c..9ca0f3077 100644 --- a/app/models/issue_query.rb +++ b/app/models/issue_query.rb @@ -45,7 +45,9 @@ class IssueQuery < Query scope :visible, lambda {|*args| user = args.shift || User.current base = Project.allowed_to_condition(user, :view_issues, *args) - scope = includes(:project).where("#{table_name}.project_id IS NULL OR (#{base})") + scope = joins("LEFT OUTER JOIN #{Project.table_name} ON #{table_name}.project_id = #{Project.table_name}.id"). + references(:project). + where("#{table_name}.project_id IS NULL OR (#{base})") if user.admin? scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id) @@ -132,17 +134,17 @@ class IssueQuery < Query if project principals += project.principals.sort unless project.leaf? - subprojects = project.descendants.visible.all + subprojects = project.descendants.visible.to_a principals += Principal.member_of(subprojects) end - versions = project.shared_versions.all - categories = project.issue_categories.all + versions = project.shared_versions.to_a + categories = project.issue_categories.to_a issue_custom_fields = project.all_issue_custom_fields else if all_projects.any? principals += Principal.member_of(all_projects) end - versions = Version.visible.where(:sharing => 'system').all + versions = Version.visible.where(:sharing => 'system').to_a issue_custom_fields = IssueCustomField.where(:is_for_all => true) end principals.uniq! @@ -339,7 +341,7 @@ class IssueQuery < Query scope = scope.preload(:author) end - issues = scope.all + issues = scope.to_a if has_column?(:spent_hours) Issue.load_visible_spent_hours(issues) @@ -360,12 +362,13 @@ class IssueQuery < Query joins(:status, :project). where(statement). includes(([:status, :project] + (options[:include] || [])).uniq). + references(([:status, :project] + (options[:include] || [])).uniq). where(options[:conditions]). order(order_option). joins(joins_for_order_statement(order_option.join(','))). limit(options[:limit]). offset(options[:offset]). - find_ids + pluck(:id) rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end @@ -380,7 +383,7 @@ class IssueQuery < Query limit(options[:limit]). offset(options[:offset]). preload(:details, :user, {:issue => [:project, :author, :tracker, :status]}). - all + to_a rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end @@ -392,7 +395,8 @@ class IssueQuery < Query where(project_statement). where(options[:conditions]). includes(:project). - all + references(:project). + to_a rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end @@ -411,7 +415,7 @@ class IssueQuery < Query groups = Group.givable operator = '!' # Override the operator since we want to find by assigned_to else - groups = Group.where(:id => value).all + groups = Group.where(:id => value).to_a end groups ||= [] @@ -431,7 +435,7 @@ class IssueQuery < Query " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" when "=", "!" role_cond = value.any? ? - "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : + "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" : "1=0" sw = operator == "!" ? 'NOT' : '' @@ -443,7 +447,7 @@ class IssueQuery < Query def sql_for_is_private_field(field, operator, value) op = (operator == "=" ? 'IN' : 'NOT IN') - va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') + va = value.map {|v| v == '0' ? self.class.connection.quoted_false : self.class.connection.quoted_true}.uniq.join(',') "#{Issue.table_name}.is_private #{op} (#{va})" end @@ -462,14 +466,14 @@ class IssueQuery < Query sql = case operator when "*", "!*" op = (operator == "*" ? 'IN' : 'NOT IN') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}')" when "=", "!" op = (operator == "=" ? 'IN' : 'NOT IN') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" when "=p", "=!p", "!p" op = (operator == "!p" ? 'NOT IN' : 'IN') comp = (operator == "=!p" ? '<>' : '=') - "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" + "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" end if relation_options[:sym] == field && !options[:reverse] diff --git a/app/models/issue_status.rb b/app/models/issue_status.rb index d19ff50c5..ba0624a04 100644 --- a/app/models/issue_status.rb +++ b/app/models/issue_status.rb @@ -27,6 +27,7 @@ class IssueStatus < ActiveRecord::Base validates_uniqueness_of :name validates_length_of :name, :maximum => 30 validates_inclusion_of :default_done_ratio, :in => 0..100, :allow_nil => true + attr_protected :id scope :sorted, lambda { order("#{table_name}.position ASC") } scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} @@ -79,7 +80,7 @@ class IssueStatus < ActiveRecord::Base includes(:new_status). where(["role_id IN (:role_ids) AND tracker_id = :tracker_id AND (#{conditions})", {:role_ids => roles.collect(&:id), :tracker_id => tracker.id, :true => true, :false => false} - ]).all. + ]).to_a. map(&:new_status).compact.sort else [] diff --git a/app/models/journal.rb b/app/models/journal.rb index e3de2b38a..543da42b8 100644 --- a/app/models/journal.rb +++ b/app/models/journal.rb @@ -24,6 +24,7 @@ class Journal < ActiveRecord::Base belongs_to :user has_many :details, :class_name => "JournalDetail", :dependent => :delete_all attr_accessor :indice + attr_protected :id acts_as_event :title => Proc.new {|o| status = ((s = o.new_status) ? " (#{s})" : nil); "#{o.issue.tracker} ##{o.issue.id}#{status}: #{o.issue.subject}" }, :description => :notes, @@ -34,17 +35,18 @@ class Journal < ActiveRecord::Base acts_as_activity_provider :type => 'issues', :author_key => :user_id, - :find_options => {:include => [{:issue => :project}, :details, :user], - :conditions => "#{Journal.table_name}.journalized_type = 'Issue' AND" + - " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')"} + :scope => preload({:issue => :project}, :user). + joins("LEFT OUTER JOIN #{JournalDetail.table_name} ON #{JournalDetail.table_name}.journal_id = #{Journal.table_name}.id"). + where("#{Journal.table_name}.journalized_type = 'Issue' AND" + + " (#{JournalDetail.table_name}.prop_key = 'status_id' OR #{Journal.table_name}.notes <> '')") before_create :split_private_notes after_create :send_notification scope :visible, lambda {|*args| user = args.shift || User.current - - includes(:issue => :project). + joins(:issue => :project). + references(:project). where(Issue.visible_condition(user, *args)). where("(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(user, :view_private_notes, *args)}))", false) } diff --git a/app/models/journal_detail.rb b/app/models/journal_detail.rb index 2557ce87d..e67c19840 100644 --- a/app/models/journal_detail.rb +++ b/app/models/journal_detail.rb @@ -18,6 +18,7 @@ class JournalDetail < ActiveRecord::Base belongs_to :journal before_save :normalize_values + attr_protected :id def custom_field if property == 'cf' diff --git a/app/models/mail_handler.rb b/app/models/mail_handler.rb index 4aaa21c98..be737931c 100644 --- a/app/models/mail_handler.rb +++ b/app/models/mail_handler.rb @@ -42,7 +42,7 @@ class MailHandler < ActionMailer::Base @@handler_options[:no_notification] = (@@handler_options[:no_notification].to_s == '1') @@handler_options[:no_permission_check] = (@@handler_options[:no_permission_check].to_s == '1') - email.force_encoding('ASCII-8BIT') if email.respond_to?(:force_encoding) + email.force_encoding('ASCII-8BIT') super(email) end @@ -417,7 +417,7 @@ class MailHandler < ActionMailer::Base end parts.reject! do |part| - part.header[:content_disposition].try(:disposition_type) == 'attachment' + part.attachment? end @plain_text_body = parts.map do |p| diff --git a/app/models/member.rb b/app/models/member.rb index aa1d50cdf..8256d2e68 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -25,6 +25,7 @@ class Member < ActiveRecord::Base validates_presence_of :principal, :project validates_uniqueness_of :user_id, :scope => :project_id validate :validate_role + attr_protected :id before_destroy :set_issue_category_nil diff --git a/app/models/member_role.rb b/app/models/member_role.rb index 74d4c631f..038509026 100644 --- a/app/models/member_role.rb +++ b/app/models/member_role.rb @@ -26,6 +26,7 @@ class MemberRole < ActiveRecord::Base validates_presence_of :role validate :validate_role_member + attr_protected :id def validate_role_member errors.add :role_id, :invalid if role && !role.member? diff --git a/app/models/message.rb b/app/models/message.rb index 576be4763..028a3eb55 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -22,9 +22,10 @@ class Message < ActiveRecord::Base acts_as_tree :counter_cache => :replies_count, :order => "#{Message.table_name}.created_on ASC" acts_as_attachable belongs_to :last_reply, :class_name => 'Message', :foreign_key => 'last_reply_id' + attr_protected :id acts_as_searchable :columns => ['subject', 'content'], - :include => {:board => :project}, + :scope => preload(:board => :project), :project_key => "#{Board.table_name}.project_id", :date_column => "#{table_name}.created_on" acts_as_event :title => Proc.new {|o| "#{o.board.name}: #{o.subject}"}, @@ -34,7 +35,7 @@ class Message < ActiveRecord::Base :url => Proc.new {|o| {:controller => 'messages', :action => 'show', :board_id => o.board_id}.merge(o.parent_id.nil? ? {:id => o.id} : {:id => o.parent_id, :r => o.id, :anchor => "message-#{o.id}"})} - acts_as_activity_provider :find_options => {:include => [{:board => :project}, :author]}, + acts_as_activity_provider :scope => preload({:board => :project}, :author), :author_key => :author_id acts_as_watchable @@ -48,7 +49,9 @@ class Message < ActiveRecord::Base after_create :send_notification scope :visible, lambda {|*args| - includes(:board => :project).where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) + joins(:board => :project). + references(:board => :project). + where(Project.allowed_to_condition(args.shift || User.current, :view_messages, *args)) } safe_attributes 'subject', 'content' diff --git a/app/models/news.rb b/app/models/news.rb index b86e3f63d..baaa92fba 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -19,16 +19,18 @@ class News < ActiveRecord::Base include Redmine::SafeAttributes belongs_to :project belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' - has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on" + has_many :comments, lambda {order("created_on")}, :as => :commented, :dependent => :delete_all validates_presence_of :title, :description validates_length_of :title, :maximum => 60 validates_length_of :summary, :maximum => 255 + attr_protected :id acts_as_attachable :delete_permission => :manage_news - acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], :include => :project + acts_as_searchable :columns => ['title', 'summary', "#{table_name}.description"], + :scope => preload(:project) acts_as_event :url => Proc.new {|o| {:controller => 'news', :action => 'show', :id => o.id}} - acts_as_activity_provider :find_options => {:include => [:project, :author]}, + acts_as_activity_provider :scope => preload(:project, :author), :author_key => :author_id acts_as_watchable @@ -36,7 +38,9 @@ class News < ActiveRecord::Base after_create :send_notification scope :visible, lambda {|*args| - includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) + joins(:project). + references([:author, :project]). + where(Project.allowed_to_condition(args.shift || User.current, :view_news, *args)) } safe_attributes 'title', 'summary', 'description' @@ -68,7 +72,7 @@ class News < ActiveRecord::Base # returns latest news for projects visible by user def self.latest(user = User.current, count = 5) - visible(user).includes([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).all + visible(user).joins([:author, :project]).order("#{News.table_name}.created_on DESC").limit(count).to_a end private diff --git a/app/models/principal.rb b/app/models/principal.rb index 1bbbbe88e..d10241b3f 100644 --- a/app/models/principal.rb +++ b/app/models/principal.rb @@ -25,11 +25,13 @@ class Principal < ActiveRecord::Base STATUS_LOCKED = 3 has_many :members, :foreign_key => 'user_id', :dependent => :destroy - has_many :memberships, :class_name => 'Member', - :foreign_key => 'user_id', - :include => [:project, :roles], - :conditions => "#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}", - :order => "#{Project.table_name}.name" + has_many :memberships, + lambda {preload(:project, :roles). + joins(:project). + where("#{Project.table_name}.status<>#{Project::STATUS_ARCHIVED}"). + order("#{Project.table_name}.name")}, + :class_name => 'Member', + :foreign_key => 'user_id' has_many :projects, :through => :memberships has_many :issue_categories, :foreign_key => 'assigned_to_id', :dependent => :nullify @@ -56,8 +58,8 @@ class Principal < ActiveRecord::Base # Principals that are members of a collection of projects scope :member_of, lambda {|projects| - projects = [projects] unless projects.is_a?(Array) - if projects.empty? + projects = [projects] if projects.is_a?(Project) + if projects.blank? where("1=0") else ids = projects.map(&:id) diff --git a/app/models/project.rb b/app/models/project.rb index 6a428b209..c2b8af134 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -28,31 +28,35 @@ class Project < ActiveRecord::Base # Specific overridden Activities has_many :time_entry_activities - has_many :members, :include => [:principal, :roles], :conditions => "#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}" + has_many :members, + lambda { joins(:principal, :roles). + references(:principal, :roles). + where("#{Principal.table_name}.type='User' AND #{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}") } has_many :memberships, :class_name => 'Member' - has_many :member_principals, :class_name => 'Member', - :include => :principal, - :conditions => "#{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}" - + has_many :member_principals, + lambda { joins(:principal). + references(:principal). + where("#{Principal.table_name}.status=#{Principal::STATUS_ACTIVE}")}, + :class_name => 'Member' has_many :enabled_modules, :dependent => :delete_all - has_and_belongs_to_many :trackers, :order => "#{Tracker.table_name}.position" - has_many :issues, :dependent => :destroy, :include => [:status, :tracker] + has_and_belongs_to_many :trackers, lambda {order("#{Tracker.table_name}.position")} + has_many :issues, :dependent => :destroy has_many :issue_changes, :through => :issues, :source => :journals - has_many :versions, :dependent => :destroy, :order => "#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC" + has_many :versions, lambda {order("#{Version.table_name}.effective_date DESC, #{Version.table_name}.name DESC")}, :dependent => :destroy has_many :time_entries, :dependent => :destroy has_many :queries, :class_name => 'IssueQuery', :dependent => :delete_all has_many :documents, :dependent => :destroy - has_many :news, :dependent => :destroy, :include => :author - has_many :issue_categories, :dependent => :delete_all, :order => "#{IssueCategory.table_name}.name" - has_many :boards, :dependent => :destroy, :order => "position ASC" - has_one :repository, :conditions => ["is_default = ?", true] + has_many :news, lambda {includes(:author)}, :dependent => :destroy + has_many :issue_categories, lambda {order("#{IssueCategory.table_name}.name")}, :dependent => :delete_all + has_many :boards, lambda {order("position ASC")}, :dependent => :destroy + has_one :repository, lambda {where(["is_default = ?", true])} has_many :repositories, :dependent => :destroy has_many :changesets, :through => :repository has_one :wiki, :dependent => :destroy # Custom field for the project issues has_and_belongs_to_many :issue_custom_fields, + lambda {order("#{CustomField.table_name}.position")}, :class_name => 'IssueCustomField', - :order => "#{CustomField.table_name}.position", :join_table => "#{table_name_prefix}custom_fields_projects#{table_name_suffix}", :association_foreign_key => 'custom_field_id' @@ -126,9 +130,9 @@ class Project < ActiveRecord::Base if !initialized.key?('trackers') && !initialized.key?('tracker_ids') default = Setting.default_projects_tracker_ids if default.is_a?(Array) - self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.all + self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a else - self.trackers = Tracker.sorted.all + self.trackers = Tracker.sorted.to_a end end end @@ -144,7 +148,7 @@ class Project < ActiveRecord::Base # returns latest created projects # non public projects will be returned only if user is a member of those def self.latest(user=nil, count=5) - visible(user).limit(count).order("created_on DESC").all + visible(user).limit(count).order("created_on DESC").to_a end # Returns true if the project is visible to +user+ or to the current user. @@ -212,9 +216,9 @@ class Project < ActiveRecord::Base end def override_roles(role) - @override_members ||= memberships.where(:user_id => [GroupAnonymous.instance_id, GroupNonMember.instance_id]).all - member = @override_members.detect {|m| role.anonymous? ^ (m.user_id == GroupNonMember.instance_id)} - member ? member.roles : [role] + group_class = role.anonymous? ? GroupAnonymous : GroupNonMember + member = member_principals.where("#{Principal.table_name}.type = ?", group_class.name).first + member ? member.roles.to_a : [role] end def principals @@ -364,7 +368,7 @@ class Project < ActiveRecord::Base # by the current user def allowed_parents return @allowed_parents if @allowed_parents - @allowed_parents = Project.where(Project.allowed_to_condition(User.current, :add_subprojects)).all + @allowed_parents = Project.where(Project.allowed_to_condition(User.current, :add_subprojects)).to_a @allowed_parents = @allowed_parents - self_and_descendants if User.current.allowed_to?(:add_project, nil, :global => true) || (!new_record? && parent.nil?) @allowed_parents << nil @@ -435,11 +439,12 @@ class Project < ActiveRecord::Base @rolled_up_trackers ||= Tracker. joins(:projects). + references(:project). joins("JOIN #{EnabledModule.table_name} ON #{EnabledModule.table_name}.project_id = #{Project.table_name}.id AND #{EnabledModule.table_name}.name = 'issue_tracking'"). select("DISTINCT #{Tracker.table_name}.*"). where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> #{STATUS_ARCHIVED}", lft, rgt). sorted. - all + to_a end # Closes open and locked project versions that are completed @@ -457,7 +462,8 @@ class Project < ActiveRecord::Base def rolled_up_versions @rolled_up_versions ||= Version. - includes(:project). + joins(:project). + references(:project). where("#{Project.table_name}.lft >= ? AND #{Project.table_name}.rgt <= ? AND #{Project.table_name}.status <> ?", lft, rgt, STATUS_ARCHIVED) end @@ -465,13 +471,17 @@ class Project < ActiveRecord::Base def shared_versions if new_record? Version. - includes(:project). + joins(:project). + references(:project). + preload(:project). where("#{Project.table_name}.status <> ? AND #{Version.table_name}.sharing = 'system'", STATUS_ARCHIVED) else @shared_versions ||= begin r = root? ? self : root Version. - includes(:project). + joins(:project). + references(:project). + preload(:project). where("#{Project.table_name}.id = #{id}" + " OR (#{Project.table_name}.status <> #{Project::STATUS_ARCHIVED} AND (" + " #{Version.table_name}.sharing = 'system'" + @@ -497,7 +507,7 @@ class Project < ActiveRecord::Base # Deletes all project's members def delete_all_members me, mr = Member.table_name, MemberRole.table_name - connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})") + self.class.connection.delete("DELETE FROM #{mr} WHERE #{mr}.member_id IN (SELECT #{me}.id FROM #{me} WHERE #{me}.project_id = #{id})") Member.delete_all(['project_id = ?', id]) end @@ -728,7 +738,7 @@ class Project < ActiveRecord::Base project = project.is_a?(Project) ? project : Project.find(project) to_be_copied = %w(wiki versions issue_categories issues members queries boards) - to_be_copied = to_be_copied & options[:only].to_a unless options[:only].nil? + to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil? Project.transaction do if save @@ -740,6 +750,7 @@ class Project < ActiveRecord::Base save end end + true end # Returns a new unsaved Project instance with attributes copied from +project+ @@ -958,11 +969,10 @@ class Project < ActiveRecord::Base def copy_queries(project) project.queries.each do |query| new_query = IssueQuery.new - new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria", "user_id", "type") + new_query.attributes = query.attributes.dup.except("id", "project_id", "sort_criteria") new_query.sort_criteria = query.sort_criteria if query.sort_criteria new_query.project = self new_query.user_id = query.user_id - new_query.role_ids = query.role_ids if query.visibility == IssueQuery::VISIBILITY_ROLES self.queries << new_query end end diff --git a/app/models/query.rb b/app/models/query.rb index 9ef002c6e..a4d34d1e4 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -288,7 +288,7 @@ class Query < ActiveRecord::Base end def trackers - @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers + @trackers ||= project.nil? ? Tracker.sorted.to_a : project.rolled_up_trackers end # Returns a hash of localized labels for all filter operators @@ -306,7 +306,7 @@ class Query < ActiveRecord::Base end def all_projects - @all_projects ||= Project.visible.all + @all_projects ||= Project.visible.to_a end def all_projects_values @@ -655,7 +655,7 @@ class Query < ActiveRecord::Base sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}" end else - sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" + sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" end else # IN an empty set @@ -663,7 +663,7 @@ class Query < ActiveRecord::Base end when "!" if value.any? - sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))" + sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + "))" else # NOT IN an empty set sql = "1=1" @@ -705,9 +705,9 @@ class Query < ActiveRecord::Base end end when "o" - sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false})" if field == "status_id" when "c" - sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" + sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_true})" if field == "status_id" when "><t-" # between today - n days and today sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0) @@ -769,9 +769,9 @@ class Query < ActiveRecord::Base date = Date.today sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year) when "~" - sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" + sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'" when "!~" - sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" + sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'" else raise "Unknown query operator #{operator}" end @@ -834,7 +834,7 @@ class Query < ActiveRecord::Base if self.class.default_timezone == :utc from = from.utc end - s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from)]) + s << ("#{table}.#{field} > '%s'" % [self.class.connection.quoted_date(from)]) end if to if to.is_a?(Date) @@ -843,7 +843,7 @@ class Query < ActiveRecord::Base if self.class.default_timezone == :utc to = to.utc end - s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to)]) + s << ("#{table}.#{field} <= '%s'" % [self.class.connection.quoted_date(to)]) end s.join(' AND ') end diff --git a/app/models/repository.rb b/app/models/repository.rb index 25b0e1da8..1cab86bd1 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -25,7 +25,7 @@ class Repository < ActiveRecord::Base IDENTIFIER_MAX_LENGTH = 255 belongs_to :project - has_many :changesets, :order => "#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC" + has_many :changesets, lambda{order("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC")} has_many :filechanges, :class_name => 'Change', :through => :changesets serialize :extra_info @@ -45,6 +45,7 @@ class Repository < ActiveRecord::Base validates_format_of :identifier, :with => /\A(?!\d+$)[a-z0-9\-_]*\z/, :allow_blank => true # Checks if the SCM is enabled when creating a repository validate :repo_create_validation, :on => :create + attr_protected :id safe_attributes 'identifier', 'login', @@ -264,7 +265,7 @@ class Repository < ActiveRecord::Base reorder("#{Changeset.table_name}.committed_on DESC, #{Changeset.table_name}.id DESC"). limit(limit). preload(:user). - all + to_a else filechanges. where("path = ?", path.with_leading_slash). @@ -313,7 +314,8 @@ class Repository < ActiveRecord::Base return @found_committer_users[committer] if @found_committer_users.has_key?(committer) user = nil - c = changesets.where(:committer => committer).includes(:user).first + c = changesets.where(:committer => committer). + includes(:user).references(:user).first if c && c.user user = c.user elsif committer.strip =~ /^([^<]+)(<(.*)>)?$/ @@ -484,10 +486,10 @@ class Repository < ActiveRecord::Base ci = "#{table_name_prefix}changesets_issues#{table_name_suffix}" cp = "#{table_name_prefix}changeset_parents#{table_name_suffix}" - connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") - connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") + self.class.connection.delete("DELETE FROM #{ch} WHERE #{ch}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") + self.class.connection.delete("DELETE FROM #{ci} WHERE #{ci}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") + self.class.connection.delete("DELETE FROM #{cp} WHERE #{cp}.changeset_id IN (SELECT #{cs}.id FROM #{cs} WHERE #{cs}.repository_id = #{id})") + self.class.connection.delete("DELETE FROM #{cs} WHERE #{cs}.repository_id = #{id}") clear_extra_info_of_changesets end diff --git a/app/models/repository/cvs.rb b/app/models/repository/cvs.rb index 09d79c4fb..abc408304 100644 --- a/app/models/repository/cvs.rb +++ b/app/models/repository/cvs.rb @@ -199,7 +199,7 @@ class Repository::Cvs < Repository # Need to retrieve existing revision numbers to sort them as integers sql = "SELECT revision FROM #{Changeset.table_name} " sql << "WHERE repository_id = #{id} AND revision NOT LIKE 'tmp%'" - @current_revision_number ||= (connection.select_values(sql).collect(&:to_i).max || 0) + @current_revision_number ||= (self.class.connection.select_values(sql).collect(&:to_i).max || 0) @current_revision_number += 1 end end diff --git a/app/models/repository/git.rb b/app/models/repository/git.rb index c51a7e58a..978944efc 100644 --- a/app/models/repository/git.rb +++ b/app/models/repository/git.rb @@ -241,7 +241,7 @@ class Repository::Git < Repository def latest_changesets(path,rev,limit=10) revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false) return [] if revisions.nil? || revisions.empty? - changesets.where(:scmid => revisions.map {|c| c.scmid}).all + changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a end def clear_extra_info_of_changesets diff --git a/app/models/repository/mercurial.rb b/app/models/repository/mercurial.rb index abe1dfb70..81dd24d00 100644 --- a/app/models/repository/mercurial.rb +++ b/app/models/repository/mercurial.rb @@ -20,7 +20,7 @@ require 'redmine/scm/adapters/mercurial_adapter' class Repository::Mercurial < Repository # sort changesets by revision number has_many :changesets, - :order => "#{Changeset.table_name}.id DESC", + lambda {order("#{Changeset.table_name}.id DESC")}, :foreign_key => 'repository_id' attr_protected :root_url @@ -117,9 +117,10 @@ class Repository::Mercurial < Repository changesets. includes(:user). where(latest_changesets_cond(path, rev, limit)). + references(:user). limit(limit). order("#{Changeset.table_name}.id DESC"). - all + to_a end def is_short_id_in_db? diff --git a/app/models/repository/subversion.rb b/app/models/repository/subversion.rb index 532c1f3a2..1659b77f8 100644 --- a/app/models/repository/subversion.rb +++ b/app/models/repository/subversion.rb @@ -42,7 +42,7 @@ class Repository::Subversion < Repository revisions = scm.revisions(path, rev, nil, :limit => limit) if revisions identifiers = revisions.collect(&:identifier).compact - changesets.where(:revision => identifiers).reorder("committed_on DESC").includes(:repository, :user).all + changesets.where(:revision => identifiers).reorder("committed_on DESC").includes(:repository, :user).to_a else [] end diff --git a/app/models/role.rb b/app/models/role.rb index 10977612d..efd9a334d 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -166,7 +166,7 @@ class Role < ActiveRecord::Base # Find all the roles that can be given to a project member def self.find_all_givable - Role.givable.all + Role.givable.to_a end # Return the builtin 'non member' role. If the role doesn't exist, diff --git a/app/models/setting.rb b/app/models/setting.rb index 532e9d44d..3881f90f1 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -86,6 +86,7 @@ class Setting < ActiveRecord::Base validates_numericality_of :value, :only_integer => true, :if => Proc.new { |setting| (s = @@available_settings[setting.name]) && s['format'] == 'int' } + attr_protected :id # Hash used to cache setting values @cached_settings = {} @@ -142,6 +143,7 @@ END_SRC def self.set_from_params(name, params) params = params.dup params.delete_if {|v| v.blank? } if params.is_a?(Array) + params.symbolize_keys! if params.is_a?(Hash) m = "#{name}_from_params" if respond_to? m diff --git a/app/models/time_entry.rb b/app/models/time_entry.rb index 662070840..8738668cc 100644 --- a/app/models/time_entry.rb +++ b/app/models/time_entry.rb @@ -35,7 +35,7 @@ class TimeEntry < ActiveRecord::Base acts_as_activity_provider :timestamp => "#{table_name}.created_on", :author_key => :user_id, - :find_options => {:include => :project} + :scope => preload(:project) validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on validates_numericality_of :hours, :allow_nil => true, :message => :invalid @@ -45,13 +45,19 @@ class TimeEntry < ActiveRecord::Base validate :validate_time_entry scope :visible, lambda {|*args| - includes(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)) + joins(:project). + references(:project). + where(Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)) } scope :on_issue, lambda {|issue| - includes(:issue).where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}") + joins(:issue). + references(:issue). + where("#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}") } scope :on_project, lambda {|project, include_subprojects| - includes(:project).where(project.project_condition(include_subprojects)) + joins(:project). + references(:project). + where(project.project_condition(include_subprojects)) } scope :spent_between, lambda {|from, to| if from && to diff --git a/app/models/time_entry_query.rb b/app/models/time_entry_query.rb index bd2eef398..05c39f55e 100644 --- a/app/models/time_entry_query.rb +++ b/app/models/time_entry_query.rb @@ -42,7 +42,7 @@ class TimeEntryQuery < Query if project principals += project.principals.sort unless project.leaf? - subprojects = project.descendants.visible.all + subprojects = project.descendants.visible.to_a if subprojects.any? add_available_filter "subproject_id", :type => :list_subprojects, @@ -109,7 +109,8 @@ class TimeEntryQuery < Query where(statement). order(order_option). joins(joins_for_order_statement(order_option.join(','))). - includes(:activity) + includes(:activity). + references(:activity) end def sql_for_activity_id_field(field, operator, value) diff --git a/app/models/token.rb b/app/models/token.rb index b1d2d2905..0f3751030 100644 --- a/app/models/token.rb +++ b/app/models/token.rb @@ -18,6 +18,7 @@ class Token < ActiveRecord::Base belongs_to :user validates_uniqueness_of :value + attr_protected :id before_create :delete_previous_tokens, :generate_new_token diff --git a/app/models/tracker.rb b/app/models/tracker.rb index 20ba84527..b4f22c811 100644 --- a/app/models/tracker.rb +++ b/app/models/tracker.rb @@ -63,7 +63,7 @@ class Tracker < ActiveRecord::Base connection.select_rows("SELECT DISTINCT old_status_id, new_status_id FROM #{WorkflowTransition.table_name} WHERE tracker_id = #{id} AND type = 'WorkflowTransition'"). flatten. uniq - @issue_statuses = IssueStatus.where(:id => ids).all.sort + @issue_statuses = IssueStatus.where(:id => ids).to_a.sort end def disabled_core_fields @@ -92,7 +92,7 @@ class Tracker < ActiveRecord::Base # Returns the fields that are disabled for all the given trackers def self.disabled_core_fields(trackers) if trackers.present? - trackers.uniq.map(&:disabled_core_fields).reduce(:&) + trackers.map(&:disabled_core_fields).reduce(:&) else [] end diff --git a/app/models/user.rb b/app/models/user.rb index d8590f47c..d86627e85 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -79,8 +79,8 @@ class User < Principal :after_remove => Proc.new {|user, group| group.user_removed(user)} has_many :changesets, :dependent => :nullify has_one :preference, :dependent => :destroy, :class_name => 'UserPreference' - has_one :rss_token, :class_name => 'Token', :conditions => "action='feeds'" - has_one :api_token, :class_name => 'Token', :conditions => "action='api'" + has_one :rss_token, lambda {where "action='feeds'"}, :class_name => 'Token' + has_one :api_token, lambda {where "action='api'"}, :class_name => 'Token' belongs_to :auth_source scope :logged, lambda { where("#{User.table_name}.status <> #{STATUS_ANONYMOUS}") } @@ -105,9 +105,13 @@ class User < Principal validates_length_of :firstname, :lastname, :maximum => 30 validates_format_of :mail, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :allow_blank => true validates_length_of :mail, :maximum => MAIL_LENGTH_LIMIT, :allow_nil => true - validates_confirmation_of :password, :allow_nil => true validates_inclusion_of :mail_notification, :in => MAIL_NOTIFICATION_OPTIONS.collect(&:first), :allow_blank => true validate :validate_password_length + validate do + if password_confirmation && password != password_confirmation + errors.add(:password, :confirmation) + end + end before_create :set_mail_notification before_save :generate_password_if_needed, :update_hashed_password @@ -151,6 +155,15 @@ class User < Principal write_attribute(:mail, arg.to_s.strip) end + def self.find_or_initialize_by_identity_url(url) + user = where(:identity_url => url).first + unless user + user = User.new + user.identity_url = url + end + user + end + def identity_url=(url) if url.blank? write_attribute(:identity_url, '') @@ -496,10 +509,12 @@ class User < Principal hash = Hash.new([]) - members = Member.joins(:project). + group_class = anonymous? ? GroupAnonymous : GroupNonMember + members = Member.joins(:project, :principal). where("#{Project.table_name}.status <> 9"). - where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Member.table_name}.user_id = ?)", self.id, true, Group.builtin_id(self)). - preload(:project, :roles) + where("#{Member.table_name}.user_id = ? OR (#{Project.table_name}.is_public = ? AND #{Principal.table_name}.type = ?)", self.id, true, group_class.name). + preload(:project, :roles). + to_a members.reject! {|member| member.user_id != id && project_ids.include?(member.project_id)} members.each do |member| @@ -558,6 +573,8 @@ class User < Principal # Authorize if user is authorized on every element of the array context.map {|project| allowed_to?(action, project, options, &block)}.reduce(:&) end + elsif context + raise ArgumentError.new("#allowed_to? context argument must be a Project, an Array of projects or nil") elsif options[:global] # Admin users are always authorized return true if admin? @@ -710,17 +727,17 @@ class User < Principal return if self.id.nil? substitute = User.anonymous - Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id]) + Attachment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id]) Comment.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id]) Issue.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id]) Issue.where(['assigned_to_id = ?', id]).update_all('assigned_to_id = NULL') - Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id]) + Journal.where(['user_id = ?', id]).update_all(['user_id = ?', substitute.id]) JournalDetail. where(["property = 'attr' AND prop_key = 'assigned_to_id' AND old_value = ?", id.to_s]). update_all(['old_value = ?', substitute.id.to_s]) JournalDetail. where(["property = 'attr' AND prop_key = 'assigned_to_id' AND value = ?", id.to_s]). - update_all(['value = ?', substitute.id.to_s]) + update_all(['value = ?', substitute.id.to_s]) Message.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id]) News.where(['author_id = ?', id]).update_all(['author_id = ?', substitute.id]) # Remove private queries and keep public ones diff --git a/app/models/version.rb b/app/models/version.rb index 1d06360ef..45c7c25a6 100644 --- a/app/models/version.rb +++ b/app/models/version.rb @@ -33,11 +33,14 @@ class Version < ActiveRecord::Base validates :effective_date, :date => true validates_inclusion_of :status, :in => VERSION_STATUSES validates_inclusion_of :sharing, :in => VERSION_SHARINGS + attr_protected :id scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)} scope :open, lambda { where(:status => 'open') } scope :visible, lambda {|*args| - includes(:project).where(Project.allowed_to_condition(args.first || User.current, :view_issues)) + joins(:project). + references(:project). + where(Project.allowed_to_condition(args.first || User.current, :view_issues)) } safe_attributes 'name', @@ -230,11 +233,6 @@ class Version < ActiveRecord::Base end end - # Returns true if the version is shared, otherwise false - def shared? - sharing != 'none' - end - private def load_issue_counts diff --git a/app/models/watcher.rb b/app/models/watcher.rb index 1f42de86f..9981bea1c 100644 --- a/app/models/watcher.rb +++ b/app/models/watcher.rb @@ -22,6 +22,7 @@ class Watcher < ActiveRecord::Base validates_presence_of :user validates_uniqueness_of :user_id, :scope => [:watchable_type, :watchable_id] validate :validate_user + attr_protected :id # Returns true if at least one object among objects is watched by user def self.any_watched?(objects, user) diff --git a/app/models/wiki.rb b/app/models/wiki.rb index 45a22fda1..b1ce29743 100644 --- a/app/models/wiki.rb +++ b/app/models/wiki.rb @@ -18,13 +18,14 @@ class Wiki < ActiveRecord::Base include Redmine::SafeAttributes belongs_to :project - has_many :pages, :class_name => 'WikiPage', :dependent => :destroy, :order => 'title' + has_many :pages, lambda {order('title')}, :class_name => 'WikiPage', :dependent => :destroy has_many :redirects, :class_name => 'WikiRedirect', :dependent => :delete_all acts_as_watchable validates_presence_of :start_page validates_format_of :start_page, :with => /\A[^,\.\/\?\;\|\:]*\z/ + attr_protected :id safe_attributes 'start_page' diff --git a/app/models/wiki_content.rb b/app/models/wiki_content.rb index 415ba9aca..3eac92688 100644 --- a/app/models/wiki_content.rb +++ b/app/models/wiki_content.rb @@ -23,6 +23,7 @@ class WikiContent < ActiveRecord::Base belongs_to :author, :class_name => 'User', :foreign_key => 'author_id' validates_presence_of :text validates_length_of :comments, :maximum => 255, :allow_nil => true + attr_protected :id acts_as_versioned @@ -68,13 +69,13 @@ class WikiContent < ActiveRecord::Base :timestamp => "#{WikiContent.versioned_table_name}.updated_on", :author_key => "#{WikiContent.versioned_table_name}.author_id", :permission => :view_wiki_edits, - :find_options => {:select => "#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + - "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + - "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " + - "#{WikiContent.versioned_table_name}.id", - :joins => "LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " + - "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + - "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id"} + :scope => select("#{WikiContent.versioned_table_name}.updated_on, #{WikiContent.versioned_table_name}.comments, " + + "#{WikiContent.versioned_table_name}.#{WikiContent.version_column}, #{WikiPage.table_name}.title, " + + "#{WikiContent.versioned_table_name}.page_id, #{WikiContent.versioned_table_name}.author_id, " + + "#{WikiContent.versioned_table_name}.id"). + joins("LEFT JOIN #{WikiPage.table_name} ON #{WikiPage.table_name}.id = #{WikiContent.versioned_table_name}.page_id " + + "LEFT JOIN #{Wiki.table_name} ON #{Wiki.table_name}.id = #{WikiPage.table_name}.wiki_id " + + "LEFT JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Wiki.table_name}.project_id") after_destroy :page_update_after_destroy @@ -104,7 +105,7 @@ class WikiContent < ActiveRecord::Base # uncompressed data data end - str.force_encoding("UTF-8") if str.respond_to?(:force_encoding) + str.force_encoding("UTF-8") str end end diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index fc56d9d6b..8aed835ad 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -33,7 +33,7 @@ class WikiPage < ActiveRecord::Base :url => Proc.new {|o| {:controller => 'wiki', :action => 'show', :project_id => o.wiki.project, :id => o.title}} acts_as_searchable :columns => ['title', "#{WikiContent.table_name}.text"], - :include => [{:wiki => :project}, :content], + :scope => preload(:wiki => :project).joins(:content, {:wiki => :project}), :permission => :view_wiki_pages, :project_key => "#{Wiki.table_name}.project_id" @@ -43,6 +43,7 @@ class WikiPage < ActiveRecord::Base validates_format_of :title, :with => /\A[^,\.\/\?\;\|\s]*\z/ validates_uniqueness_of :title, :scope => :wiki_id, :case_sensitive => false validates_associated :content + attr_protected :id validate :validate_parent_title before_destroy :remove_redirects @@ -180,12 +181,10 @@ class WikiPage < ActiveRecord::Base def save_with_content(content) ret = nil transaction do - self.content = content - if new_record? - # Rails automatically saves associated content - ret = save - else - ret = save && (content.text_changed? ? content.save : true) + ret = save + if content.text_changed? + self.content = content + ret = ret && content.changed? end raise ActiveRecord::Rollback unless ret end diff --git a/app/models/wiki_redirect.rb b/app/models/wiki_redirect.rb index 403fb5c52..166453138 100644 --- a/app/models/wiki_redirect.rb +++ b/app/models/wiki_redirect.rb @@ -20,4 +20,5 @@ class WikiRedirect < ActiveRecord::Base validates_presence_of :title, :redirects_to validates_length_of :title, :redirects_to, :maximum => 255 + attr_protected :id end diff --git a/app/models/workflow_rule.rb b/app/models/workflow_rule.rb index 829b88a32..3d1b75bd0 100644 --- a/app/models/workflow_rule.rb +++ b/app/models/workflow_rule.rb @@ -24,6 +24,7 @@ class WorkflowRule < ActiveRecord::Base belongs_to :new_status, :class_name => 'IssueStatus', :foreign_key => 'new_status_id' validates_presence_of :role, :tracker, :old_status + attr_protected :id # Copies workflows from source to targets def self.copy(source_tracker, source_role, target_trackers, target_roles) @@ -34,7 +35,7 @@ class WorkflowRule < ActiveRecord::Base target_trackers = [target_trackers].flatten.compact target_roles = [target_roles].flatten.compact - target_trackers = Tracker.sorted.all if target_trackers.empty? + target_trackers = Tracker.sorted.to_a if target_trackers.empty? target_roles = Role.all if target_roles.empty? target_trackers.each do |target_tracker| diff --git a/app/models/workflow_transition.rb b/app/models/workflow_transition.rb index 6b04d5983..e88769aa7 100644 --- a/app/models/workflow_transition.rb +++ b/app/models/workflow_transition.rb @@ -40,7 +40,7 @@ class WorkflowTransition < WorkflowRule roles = Array.wrap roles transaction do - records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).all + records = WorkflowTransition.where(:tracker_id => trackers.map(&:id), :role_id => roles.map(&:id)).to_a transitions.each do |old_status_id, transitions_by_new_status| transitions_by_new_status.each do |new_status_id, transition_by_rule| |