I am still new to rails and I am trying to figure out how to implement a polymorphic association without using a nested route or form. I tried searching but everything seemed to be about nesting forms or adding comments, which is not what I am trying to do.
Here are my models
Article.rb
class Article < ActiveRecord::Base
belongs_to :articable, polymorphic: true
end
Organization.rb
class Organization < ActiveRecord::Base
has_many :articles, as: :articable
end
People.rb
class People < ActiveRecord::Base
has_many :articles, as: :articable
end
I want to implement a 'New Article' link from a Organization or People show page and have the correct article_id and article_type entered. What would the correct syntax be to generate this link?
Thanks!
Routes:
resource :people do
resource :articles
end
resource :organizations do
resource :articles
end
ArticlesController:
def create
article = Article.new(params[:article])
if params[:people_id]
people = People.find(params[:people_id])
people.articles << article
else
organization = Organization.find(params[:organization_id])
organization.articles << article
end
article.save
end
Organizations view:
link_to new_organization_article_path(#organization)...
Related
I have an article with a many relationship to users and vice versa. When I create an article signed in as a user, the article is created with the relationship to user. So the relationship is working. I want other users to be able to join this article, so essentially, I would like a button to push the current_user to the array/list of many users.
I am at a complete loss at how to go about this process... Any help is appreciated
So users can have many articles and each article can belong to several users? Sounds like a has_and_belongs_to_many relationship. Have a look at the relevant Rails documentation:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
In short you'd have a articles_users table, where each row consists of article_id and user_id. When adding a new users to an article, you just create another record in that table.
Alternatively you could look at has_many :through if you believe you'll work with that relationship as a separate entity. I.e. article has_many :users, through: authors.
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
To help you decide, the guide offers some advice:
http://guides.rubyonrails.org/association_basics.html#choosing-between-has-many-through-and-has-and-belongs-to-many
#app/models/user.rb
class User < ActiveRecord::Base
has_many :written_articles, class_name: "Article", foreign_key: :user_id
has_and_belongs_to_many :articles
end
#app/models/article.rb
class Article < ActiveRecord::Base
belongs_to :user #-> for the original owner
has_and_belongs_to_many :users
end
The above is a has_and_belongs_to_many association, which gives you the ability to add users to the article:
#config/routes.rb
resources :articles do
match "users/:id", to: :users, via: [:post, :delete] #-> url.com/articles/:article_id/users/:id
end
#app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def users
#article = Article.find params[:article_id]
#user = User.find params[:id]
if request.post?
#article.users << #user
elsif request.delete?
#article.users.delete #user
end
#redirect somewhere
end
end
This will allow you to use:
<%= link_to "Add User", article_user_path(#article, #user), method: :post %>
<%= link_to "remove User", article_user_path(#article, #user), method: :delete %>
I created one has_many through relationship in rails api. I also used nested routes.
My models like below;
class Author < ActiveRecord::Base
has_many :comments
has_many :posts, :through => :comments
end
class Post < ActiveRecord::Base
has_many :comments
has_many :authors, :through => :comments
end
class Comment < ActiveRecord::Base
belongs_to :author
belongs_to :post
end
my routes like below;
Rails.application.routes.draw do
namespace :api, defaults: { :format => 'json' } do
resources :posts do
resources :comments
end
resources :authors
end
end
So here my aims are
Comments are nested route so that i can create and display comments from post
Here not any post author. The author is meant for comment owner
I implemented the concepts and working almost all. But i am facing the following 2 problems with associations
How to add additional fields for associated table when parent create. Here my requirement is when a post is created, i need to insert one default entry for comment. My post controller implementation for create is like below
def create
params = create_params.merge(:record_status => 1)
#post = Post.new(params)
#post.authors << Author.find(1)
if #post.save
render :show, status: :created
end
end
def show
#post = Post.find(params[:id])
end
private
def create_params
params.permit(:name, :description, :author_id )
end
Here i am passing author_id in the request json. This needs to be added as author id in the comments table. Now i just hard coded as 1. I used '<<' for association entry. This is working but i also need to include two more fields which are :comments and :record_status. Again :comments is from the request itself.
Note: This is not rails mvc application. This is rails api and input as json.
When i display comments using nested routes i need to show author and also comments from comments table. My comments controller method is ;
class Api::CommentsController < ApplicationController
before_filter :fetch_post
def index
#authors = #post.authors.where(:record_status => 1, comments: { record_status: 1 })
end
private
def fetch_post
#post = Post.find(params[:post_id])
end
end
Here i got authors but not correct comments in the join table 'comments'
Please help me to solve these issues
For the first problem, you want to configure the posts_controller to accept the nested attributes for comments. Add this line at the beginning of your controller:
accepts_nested_attributes_for :comments
Check the documentation for further details on this method.
Then, you need to modify the parameters that the controller will allow:
def create_params
params.permit(:name, :description, :author_id, comments_attributes: [ :author_id, :comments, :record_status ] )
end
Modify the attributes listed in the comments_attributes array to match those of your Comment model.
I'm not sure what you're trying to get in the second problem, but maybe you just need to query a little differently:
#comments = Comment.where(post_id: #post.id).includes(:author)
The above would return a list of comments including the comment author.
I have a controller for "Productions" and if you go to localhost/productions you see an index page, you can click show and view the show page for that particular productions.
Each production has a unique ID like 036ea872f9011a7c, I want my users to be able to add items to a production like follows:
localhost/productions/036ea872f9011a7c/fixtures/add
localhost/productions/036ea872f9011a7c/positions/add
localhost/productions/036ea872f9011a7c/dimmers/add
localhost/productions/036ea872f9011a7c/channels/add
localhost/productions/036ea872f9011a7c/etc/add
You should build a route with the necessary parameters like this:
Suppose we have tasks that we assign to a project
model project.rb:
class Project < ActiveRecord::Base
has_many :tasks, through: :project_task
end
model task.rb
class Task < ActiveRecord::Base
has_many :projects, through: :project_task
end
routes.rb
...
resources :projects do
member do
get 'affect_task/:task_id', action: 'affect_task', as: :affect_task
end
end
projects/show.haml
= link_to "task_name", affect_task_project_path(task_id: #task_id, project_id: #project_id)
controller.rb
...
def affect_task
...
CollaboratorTask.create(task_id: params[:task_id], project_id: params[:project_id])
...
end
...
Of course this is an example so you understand..
I'm currently cloning a single-level association like this:
class Survey < ActiveRecord::Base
def duplicate
new_template = self.clone
new_template.questions << self.questions.collect { |question| question.clone }
new_template.save
end
end
So that clones the Survey then clones the Questions associated with that survey. Fine. That works quite well.
But what I'm having trouble with is that each question has_many Answers. So Survey has_many Questions which has_many Answers.
I can't figure out how to clone the answers properly. I've tried this:
def duplicate
new_template = self.clone
self.questions.each do |question|
new_question = question.clone
new_question.save
question.answers.each do |answer|
new_answer = answer.clone
new_answer.save
new_question.answers << answer
end
new_template.questions << question
end
new_template.save
end
But that does some weird stuff with actually replacing the original answers then creating new ones, so ID's stop matching correctly.
Use deep_clonable gem
new_survey = original_survey.clone :include => [:questions => :answers]
You may also like the Amoeba gem for ActiveRecord 3.2.
In your case, you probably want to make use of the nullify, regex or prefix options available in the configuration DSL.
It supports easy and automatic recursive duplication of has_one, has_many and has_and_belongs_to_many associations, field preprocessing and a highly flexible and powerful configuration DSL that can be applied both to the model and on the fly.
be sure to check out the Amoeba Documentation but usage is pretty easy...
just
gem install amoeba
or add
gem 'amoeba'
to your Gemfile
then add the amoeba block to your model and run the dup method as usual
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :posts
end
class PostsController < ActionController
def some_method
my_post = Post.find(params[:id])
new_post = my_post.dup
new_post.save
end
end
You can also control which fields get copied in numerous ways, but for example, if you wanted to prevent comments from being duplicated but you wanted to maintain the same tags, you could do something like this:
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
exclude_field :comments
end
end
You can also preprocess fields to help indicate uniqueness with both prefixes and suffixes as well as regexes. In addition, there are also numerous options so you can write in the most readable style for your purpose:
class Post < ActiveRecord::Base
has_many :comments
has_and_belongs_to_many :tags
amoeba do
include_field :tags
prepend :title => "Copy of "
append :contents => " (copied version)"
regex :contents => {:replace => /dog/, :with => "cat"}
end
end
Recursive copying of associations is easy, just enable amoeba on child models as well
class Post < ActiveRecord::Base
has_many :comments
amoeba do
enable
end
end
class Comment < ActiveRecord::Base
belongs_to :post
has_many :ratings
amoeba do
enable
end
end
class Rating < ActiveRecord::Base
belongs_to :comment
end
The configuration DSL has yet more options, so be sure to check out the documentation.
Enjoy! :)
Without using gems, you can do the following:
class Survey < ApplicationRecord
has_and_belongs_to_many :questions
def copy_from(last_survey)
last_survery.questions.each do |question|
new_question = question.dup
new_question.save
questions << new_question
end
save
end
…
end
Then you can call:
new_survey = Survey.create
new_survey.copy_from(past_survey)
That will duplicate all questions from last Survey to new Survey and tie them.
Shouldn't it be..
new_question.answers << new_answer
end
new_template.questions << new_question
You can also alias the rails dup method, as follows:
class Survey
has_many :questions, :inverse_of=>:survey, :autosave=>true
alias orig_dup dup
def dup
copy=orig_dup
copy.questions=questions
copy
end
end
class Questions
belongs_to :survey, :inverse_of=>:questions
has_many :answers, :inverse_of=>:question, :autosave=>true
alias orig_dup dup
def dup
copy=orig_dup
copy.answers=answers
copy
end
end
class Answer
belongs_to :question
end
and then you can do this
aaa = Survey.find(123).dup
aaa.save
I have users, posts and comments. User can post only one comment to each post.
class User < ActiveRecord::Base
has_many :posts
has_many :comments
end
class Post < ActiveRecord::Base
has_many :comments
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
On userpage (http://host/users/1 for example) I want to show all posts where the given user has commented. Each post then will have all other comments.
I can do something like this in my User controller:
def show
#user = User.find(params[:user_id])
#posts = []
user.comments.each {|comment| #posts << comment.post}
end
This way I will find User, then all his comments, then corresponding post to each comment, and then (in my view) for each post I will render post.comments. I'm totally new in Rails, so I can do this =) But I think it's somehow bad and there is a better way to do this, maybe I should use scopes or named_scopes (don't know yet what this is, but looks scary).
So can you point me out to the right direction here?
You could define an association which retrieves all the posts with comments in a single query. Keeping it in the model reduces the complexity of your controllers, enables you to reuse the association and makes it easier to unit test.
class User < ActiveRecord::Base
has_many :posts_with_comments, :through => :comments, :source => :post
# ...
end
:through is an option for has_many to specify a join table through which to perform the query. We need to specify the :source as Rails wouldn't be able to infer the source from :post_with_comments.
Lastly, update your controller to use the association.
def show
#user = User.find(params[:user_id])
#posts = #user.posts_with_comments
end
To understand more about :through and :source take a look at the documentation.
When you got the user, you have the associations to his posts and each post has his comments.
You could write:
(I don't know the names of your table fields, so i named the text text)
# In Controller
#user = User.find(params[:user_id]).include([:posts, :comments])
# In View
#user.posts.each do |post|
post.text
# Comments to the Post
post.comments.each do |comment|
comment.text
end
end
I haven't tested the code, so there could be some errors.