按编号排名卡和调整排名的功能

时间:2015-11-21 01:35:18

标签: ruby-on-rails ruby sorting logic

这是一个开放式的问题,并不是特定于语言的,尽管我最终写的代码将是Ruby / Rails。

我的应用中有一个页面,其中包含一组卡片,每张卡片都有一些内容。每张卡都有一个排名属性,它是一个正整数。我正在通过单击卡上的上/下按钮让用户重新排序卡片,这需要调整等级。例如,如果我想要移动#2,我需要用#1交换位置(如果有#1)。如果我想向下移动#1,我需要将地点与集合中的最后一张卡交换。

我遇到的问题是我有很多不良数据需要解决。我的大部分卡片都没有排名(rank = nil)。此外,个人资料中的某些卡片不会按顺序排列。排名可能是[1,3,7,99,nil]而不是[1,2,3,4,5]。列表末尾会显示nil等级的卡片。

如果数据是完美的,编写一个调整卡片等级的功能将非常容易,但事实并非如此。我想知道的是,如果有任何好的资源可以解决这个问题。我想确保我考虑所有可能的情况,例如当我将rank = 4向上移动一张卡时该怎么办,但上一个插槽中的卡也有rank = 4

作为一项额外的挑战,此功能将在控制器中进行,因此我正在寻找一种降低复杂性的方法,以便在控制器方法中不会有100多行代码。

回答工作代码

如果它对其他人有帮助,这里是我提出的用于对不同类别的卡进行排名的工作代码:

def adjust_rank
    company = current_user.company

    # Build array of cards
    cards = []
    cards << company.office_card if company.office_card.present?
    cards << company.twitter_card if company.twitter_card.present?
    company.gallery_cards.each { |gc| cards << gc }
    company.quote_cards.each { |qc| cards << qc }
    company.wild_cards.each { |wc| cards << wc }

    # Ensure no cards have nil for rank before sorting
    cards.each_with_index { |c,i| c.rank = 100+i if c.rank.nil? }
    cards.sort_by! { |x| x.rank }

    # Ensure cards are ranked sequentially
    cards.each_with_index { |c, i| c.rank = i }

    # Save all cards in a single transaction. This works even though not all the cards belong to the same class.
    cards[0].class.transaction do
        cards.each { |c| c.save }
    end

    # This is the card whose rank the user is adjusting
    selected = Object.const_get(rank_params[:type]).find(rank_params[:id])

    # Get the adjacent card from the cards array
    if rank_params[:direction] == 'up'
        swap = cards.select { |c| c.rank == selected.rank + 1 }[0]
        # If there is no card after selected, get first card in array
        swap = cards[0] if swap.nil?
    elsif rank_params[:direction] == 'down'
        swap = cards.select { |c| c.rank == selected.rank - 1 }[0]
        # If there is no card before selected, get last card in array
        swap = cards[-1] if swap.nil?
    end

    # Swap ranks
    swap_new_rank = selected.rank
    selected_new_rank = swap.rank

    swap.rank = swap_new_rank
    selected.rank = selected_new_rank

    respond_to do |format|
        if swap.save && selected.save
            format.json { render json: {}, status: :ok }
        else
            format.json { render json: {}, status: :bad_request }
        end
    end
end

1 个答案:

答案 0 :(得分:1)

我做了类似排名内容的事情。我在Rails中构建了一个CMS,让用户可以订购显示内容的位置。

为了提供一个更加平台无关的例子,我将使用一些伪代码。我还假设您将使用某种类似ORM的ActiveMcord。

Rails方面的事情

无论如何,我的解决方案是采用混合Javascript / Backend方法。我首先创建了我的Card模型。迁移可能看起来像这样 -

create_table :cards do |t|
    t.string :name
    t.integer :position, :default => 0
end

请注意我有一个职位。对于id来说,这似乎是多余的,但我将使用它来存储对象的可视位置。另请注意,我已经为position提供了默认值,因此我们永远不会有如上所述的错误数据。

所以,让我们假设我们有以下种子数据 -

Card.create(name: 'card1', position: 1) 
Card.create(name: 'card2', position: nil) 
Card.create(name: 'card3', position: 6) 
Card.create(name: 'card4', position: 5) 

请注意,即使我们的卡片按顺序标记,它们的位置也可以自由改变。如果我们要Card.all并根据position对其进行排序,我们会得到一个如下所示的列表,假设我们输出了他们的名字。

card2 # position 0 since our default is 0
card1 # position 1
card4 # position 5
card3 # position 6

Javascript方面的事情

现在,我们希望能够重新排序我们的卡片。由于创建前端有很多解决方案,我将更抽象地解释这一部分。

在这个例子中,我假设每张卡都在它自己的div元素内,并且在div元素内是一个确定position的隐藏形式。

基本上,当您单击箭头以在卡片组中向上或向下移动项目时,您需要重新评估其位置。代码可能如下所示:

function whenArrowClicked(){
    for every card div{
        this.form.val(i)
    }
}

所以,我们的初步定位是

card2 # position 0
card1 # position 1
card4 # position 5
card3 # position 6

看起来更像

card2 # position 1
card1 # position 2
card4 # position 3
card3 # position 4

这更有意义!

此外,如果我们要执行类似移动card1某个位置的操作,我们的javascript会修复我们的定位,因此position值会反映视觉值。

+ card1 # position 1 - used to be 2
- card2 # position 2 - used to be 1
  card4 # position 3
  card3 # position 4

保存此表单将更新我们的对象并修复任何具有nil位置的对象。

请记住,这是一个非常简短的解决方案,实际实施需要考虑更多细节 - 例如,如果您计划拥有大量的卡片,Card.all将不是一种有效的方式抓住内容。此外,您可能会遇到尝试一次保存多个模型的问题。 This might help(见评论)。但是,希望这可以帮助您了解如何构建应用程序。