I have a model of payments and visits. I have associated them in the model shown below. A visit can only have one payment.
I have it set up that a user fills out a visit form and then once completed is redirected to the payment form. What I would like to happen, is the for the visit id to be automatically passed into a hidden_field in the visit_id form on the next page.
class Visit < ActiveRecord::Base
has_one :payment
end
class Payment < ActiveRecord::Base
belongs_to :visit
end
It seems fairly basic, I just can't seem to wrap my mind around the associations correctly. I've searched around and seen a few people trying to explain it but whatever I try is not working correctly. Thanks in advance!
One way to do it would be, in your VisitsController
def create
#visit = Visit.create(visits_params)
if !#visit.save
render :new, error: "Something went wrong"
else
#payment = #visit.build_payment
end
end
Then in your visits/create.html.erb, simply put
<%= form_for #payment do |f| %>
<%= f.hidden_field :visit_id %>
<% end %>
It should work but it doesn't feel right from a RESTFUL perspective. A better way to do it would be to have, in your config/routes.rb
resources :visits do
resources :payments
end
That will generate the following route:
GET /visits/:visit_id/payments/new
Then in the VisitsController
def create
#visit = Visit.create(visit_params)
if !#visit.save
render :new, error: "Something went wrong"
else
redirect_to new_payment_path(visit_id: #visit.id)
end
end
And in your PaymentsController
def new
#visit = Visit.find(params[:visit_id])
#payment = #visit.build_payment
end
In your payments/new.html.erb don't forget to put
<%= form_for #payment do |f| %>
<%= f.hidden_field :visit_id %>
<% end %>
And there you have it... Let me know if that doesn't make any sense.
Related
I have an app where users can ask questions and bookmark certain questions. I'm done with the users, questions, and answers, so I've added a BookmarkController & Bookmarks model. At first, I considered using associations, but my app has a few associations already so I'm (or I've attempted at) using query parameters such as user_id and question_id to fetch bookmarks.
The structure is a bit like StackOverflow. A user navigates to a single question view and bookmarks it on that page. This creates a new bookmark model containing the user_id of current_user and the question_id. The user can go to his profile to view all the questions he bookmarked, fetched using his user_id. (Answers cannot be bookmarked. Only questions.)
I've been getting a 'param is missing or the value is empty: bookmark' error, although I have followed similar steps I did for my QuestionsController. It would be great if someone could help me out in identifying what's wrong/bad about my code!
rake routes (first part omitted)
bookmark_question PUT /questions/:id/bookmark(.:format) questions#bookmark
questions GET /questions(.:format) questions#index
POST /questions(.:format) questions#create
new_question GET /questions/new(.:format) questions#new
edit_question GET /questions/:id/edit(.:format) questions#edit
question GET /questions/:id(.:format) questions#show
PATCH /questions/:id(.:format) questions#update
PUT /questions/:id(.:format) questions#update
DELETE /questions/:id(.:format) questions#destroy
route.rb (excerpt)
# Questions
get '/questions/:id' => 'bookmarks#create'
show.html.erb (questions#show)
<% if current_user %>
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create' %>
<% end %>
BookmarksController
class BookmarksController < ApplicationController
def new
#bookmark = Bookmark.new
end
def create
#question = Question.find(params[:id]) # when I delete this line, I get a new error - "undefined local variable 'params'"
#bookmark = Bookmark.new(bookmark_params)
#bookmark.user_id = current_user.id
#bookmark.question_id = #question.id
#bookmark.save
redirect_to #question
end
def destroy
end
private
def bookmark_params
params.require(:bookmark).permit(:user_id, :question_id)
end
end
Bookmark model
class Bookmark < ApplicationRecord
validates :user_id, presence: true
validates :question_id, presence: true
end
QuestionsController
(at the moment, contains no reference to Bookmarks. I thought so because I did the routing, but this might be where I'm going wrong)
class QuestionsController < ApplicationController
def index
#questions = Question.all
end
def show
#question = Question.find(params[:id])
#answers = Answer.all
# Delete only appears when no answers
#deletable = (current_user== User.find(#question.user_id)) && (#question.answers.all.size==0)
end
def new
#question = Question.new
end
def create
if logged_in?
#question = Question.new(question_params)
#question.user_id = current_user.id
#question.save
redirect_to #question
else
redirect_to login_path
end
end
def destroy
#question = Question.find(params[:id])
#question.destroy
redirect_to root_path
end
private
def question_params
params.require(:question).permit(:picture_url, :country, :educational_level, :topic)
end
end
profile index.html.erb (just for ref)
<% if (#bookmarks.count == 0) %>
///
<% else %>
<%= #bookmarks.each do |bookmark| %>
<!-- Show bookmark content here like Question.find(bookmark.question_id) etc -->
<% end %>
<% end %>
I have looked a the previous qns that have the same error as me. But they were all using associations. I hope to not use associations as the bookmark model only needs to keep a record of the user id and qn id.
UPDATE
So, referring to the answers given, I updated my erb to:
<% if logged_in? %>
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create', bookmark: {user_id: current_user.id, question_id: #question.id} %>
<% end %>
hence specifying the controller and action (and the params) that need to be directed. But rails sends an error:
No route matches {:action=>"create", :bookmark=>{:user_id=>2, :question_id=>4}, :controller=>"bookmarks", :id=>"4"}
So I assume it was a routing problem. As Pavan suggested, I did consider nesting my resources, but the nesting is already one level deep, as such:
resources :questions do
resources :answers
end
And I reckon doing something like:
resources :questions do
resources :bookmarks # or resources :bookmarks, only: create
resources :answers
end
won't work. (And it didn't :( )
I'm not so sure how to get this routing problem fixed (tried Googling). Thanks.
param is missing or the value is empty: bookmark
The reason for the error is bookmark_params expects a :bookmark key to be present in the params hash, which in your case is missing since you are not passing any.
Change link_to like below:
<% if current_user %>
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create', bookmark: {user_id: current_user.id, question_id: #question.id} %>
<% end %>
Also, the route get '/questions/:id' => 'bookmarks#create' isn't right and would conflict with this route question GET /questions/:id(.:format) questions#show. I would instead recommend building nested routes
resources :users do
resources :questions do
resources :bookmarks, only: [:create]
end
end
Update:
Along with the above, you should change #question = Question.find(params[:id]) to #question = Question.find(params[:bookmark][:question_id])
'param is missing or the value is empty: bookmark, this error means that, there is no bookmark key present in your params object, but you defined your bookmark_params to have one:
def bookmark_params
params.require(:bookmark).permit(:user_id, :question_id)
end
That's why it's throwing the above error message.
You should make sure you send the user_id and question_id key/value pairs under the bookmark key. Something like this:
bookmark: { user_id: 1, question_id: 2}.
So, your code should look something like this (adding the bookmark to params):
<%= link_to "Bookmark", :controller => 'bookmarks', :action => 'create', bookmark: {user_id: current_user.id, question_id: #question.id} %>
So I am in the process of setting up a forum and everything is setup/working well except for my replies are not appearing on the thread "show" page. After checking the rails console, I see they are saving but the user_id and discussion_id are no. The user_id is always nil and the discussion_id is always 0. The discussion threads were easier to setup but with having these replies, I obviously seem to be having an issue. Here are my snippets of code:
class PostsController
# ...
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
def create
#post = #discussion.post.new(create_params) do |post|
post.user = current_user
end
if #post.save
redirect_to #discussion, notice: "It has been posted!"
else
render :new
end
end
def destroy
#post = #discussion.posts.find(params[:id])
#post.destroy
flash.notice = "Deleted"
redirect_to discussion_path(#discussion)
end
private
def create_params
params.require(:post).permit(:reply)
end
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
class DiscussionsController
def show
#discussion = Discussion.friendly.find(params[:id])
#post = Post.new
render :layout => 'discussion'
end
end
Partial rendered to reply:
<h2>Reply</h2>
<%= form_for [ #discussion, #post ] do |f| %>
<p>
<%= f.label :reply, "Reply" %><br/>
<%= f.text_field :reply %>
</p>
<p>
<%= f.submit 'Submit' %>
</p>
<% end %>
Partial rendered to show replies in on discussion page:
<h3><%= post.user.first_name %></h3>
<%= post.reply %>
Posted: <%= post.created_at.strftime("%b. %d %Y") %></p>
<p><%= link_to "Delete Comment", [post.discussion, post], data: {confirm: "Are you sure you wish to delete?"}, method: :delete, :class => "post_choices" %></p>
Just want to mention that I also have the correct associations between the three models (User, Discussion, Post). If there is more code needed, please let me know. I appreciate it very much for any information that may be helpful =)
Joe
EDIT
class User < ActiveRecord::Base
has_many :articles
has_many :discussions
has_many :posts
# ...
end
class Discussion
belongs_to :user
has_many :posts
extend FriendlyId
friendly_id :subject, use: :slugged
end
class Post
belongs_to :discussion
belongs_to :user
end
I could post the entire user model if needed but its all validations/devise aspects =P The other two I listed all of the contents in the models.
Edit 2
Thanks to Max, the user_id returns correctly in the console but still not the discussions. Going go dig around a bit more with the recent changes to see what else =)
There are a few issue you need to deal with.
First you should ensure that Devise is actually authorizing your controller action.
class PostsController < ApplicationController
before_filter :authenticate_user!
end
Otherwise current_user will return nil if there is no signed in user. And I'm
guessing that you do not want un-authenticated users to be able to create posts.
Also if you have a nested route you most likely want to check that the discussion actually
exists before trying to add posts.
class PostsController
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
private
# Will raise an ActiveRecord::NotFoundError
# if the Discussion does not exist
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
When you are creating resources be careful not to query the database needlessly.
This especially applies to CREATE and UPDATE queries which are expensive.
def create
#post = Post.create(post_params) # INSERT INTO 'users'
#post.discussion_id = params[:discussion_id]
#post.user = current_user
#post.save # UPDATE 'users'
flash.notice = "It has been posted!"
redirect_to discussions_path(#post.discussion)
end
Also you are not even checking if the record was created successfully.
So lets put it all together:
class PostsController
before_filter :authenticate_user!
before_filter :set_discussion, only: [:new, :create, :destroy]
def new
#post = #discussion.post.new
end
def create
# new does not insert the record into the database
#post = #discussion.post.new(create_params) do |post|
post.user = current_user
end
if #post.save
redirect_to #discussion, notice: "It has been posted!"
else
render :new # or redirect back
end
end
def destroy
#post = #discussion.posts.find(params[:id])
#post.destroy
flash.notice = "Deleted"
redirect_to discussion_path(#discussion)
end
private
def create_params
# Only permit the params which the user should actually send!
params.require(:post).permit(:reply)
end
# Will raise an ActiveRecord::NotFoundError
# if the Discussion does not exist
def set_discussion
#discussion = Discussion.friendly.find(params[:id])
end
end
So I am making a site where users can only submit a post once, and then the "new post" button goes away forever.
I would also like to put a limit on the overall amount of posts. So, only the first 100 or so people can actually post.
I used rails generate scaffold to build the posting system.
I don't know where to start.
Thanks!
You can either create a constant if all user will have the same limit, or add a field in your user record if you plan for each user to have different limits.
Then you create a validator which check the number of existing posts and forbid creation of new posts if the limit is reached
More info in rails guide: http://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations
An alternative approach is using a policy object. Here's how I would approach this using Pundit.
Updated:
app/models/post.rb
class Post < ActiveRecord::Base
belongs_to :user
def self.limit_exceeded?(max = 100)
count >= max
end
end
app/models/user.rb
class User < ActiveRecord::Base
has_one :post
end
app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
def create?
!user_has_post? && space_to_post?
end
private
def user_has_post?
user.post.present?
end
def space_to_post?
!Post.limit_exceeded?
end
end
app/controllers/posts_controller.rb
class PostsController < ApplicationController
def show
#post = Post.find(params[:id])
end
def new
#post = Post.new
end
def create
authorize(:post)
#post = current_user.build_post(post_params)
if #post.save
redirect_to #post, notice: "Your post was created!"
else
render :new
end
end
private
def post_params
params.require(:post).permit(:message)
end
end
app/view/posts/new.html.erb
<% if policy(:post).create? %>
<%= form_for(#post) do |form| %>
<%= form.text_area :message %>
<%= form.submit "Post" %>
<% end %>
<% else %>
You cannot post.
<% end %>
This code assumes the user is authenticated. If you haven't incorporated authentication, you'll need to use a gem for that, or roll your own implementation. I'd recommend Devise or Clearance.
Good luck!
Hello all i am trying to insert multiple records into the table using the same form so far i have achieved the following
class ProjectController < ApplicationController
def new
#project = Project.new
end
def create
#projec = Project.new(project_params)
respond_to do |format|
if #project.save
format.html { flash[:notice] = 'User successfully created.' and redirect_to action: "index"}
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def project_params
params.require(:project).permit(:PROJECT_ID,:COMPANY_ID, :ASSESSMENT_ID, :PROJECT_SCORE , :CREATED_BY, :UPDATED_BY)
end
end
so in my view i have used like this since i want only the project level score to be saved into the database with all other values remaining the same
<% 10.times do %>
<%= f.range_field :PROJECT_SCORE[], :min=>0, :max=>10, :class=>"slide", :id=>"slider1", name: 'PROJECT_SCORE[of_values][]'%>
<% end %>
next in my model i have used like this
class Project< ActiveRecord::Base
serialize :PROJECT_SCORE ,Array
end
but i receive an error
Attribute was supposed to be a Array, but was a Fixnum. -- 0
SO is there any alternate ways to insert multiple records in the table at the same time ? or how do i solve this issue ?
There is an alternate way for inserting multiple records and I would highly recommend doing so. You could get this to work but it feels a bit hacky and not very flexible to me. Try using a has_many association and nested forms instead. If you're not familiar with has_many associations there is an introductory course at codeschool.com called Models Taste Like Chicken. There is also a great RailsCasts episode (#196) that goes into detail about nested forms with some cool AJAX features.
To do this you could create a Model called Score and tell it to belong the projects:
rails g model Score score:integer project:references
class Score < ActiveRecord::Base
belongs_to :project
end
And each project will have many scores:
class Project < ActiveRecord::Base
has_many :scores, dependent: :destroy
accepts_nested_attributes_for :scores, allow_destroy: true
end
The dependent destroy makes sure the associated scores get deleted if a Project is deleted. You also need to tell it to accept nested attributes for the scores model. Details here.
Next, set up the strong parameters with things_attributes:[:thing1, :thing2, :etc]
class ProjectController < ApplicationController
### other stuff
def project_params
params.require(:project).permit(:PROJECT_ID, :etc, scores_attributes: [:id, :score])
end
One thing I would like to mention here is the best practice for
naming conventions is to use snake_case for your
variables and database names. So instead of :PROJECT_ID name it
:project_id
ALL_CAPS is usually used for constants.
Now your view you can use the fields_for form helper to create another form block within the form block.
<%= form_for #project do |f| %>
<div class="field">
<!-- all normal form inputs -->
</div>
<!-- and now the nested form -->
<%= f.fields_for :scores do |ff| %>
<div class="field">
<%= ff.range_field :score %>
</div>
<% end %>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
This won't allow you to create new scores yet, but that starts to get really complicated and is explained in the RailsCasts I mentioned above (However he is using an older version of rails so make sure and set the strong parameters for the nested attributes).
The README does not show how to handle the controller and view aspects of setting up this plugin. I have been searching for a couple hours and can't find anything that shows how to use this plugin.
After even more searching, I gave up on finding a tutorial and came up with this. If anyone can point out a better / cleaner way to do this, please let me know. Otherwise, here is what I am using now in case this will benefit anyone else.
First, install the plugin with script/plugin install http://github.com/jackdempsey/acts_as_commentable.git -r 2.x
Then, generate the comment model and migration with script/generate comment and migrate the database with rake db:migrate
The tricky bit is nesting comments under other resources in a polymorphic way. Here is what I did:
# In config/routes.rb
map.resources :comments, :path_prefix => '/:commentable_type/:commentable_id'
# In app/controllers/comments_controller.rb
before_filter :load_commentable
def create
#comment = #commentable.comments.build(params[:comment])
#comment.user = current_user
respond_to do |format|
if #comment.save
format.html { redirect_to #commentable }
else
format.html { render :action => 'new' }
end
end
end
protected
def load_commentable
#commentable = params[:commentable_type].camelize.constantize.find(params[:commentable_id])
end
# In app/views/comments/_form.html.erb
<%= form_for(:comment, :url => comments_path(commentable.class.to_s.underscore, commentable.id)) do |f| %>
# In app/views/model_that_allows_comments/show.html.erb
<%= render :partial => 'comments/form', :locals => {:commentable => #model_that_allows_comments} %>
I think that shows the relevant parts clearly enough to understand what is happening. It makes it possible to add acts_as_commentable to any model. You just have to pass in the commentable object in the locals hash when rendering the comments form and the same comments controller / view code should work.
acts_as_commentable merely exposes you a Comment model and takes care of the plumbing between that model and your commentable models. It doesn't give you any controllers or views. You are responsible for deciding how you want to implement this part of your application.
It is pretty straightforward, though. For example...
# in routes.rb
map.resources :posts, :has_many => :comments
# in your comments controller...
class CommentsController < ApplicationController
before_filter :get_post
def get_post
#post = Post.find(params[:post_id])
end
def index
#comments = #post.comments.all # or sorted by date, or paginated, etc.
end
end
# In your haml view...
%h1== Comments for #{#post.title}:
%ul
- comments.each do |comment|
%h3= comment.title
%p= comment.comment
You'll see the comments for a particular post when you go to /posts/1/comments now.
I think the best way to add comments to any model is creating a method called comment in your ApplicationController.rb file like this.
def comment
# Extracts the name of the class
klass = self.class.to_s[/\A(\w+)sController\Z/,1]
# Evaluates the class and gets the current object of that class
#comentable_class = eval "#{klass}.find(params[:id])"
# Creates a comment using the data of the form
comment = Comment.new(params[:comment])
# Adds the comment to that instance of the class
#comentable_class.add_comment(comment)
flash[:notice] = "Your comment has been added!"
redirect_to :action => "show", :id => params[:id]
end
and then just create some partial _comment.rb to use it in any model you want
<%= form_tag :action => "comment", :id => Your_model_goes_here %>
<p><label for="comment_title">Title</label><br/>
<%= text_field 'comment', 'title' %></p>
<%= text_area "comment", "comment", :rows => 5, :cols => 50 %> <br />
<%= submit_tag "Comment!.." %>
</form>
I hope it's useful for someone...