we have models Articles , Photos and Events . And there is a model Comments . And I really want to keep all comments (comments of articles, photos and events) in one table.There are a lot of articles on this problem on the Internet, but there are also cases of “vice versa”. You don’t need to go far, let's try to develop the functionality of Habrahabr posts!
bash-3.2$ script/generate model post title:string published:boolean content_id:integer content_type:string
bash-3.2$ script/generate model topic body:text
bash-3.2$ script/generate model podcast link:string description:text
bash-3.2$ script/generate model link link:string description:text
t.timestamps
After all, the fields created_at and updated_at (which are generated by the helper timestamps) are now one for all - in the posts table.# app/models/post.rb <br/> class Post < ActiveRecord::Base <br/> belongs_to :content, :polymorphic => true , :dependent => :destroy<br/> end <br/> <br/> # app/models/topic.rb <br/> class Topic < ActiveRecord::Base <br/> has_one :post, :as => :content, :dependent => :destroy<br/> end <br/> <br/> # app/models/link.rb <br/> class Link < ActiveRecord::Base <br/> has_one :post, :as => :content, :dependent => :destroy<br/> end <br/> <br/> # app/models/podcast.rb <br/> class Podcast < ActiveRecord::Base <br/> has_one :post, :as => :content, :dependent => :destroy<br/> end <br/>
Instead of writing “has_many: links (: topics,: podcasts)” in the Post model, we say that Post is tightly tied by :has_one :post, :as => content
will become a subsidiary of our Post'a. What we, in fact, have done above.bash-3.2$ script/console
>> t = Topic.new(:body => "Just one more test topic body here")
>> t.save
>> p = Post.new(:title => "Some test title", :published => true, :content => t)
>> p.save
They created a new topic (indicating only the body), saved, created a new post (indicating the title, status and content itself (you could write and: content_id => t.id,: content_type => t.class (as if implying also .to_s)).>> Post.topics.new
=> #<Post id: nil, title: nil, published: nil, content_id: nil, content_type: "Topic", created_at: nil, updated_at: nil>
>> Post.find_all_by_content_type("Topic")
I agree, uncomfortable; let's add some named_scope to the Post model:named_scope :topics, :conditions => { :content_type => "Topic" }<br/>named_scope :links, :conditions => { :content_type => "Link" }<br/>named_scope :podcasts, :conditions => { :content_type => "Podcast" } <br/>
We go again to the console, do reload! and look around:>> Post.topics
>> Post.links
>> Post.podcasts
Now you need to understand how to access all the properties of our posts.>> p.body
NoMethodError: undefined method `body' for #<Post:0x2653e00>
>> t.title
NoMethodError: undefined method `title' for #<Topic id: 8, body: "Just one more test topic body here">
It turns out that not so :) Let's try something like this:>> p.content.body
=> "Just one more test topic body here"
>> t.post.title
=> "Some test title"
Played and that's enough, it's time to make controllers (and there and close to the presentation). We leave from the rail console, we are waiting for the usual :)bash-3.2$ script/generate controller posts index
bash-3.2$ script/generate controller posts/topics index show
bash-3.2$ script/generate controller posts/podcasts index show
bash-3.2$ script/generate controller posts/links index show
bash-3.2$ script/generate controller home index
Next we go to config / routes.rb and bring it to this view:ActionController::Routing ::Routes.draw do |map|<br/> map.root :controller => 'home' <br/> <br/> map.namespace(:posts) do |post|<br/> post.resources :topics, :links, :podcasts<br/> end <br/> map.resources :posts<br/> <br/> map.connect ':controller/:action/:id' <br/> map.connect ':controller/:action/:id.:format' <br/> end <br/>
And now let's start the server and see what we have: bash-3.2$ script/server
And we got this:/posts ( )
/posts/topics ( — –)
/posts/links ( — –)
/posts/podcasts ( , ;)
# app/controllers/posts_controller.rb <br/> class PostsController < ApplicationController<br/> def index <br/> @posts = Post.find(:all)<br/> end <br/> end <br/> <br/> # app/controllers/posts/topics_controller.rb <br/> class Posts ::TopicsController < ApplicationController<br/> def index <br/> @posts = Post.topics.find(:all)<br/> end <br/> <br/> def show <br/> @post = Post.topics.find(params[:id])<br/> end <br/> end <br/> <br/> # app/controllers/posts/links_controller.rb <br/> class Posts ::LinksController < ApplicationController<br/> def index <br/> @posts = Post.links.find(:all)<br/> end <br/> <br/> def show <br/> @post = Post.links.find(params[:id])<br/> end <br/> end <br/> <br/> # app/controllers/posts/podcasts_controller.rb <br/> class Posts ::PodcastsController < ApplicationController<br/> def index <br/> @posts = Post.podcasts.find(:all)<br/> end <br/> <br/> def show <br/> @post = Post.podcasts.find(params[:id])<br/> end <br/> end <br/>
In posts_controller, for the time being, we only fill in the index, show is not needed there. In the rest we fill in both the index (there, as you can see, only the “necessary” posts will be displayed), and show (and the article / link / podcast will be displayed here). I think, here you can do without explanations, we have already written all this code in the console.<!-- app/views/posts/index.html.erb --><br/><% @posts.each do |post| %><br/> <%= link_to post.content. class . to_s .pluralize, "/posts/#{post.content.class.to_s.downcase.pluralize}" %> →<br/> <%= link_to post.title, "/posts/#{post.content.class.to_s.downcase.pluralize}/#{post.id}" %><br/><br/><% end %> <br/>
At first, I wrote this way, because to count down on tons of ifs is even worse (IMHO). Then I felt ashamed that people would see such horror on Habré, and decided to make this horror a little less awful. So, open app / helpers / posts_helper.rb and write something likemodule PostsHelper<br/> def posts_smth_path (post)<br/> case post.content. class . to_s .downcase<br/> when "topic" : posts_topic_path(post)<br/> when "link" : posts_link_path(post)<br/> when "podcast" : posts_podcast_path(post)<br/> end <br/> end <br/> <br/> def posts_smths_path (post)<br/> case post.content. class . to_s .downcase<br/> when "topic" : posts_topics_path<br/> when "link" : posts_links_path<br/> when "podcast" : posts_podcasts_path<br/> end <br/> end <br/> end <br/>
Now we have 2 methods: posts_smth_path and posts_smths_path, which are a special case of posts_topic_path and posts_topics_path (instead of topic / topics, of course, there may also be link / links and podcast / podcasts). Having done the work on the bugs, we look at what we did:<!-- app/views/posts/index.html.erb --><br/><% @posts.each do |post| %><br/> <%= link_to post.content. class . to_s .pluralize, posts_smths_path(post) %> →<br/> <%= link_to post.title, posts_smth_path(post) %><br/><br/><% end %> <br/>
I think that is enough for a draft. Now the rest of the submission:<!-- app/views/posts/topics/index.html.erb --><br/><% @posts.each do |post| %><br/> <%= link_to post.title, posts_topic_path(post) %><br/><br/><% end %><br/>< p ><br/> <%= link_to "Add new Topic" , new_posts_topic_path %><br/></ p > <br/>
This is the index method, and with the exception of the posts_topic_path and new_posts_topic_path methods it is the same everywhere, it makes no sense to build a ton of code here. The other two will be posts_link_path / new_posts_link_path and posts_podcast_path / new_posts_podcast_path respectively.<h1><%= @post.title %></h1><br/><%= @post.content.body %> <br/>
And this is a show, and in this example it is generally the same everywhere :)<%= link_to "Add new Topic" , new_posts_topic_path %>
The link_to helper will generate a link that, when clicked, will go to page / posts / topics / new, so it’s just vital for us to create the file app / views / posts / topics / new.html.erb and write something like this to it:<!-- app/views/posts/topics/ new .html.erb --><br/><% form_for [:posts, @topic] do |form| %><br/> <% form .fields_for @post do | p | %> <br/> < p ><br/> <%= p .label :title %><br/><br/> <%= p .text_field :title %> <br/> </ p ><br/> < p ><br/> <%= p .check_box :published %><br/> <%= p .label :published %><br/> </ p ><br/> <% end %><br/> <br/> < p ><br/> <%= form .label :body %><br/><br/> <%= form .text_area :body %> <br/> </ p ><br/> <br/> < p ><%= form .submit "Create" %></ p ><br/><% end %> <br/>
At once I will make a reservation, so far it will only be about topics, in the rest of the controllers / views there will be a similar code.def new <br/> @topic = Topic. new <br/> @post = Post.topics. new <br/> end <br/>
And, for complete clarity, I will repeat the code that we wrote in the routes.rb file:map.namespace(:posts) do |post|<br/> post.resources :topics, :links, :podcasts<br/> end <br/>
Once upon a time we defined a namespace, and now when creating forms for topics instead of form_for topic do ... we will specify our namespace, that is, write form_for [: posts, topic ] do ... (similarly for links and podcasts).def create <br/> @topic = Topic. new (:body => params[:topic][:body])<br/> if @topic.save<br/> @post = Post. new ({ :content => @topic }.merge params[:topic][:post])<br/> if @post.save<br/> redirect_to root_url<br/> else <br/> render :new<br/> end <br/> else <br/> render :new<br/> end <br/> end <br/>
I sincerely apologize for such an abundance of code in the method, I am sure that this code can (and should!) Be put into the model, but I do not know how. I hope some of the more experienced comrades will correct me.Source: https://habr.com/ru/post/79431/
All Articles