我有一个名为Category的模型和另一个名为Articles的模型。类别是"部分"有很多文章,例如新闻和活动。两个类别都使用这些文章,除非它们在我网站的不同部分显示。
现在我正在创建新闻控制器(NewsController),我想访问/news/new
添加新闻。同样,同样适用于EventsController和/events/new
。
我必须在我的路线上使用什么来做这件事?
我的第一次尝试是使用:
resources :categories do
resources :articles, path: '/news'
end
但这迫使我使用/categories/1/news/new
,这有点难看。
如果新闻总是category_id
1而且活动总是2,那么我如何在我的路线上指定这个,所以我可以使用我提到的网址轻松访问它们?
我有一个文章模型。我想要一个叫做NewsController的控制器来处理文章,这样/news/new
(以及其他路径)就可以使用Article。我还希望有一个名为EventsController的控制器,它也可以处理文章,因此/events
也适用于文章。它们之间的区别在于它们具有不同的category_id
。
这可以通过路线进行吗?
取得了一些进展。
resources :categories do
resources :articles
end
get 'news/new' => 'articles#new', defaults: {category_id: 1}
get 'events/new' => 'articles#new', defaults: {category_id: 2}
这解决了我想要对/news/new
和/events/new
做些什么,但我错过了其余的路线(编辑,显示,更新等)。此外,这使我使用目前不存在的文章控制器,也会使新闻控制器过时/无用。
我的逻辑可能是错的,它与我刚刚制作的内容有点明显,但也许有了这个更新,我可以更好地说明我想要做的事情。
我目前正在测试以下内容:
resources :articles, path: '/news', controller: 'news'
resources :articles, path: '/events', controller: 'events'
到目前为止它是有意义的,它是我想要的路线,它使用两个控制器和他们自己的配置,当我访问/news
和/events
时它没有任何错误(尚)。
也可以这样做:
resources :articles, path: '/news', defaults: {category_id: 1}
resources :articles, path: '/events', defaults: {category_id: 2}
但这取决于文章控制器,它可以处理两种类型的类别。这两种解决方案都可以工作(理论上),但是由于各个控制器允许对这两种情况进行更具体的配置,因此我会在第一个解决方案上更多地倾斜。但是,当创建的文章之间没有那么多差异时,第二个更为充分。 defaults
属性也没有明确的必要,我只是为了方便而把它放在那里。
答案 0 :(得分:1)
你的问题是问我认为没有意义的事情,也许你的设计存在缺陷。
如果与类别资源无关,为什么会有新闻资源呢?
类别只是名称空间吗?
如果新闻记录确实总是与您的问题所暗示的第一类相关,则您不能使用ID,因为您无法控制第一类和第一类的ID类别可以有任何ID,在这种情况下,您可以使用顶级新闻资源,并在创建之前在模型中查找第一个类别然后您不必担心丑陋的网址。
如果新闻记录确实与类别相关,那么您必须提供相关的类别ID并嵌套您的路线,但您可以使用以下内容从
中提取相应的网址。https://gist.github.com/jcasimir/1209730
以下是
友情网址
默认情况下,Rails应用程序根据主键构建URL - 数据库中的id列。想象一下,我们有一个Person模型和 相关控制器。我们有Bob Martin的人事记录 身份证号码6.他的节目页面的网址为:
/人/ 6
但是,出于审美或搜索引擎优化的目的,我们希望在网址中使用Bob的名字。该 最后一段,这里的6,被称为" slug"。我们来看几个 如何实施更好的slu ..简单方法
最简单的方法是覆盖Person中的to_param方法 模型。每当我们调用这样的路线助手时:
person_path(@person)
Rails会调用to_param将对象转换为URL的slug。 如果您的模型没有定义此方法,那么它将使用 ActiveRecord :: Base中的实现只返回id。
要使此方法成功,所有链接都使用该方法至关重要 ActiveRecord对象而不是调用id。不要这样做:
person_path(@ person.id)#Bad!
相反,始终传递对象:
person_path(@person)
Slug Generation
相反,在模型中,我们可以覆盖to_param以包含a 该人名的参数化版本:
class Person< ActiveRecord :: Base def to_param [id,name.parameterize] .join(" - ")end end
对于ID为6的用户Bob Martin,这将生成一个slug 6- bob_martin。完整的URL将是:
/人/ 6,鲍勃 - 马丁
ActiveSupport的参数化方法将处理转换 任何对URL无效的字符。对象查找
我们需要改变哪些发现者?没有!当我们打电话 Person.find(x),参数x被转换为要执行的整数 SQL查找。看看to_i如何处理混合的字符串 字母和数字:
" 1" .to_i
=> 1
" 1与 - 词语" .to_i
=> 1
" 1-2345" .to_i
=> 1
" 6,鲍勃 - 马丁" .to_i
=> 6
to_i方法一旦命中就会停止解释字符串 非数字。由于我们的to_param实现始终具有id 前面跟一个连字符,它总是会根据查找 只是id并丢弃其余的slu ..好处/限制
我们已经向slug添加了内容,这将改善SEO并使我们的 网址更具可读性。
一个限制是用户无法操纵任何URL 有意义的方式知道网址6-bob-martin不允许你这样做 猜猜网址是7-russ-olsen,你还需要知道ID。
数字ID仍在URL中。如果这是你想要的东西 为了混淆,那么简单的方案并没有帮助。使用非ID 字段
有时你想要一起离开身份证并使用 数据库中用于查找的另一个属性。想象一下,我们有一个标签 具有名称列的对象。这个名字就像红宝石一样 或者铁轨。链接生成
创建链接可以再次覆盖to_param:
class Tag< ActiveRecord :: Base validates_uniqueness_of:name def to_param 名称末尾
现在,当我们调用tag_path(@tag)时,我们会得到一个像/ tags / ruby这样的网址。宾语 查找
但查找更难。当请求进入/ tags / ruby时 ruby将存储在params [:id]中。典型的控制器会调用 Tag.find(params [:id]),基本上是Tag.find(" ruby"),它将失败。 选项1:从控制器查询名称
相反,我们可以修改控制器 Tag.find_by_name(PARAMS [:ID])。它会起作用,但它很糟糕 面向对象的设计。我们打破了Tag的封装 类。
DRY原则说知识应该有一个单一的知识 系统中的表示。在这个标签的实现中,这个想法 "标签可以通过其名称找到#34;现在已经代表了 模型的to_param和控制器查找。这是一项维护工作 头痛。选项2:自定义查找器
在我们的模型中,我们可以定义一个自定义查找器:
class Tag< ActiveRecord :: Base validates_uniqueness_of:name def to_param 名字结束 def self.find_by_param(输入) find_by_name(输入)end end
然后在控制器中调用Tag.find_by_param(params [:id])。这一层 抽象意味着只有模型确切地知道标签是如何的 转换为参数和从参数转换。封装已恢复。
但我们必须记住使用Tag.find_by_param而不是Tag.find 到处。特别是如果您将友好ID改装到 现有系统,这可能是一项重大的努力。选项3: 覆盖查找
我们可以覆盖查找,而不是实现自定义查找程序 方法:
class Tag< ActiveRecord :: Base#... def self.find(输入) find_by_name(输入)end end
当你传入一个名字slug时它会起作用,但是当你传入一个名字slug时会有效 传入数字ID。我们怎么能处理这两个?
第一个诱惑是做一些类型转换:
class Tag< ActiveRecord :: Base#... def self.find(输入) if input.is_a?(整数) 超 其他 find_by_name(输入) 结束端
那可以工作,但是检查类型是违背Ruby的原则。 写is_a?应该总是让你问'#34;有更好的方法吗?"
是的,基于这些事实:
Databases give the id of 1 to the first record Ruby converts strings starting with a letter to 0
class Tag< ActiveRecord :: Base#... def self.find(输入) 如果input.to_i!= 0 超 其他 find_by_name(输入) 结束端
或者,用三元缩写:
class Tag< ActiveRecord :: Base#... def self.find(输入) input.to_i == 0? find_by_name(输入):超级结束
我们的目标已实现,但我们已经引入了一个可能的错误:如果一个名字 以数字开头,它看起来像一个ID。如果它是我们可以接受的 在业务领域,我们可以添加名称无法启动的验证 一个数字:
class Tag< ActiveRecord :: Base#... validates_format_of:name, :没有=> / ^ \ d / def self.find(输入) input.to_i == 0? find_by_name(输入):超级结束
现在一切都应该很棒!使用FriendlyID Gem
实施另外两种方法是否真的很痛苦?或者更多 说真的,你打算实现这种功能吗? 您的应用程序的多个模型?那么它可能值得检查 out FriendlyID gem:https://github.com/norman/friendly_id设置
宝石即将达到4.0版本。在撰写本文时,你 想要使用测试版。在您的Gemfile中:
gem" friendly_id","〜> 4.0.0.beta8"
然后从命令行运行bundle。简单用法
模型中的最低配置是:
class Tag< ActiveRecord :: Base扩展FriendlyId friendly_id:name 端
这将允许您使用名称列或id进行查找 找到,就像我们之前做的那样。 Dedicated Slug
但是图书馆在维护专用slu g方面做得很好 专栏给你。例如,如果我们处理文章,我们就是 不想一遍又一遍地生成slu ..更重要的是,我们会 想要将slug存储在数据库中直接查询。
库默认为名为slug的String列。如果你有 在列中,您可以使用:slugged选项自动生成和 slu the:
class Tag< ActiveRecord :: Base扩展FriendlyId friendly_id :name,:use => :slu end end
用法
你可以在这里看到它:
t = Tag.create(:name =>" Ruby on Rails")
=> #
Tag.find 16
=> #
Tag.find" ruby-on-rails"
=> #
t.to_param
=> "红宝石上导轨"
我们可以透明地使用带有ID或slug的.find。当对象 转换为链接的参数,我们将得到没有ID的slug 数。我们获得良好的封装,易于使用,改进的SEO和简单 阅读网址。
答案 1 :(得分:0)
如果您确定只有两个类别,为什么不简单地在文章中添加一个布尔值?
赞:article.event
=如果是事件类别则为true,如果是新闻则为false
然后,您可以为两个类别的文章类添加范围
class Article
scope :events, -> { where(event: true) }
scope :news, -> { where(event: false) }
end
创建控制器,例如:
class EventsController < ApplicationController
def index
@articles = Article.events
end
def create
@article.new(params)
@article.event = true
@article.save
end
...
end
和路线:resources :events
答案 2 :(得分:0)
您应该尝试使用动态细分:http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments
将一些slug属性添加到Category,它应该是唯一的并为其添加索引。
# routes
resources :articles, except: [:index, :new]
get '*category_slug/new', to: 'articles#new'
get '*category_slug', to: 'articles#index'
# controller
class ArticlesController < ApplicationController
def index
@category = Category.find_by slug: params[:category_slug]
@articles = @category.articles
end
def new
@category = Category.find_by slug: params[:category_slug]
@article = @category.articles.build
end
...
end
请务必在form_for @article