If I have a nested resource like so:
resources :users
resources :posts
end
and a user has_many posts, it is possible to have Rails start numbering based on the parent association in the URL? For example, currently, nesting resources just grabs the ID:
#user.posts.find(params[:id])
This correctly namespaces the posts, only allowing posts from #user... however, is there a way such that the post_id is independent? I.E. I want each user's posts to start at 1, where:
/users/1/posts/1
/users/2/posts/1
Actually refer to two different posts?
It can be quite a bit of work, but basically you can do it with these steps:
Create a migration to add a new attribute to store the specific user-post count. (I used user_post_id)
Override Post's to_param method to use the new value you just created. (It has to be a string.)
to_param is the method that the url and path helpers use.
Create a before_save filter that will actually increment the user_post_id value for each new post.
Change all your controller methods to find on user_post_id
#user = User.find(params[:user_id])
#post = #user.posts.where(:user_post_id => (params[:id])).first
Change all your Views that might not work now
You can see the source here: Custom Nested Resource URL example
Code
migration:
class AddUserPostIdToPosts < ActiveRecord::Migration
def change
add_column :posts, :user_post_id, :integer
end
end
post.rb:
class Post < ActiveRecord::Base
before_save :set_next_user_post_id
belongs_to :user
validates :user_post_id, :uniqueness => {:scope => :user_id}
def to_param
self.user_post_id.to_s
end
private
def set_next_user_post_id
self.user_post_id ||= get_new_user_post_id
end
def get_new_user_post_id
user = self.user
max = user.posts.maximum('user_post_id') || 0
max + 1
end
end
A couple controller methods
posts_controller.rb:
class PostsController < ApplicationController
respond_to :html, :xml
before_filter :find_user
def index
#posts = #user.posts.all
respond_with #posts
end
def show
#post = #user.posts.where(:user_post_id => (params[:id])).first
respond_with [#user, #post]
end
...
end
Related
How to configure the rails controller so I can have a user post a submission in no matter what contest. When they post their user id and the contest id should be automatically appended to the submission.
I know I can do:
User.first.contests.create => let the user create a contest
Contest.first.submissions.create => create a submission in a contest (not linked to a user)
User.first.submissions.create => create a submission linked to a user but not to a contest
I cannot do User.first.Contest.last.submissions.create => I want to link a submission to a contest and to a submission.
Is there an elegant way to fix this?
The submission controller looks like this:
class SubmissionsController < ApplicationController
before_action :set_submission, only: [:show, :edit, :update, :destroy]
# the current user can only edit, update or destroy if the id of the pin matches the id the user is linked with.
before_action :correct_user, only: [:edit, :update, :destroy]
# the user has to authenticate for every action except index and show.
before_action :authenticate_user!, except: [:index, :show]
respond_to :html
def index
#title = t('submissions.index.title')
#submissions = Submission.all
respond_with(#submissions)
end
def show
#title = t('submissions.show.title')
respond_with(#submission)
end
def new
#title = t('submissions.new.title')
#submission = Submission.new
respond_with(#submission)
end
def edit
#title = t('submissions.edit.title')
end
def create
#title = t('submissions.create.title')
#submission = Submission.new(submission_params)
#submission.save
respond_with(#submission)
end
def update
#title = t('submissions.update.title')
#submission.update(submission_params)
respond_with(#submission)
end
def destroy
#title = t('submissions.destroy.title')
#submission.destroy
respond_with(#submission)
end
private
def set_submission
#submission = Submission.find(params[:id])
end
def submission_params
arams.require(:submission).permit(:reps, :weight, :user_id)
end
def correct_user
#submission = current_user.submissions.find_by(id: params[:id])
redirect_to submissions_path, notice: t('submissions.controller.correct_user') if #submission.nil?
end
end
I have following models:
class Contest < ActiveRecord::Base
has_many :submissions
has_many :users, through: :submissions
class Submission < ActiveRecord::Base
belongs_to :user
belongs_to :contest
class User < ActiveRecord::Base
has_many :submissions
has_many :contests, through: :submissions
I think you're making this a bit complicated.
Submission is POSTED within Contest, Submission needs to know the user_id.
<%= simple_form_for :submission, url: contest_submissions_path(contest) do |f| %>
...
<%= f.submit 'Submit', class: "button" %>
<% end %>
And on your submissions CREATE method
class SubmissionsController < ApplicationController
def create
#contest = Contest.find(params[:contest_id])
#submission = #contest.submissions.new(submission_params)
#submissions.user = current_user
.....
end
The magic happens at #submissions.user = current_user If you are using Devise, it is easy to pass in the current_user.id ANYWHERE in the controller, as I just did in the submissions controller.
Are you able to use #submission = current_user.submissions.new(submission_params) and #contest = Contest.find(params[:contest_id]) in your SubmissionsController#create
EDIT: I've added some details on adding a reference to contest_id in the submissions table.
The best way I've found to tie related things together in Rails (and indeed, any relational database) is to add a reference in the child table to the parent's id. You can do this with a migration in Rails.
rails g migration AddContestToSubmission contest:references
And modify the migration file generated in your db/migrate/<datetime>_add_contest_to_submission to look similar to:
class AddContestToSubmission < ActiveRecord::Migration
def change
add_reference :submissions, :contest, index: true
end
end
Then go ahead and look at your submissions table in your schema.rb. You should notice something like t.integer "contest_id" You should probably also add the user_id in your migration is you want a submission to be tied to one user.
I have 1:N relationship between user and post model. I want to access user_id in post model. I tried it by accessing current_user but it's throwing cannot find current_user variable.
My userModel class:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :validatable
has_many :post
validates_format_of :email, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
end
MyPostModel class:
class Post < ActiveRecord::Base
belongs_to :user
before_create :fill_data
validates_presence_of :name, :message => 'Name field cannot be empty..'
def fill_data
self.is_delete = false
self.user_id = current_user # here I am getting the error
end
end
MyPostController class
class PostController < ApplicationController
before_action :authenticate_user!
def index
#post = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to action: 'index'
else
render 'new'
end
end
.....
private
def post_params
params.require(:post).permit(:name,:user_id,:is_delete)
end
end
I can access the before_action :authenticate_user! in Post controller but not current_user in post model or controller. What I am doing wrong here in Post.fill_data. self.user_id?
Rest of the code is working fine and I can see the new entry of :name and :is_delete in sqlite3 database (when I am commenting self.user_id line in Post class).
Edit-1
I already have migration class for post
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :name
t.boolean :is_delete
t.references :user, index: true, foreign_key: true
t.timestamps null: false
end
end
end
In Rails your models should not be aware of the apps current user or any other state. They only need to know about themselves and the objects they are directly related to.
The controller on the other hand is aware of the current user.
So the proper way to do this would be to remove the fill_data callback from Post. And do it in the controller:
class PostController < ApplicationController
before_action :authenticate_user!
def index
#post = Post.all
end
def new
#post = current_user.posts.build
end
def create
#post = current_user.posts.build(post_params)
if #post.save
redirect_to action: 'index'
else
render 'new'
end
end
private
def post_params
params.require(:post).permit(:name,:user_id,:is_delete)
end
end
You should also set the default for your is_delete column in the database instead, but if you want to rock it like a pro use an enum instead.
Create a migration rails g migration AddStateToUsers and fill it with:
class AddStateToUsers < ActiveRecord::Migration
def change
add_column :users, :state, :integer, default: 0
remove_column :users, :is_delete
add_index :users, :state
end
end
We then use the rails enum macro to map state to a list of symbols:
class Post
enum state: [:draft, :published, :trashed]
# ...
end
That lets you do Post.trashed to get all posts in the trash or post.trashed? to check if a specific post is trashed.
notice that I use trashed instead of deleted because ActiveRecord has build in deleted? methods that we don't want to mess with.
You are trying to add current_user.id in post model using before_create call back. but better to do is use this
In posts_controller.rb
def new
#post = current_user.posts.new
end
def create
#post = current_user.posts.create(posts_params)
end
This will create a post for the current user.
Your fill_data method would be
def fill_data
self.is_delete = false
end
I have an application with 3 models user company and post.
I'm having trouble with figuring out the logic
A user can have many companies and a company can have many posts.
here are my models
class user < ActiveRecord::Base
has_many :companies
end
class company < ActiveRecord::Base
belongs_to :user
has_many :posts
end
class post < ActiveRecord::Base
belongs_to :company
end
Note: company has user_id, post has company_id in table
how do I make sure when creating a post the company_id is automatically recorded
extra information: for routes it will be resources :jobs and resources :companies
--update--
post controller
def new
Post.new(post_params)
end
def create
Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
routes
resources :companies
resources :posts
I think I would probably nest posts within companies, so you would have available to you the company_id in the parameters.
Note your path names will change if you do this.
Routes.rb
resources :companies do
resources :posts
end
posts_controller.rb
def create
#company = Company.find(params[:company_id])
#post = #company.posts.build(post_params)
...
end
And since you will want to find the company on each of these actions in the controller, you can refactor it a bit.
class PostsController
before_action :set_company
def index
#post = #company.posts
end
def create
#post = #company.posts.build(post_params)
...
end
...
...
private
def set_company
#company = Company.find(params[:company_id])
end
end
2 options come to mind.
You can create a hidden company_id field in your posts#new view, so it would be sent automatically along with your params and hence be set in the model accordingly (provided that you permit it). But in that case you'll need to populate that field in the controller when creating the #post. Just like this:
#post = Post.new(company_id: some_company_id)
Use nested resources.First of all change your routes to this:
resources :companies do
resoures :posts
end
That way when you look at the rake routes output you'll see the :company_id param being set in the request url. So when you go on creating a post, you can do #post.company_id = params[:company_id] and you're done.
I have two models like that
class Plan < ActiveRecord::Base
belongs_to :profile
And
class Profile < ActiveRecord::Base
has_many :plans
And routes like: (I need to)
resources :profiles do
resources :plans
end
resources :plans
So, following up ruby-on-rails - Problem with Nested Resources, I've made my PLANS index controller like this, to works NESTED and UNESTED at same time (the only way I've found for now):
def index
if params.has_key? :profile_id
#profile = Profile.find(params[:profile_id])
#plans = #profile.plans
else
#plans = Plan.all
end
There is a cleaner approach to this?
I have another models in this situation, and putting all actions, in all controllers to behave like this is cumbersome.
You gave me an idea:
models/user.rb:
class User < ActiveRecord::Base
has_many :posts
attr_accessible :name
end
models/post.rb:
class Post < ActiveRecord::Base
belongs_to :user
attr_accessible :title, :user_id
end
controllers/posts_controller.rb:
class PostsController < ApplicationController
belongs_to :user # creates belongs_to_user filter
# #posts = Post.all # managed by belongs_to_user filter
# GET /posts
# GET /posts.json
def index
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
end
And now the substance:
controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
def self.belongs_to(model)
# Example: model == :user
filter_method_name = :"belongs_to_#{model}_index" # :belongs_to_user_index
foreign_key = "#{model}_id" # 'user_id'
model_class = model.to_s.classify # User
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{filter_method_name} # def belongs_to_user_index
if params.has_key? :'#{foreign_key}' # if params.has_key? :user_id
instance_variable_set :"##{model}", # instance_variable_set :"#user",
#{model_class}.find(params[:'#{foreign_key}']) # User.find(params[:user_id])
instance_variable_set :"#\#{controller_name}", # instance_variable_set :"##{controller_name}",
##{model}.send(controller_name.pluralize) # #user.send(controller_name.pluralize)
else # else
instance_variable_set :"#\#{controller_name}", # instance_variable_set :"##{controller_name}",
controller_name.classify.constantize.all # controller_name.classify.constantize.all
end # end
end # end
EOV
before_filter filter_method_name, only: :index # before_filter :belongs_to_user_index, only: :index
end
end
The code is not complex to understand if you have notions of Ruby metaprogramming: it declares a before_filter which declares the instance variables inferring the names from the controller name and from the association. It is implemented just for the index actions, which is the only action using the plural instance variable version, but it should be easy to write a filter version for the other actions.
I am using Rails Inherited_resource gem in my comments controller, and comments is a nested resource so:
resources :projects do
resources :comments do
end
I also have a belongs_to in the comments controller:
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project", :polymorphic => true
How can I set the comment's user association to the current_user(user_id) when its created? As user_id is not suppose to be massive assigned.
I tried following:
def begin_of_association_chain
current_user
end
This does set the user id correctly, but I cannot get nested resource working for Project with this.
Same question come when destroy a comment, I will need to find the comment through current_user, so how to achieve this?
So do I have to write my own create and destroy actions?
Thanks :)
Have you tried the following inside comments_controller?
class CommentsController < InheritedResources::Base
before_filter :authenticate_user! # Assuming you are using Devise for authentication
respond_to :html, :xml, :json
belongs_to :project, :finder => :find_by_project_uuid!, :class_name => "Thfz::Project"
def create
#comment = build_resource
#comment.author = current_user
create!
end
end