I am working on a rails application where one user class named (submitters) are able to login and once they are logged in they create videos. My videos controller is here:
class VideosController < ApplicationController
def index
#videos = Video.find :all
end
def new
#submitter = current_submitter
#video = #submitter.videos.build
end
def create
#submitter = current_submitter
#video = #submitter.videos.build(params[:video])
if #video.save
#video.convert
flash[:notice] = 'Video has been uploaded'
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def show
#video = Video.find(params[:id])
end
def destroy
#video = Video.find(params[:id])
#video.destroy
flash[:notice] = "Successfully deleted the video."
redirect_to root_url
end
def update_date
#video = Video.find(params[:id])
#video.update_attributes(params[:video])
flash[:notice] = "Successfully added a launch date!"
redirect_to #video
end
end
As you can probably see, I am trying to construct the controller so that when a video is created, it is created as belonging to the submitter who upload the video (via the video new view). I am using a auth system with a current_submitter method written in the application controller.
Now it lets me upload a video fine when I am logged in as a submitter. The trouble for me is working out how to display information in my view. If I want to display some columns with information about the video and then others with information about the submitter who uploaded the video, how do I go about doing that from the controller (index action), into the index view. My current view which does not work in below:
<% title "Films Submitted" %>
<table>
<tr>
<th>Title</th>
<th>Film Type</th>
<th>Premiere</th>
<th>Company</th>
<th>Name</th>
</tr>
<% for video in #videos do %>
<tr>
<td><%= link_to video.title, video %></td>
<td><%= video.film_type %></td>
<% if video.premiere == "true" %>
<td>Premiere</td>
<% else %>
<td><%= %></td>
<% end %>
<td><%= video.submitter.company %></td>
<td><%= video.submitter.name %></td>
<td><%= link_to "Delete", video, :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br><br>
<%= link_to "Upload a Video", new_video_path %>
Any suggestions or tips from rails developers would be much appreciative... I am new and trying to learn.
Video Model:
class Video < ActiveRecord::Base
belongs_to :submitter
has_attachment :content_type => :video,
:storage => :file_system,
:max_size => 50.megabytes
end
Submitter Model:
class Submitter < ActiveRecord::Base
acts_as_authentic
has_many :videos
end
Schema:
create_table "videos", :force => true do |t|
t.string "title"
t.text "description"
t.string "state"
t.datetime "created_at"
t.datetime "updated_at"
t.string "content_type"
t.integer "size"
t.string "filename"
t.string "film_type"
t.boolean "premiere", :default => false
t.date "preferred_date"
t.text "reason"
t.integer "submitter_id"
t.date "actual_date"
end
create_table "submitters", :force => true do |t|
t.string "name"
t.string "company"
t.string "email"
t.string "username"
t.string "crypted_password"
t.string "password_salt"
t.string "persistence_token"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "video_id"
end
I think you've probably only setup your associations in one direction. You already have:
class Submitter < ActiveRecord::Base
has_many :videos
end
But you also need to reciprocate that relationship in the video model:
class Video < ActiveRecord::Base
belongs_to :submitter
end
This will create the submitter method in your video model, that references the submitter it belongs to.
UPDATE:
After you updated your question with the model info, I noticed one small thing: You have a video_id column on your submitters table that doesn't need to be there. Only the "belongs_to" side of the association needs a foreign key. But this isn't causing your problem.
I think you might have a video in your database without a submitter! In the index action, since you're not checking for a valid submitter before you start using it, if even a single video record is missing a submitter_id, it'll break the view.
Related
hello I have 2 models User.rb and Guest.rb.
in my app the user is responsible for entering the Guest information. I have a table in my views that will show all guests. I would like each guest row to display the user who has entered their information. Iam having some trouble properly setting up the current_user method in my controller methods. Currently iam grabbing the current_usera nd entering it next to every guest. Thank you so much in advance.
Controller:
def new
#guest = Guest.new
end
def create
#guest = Guest.new(guest_params)
if #guest.save
redirect_to guests_path
else
render 'new'
end
end
def index
#guests = Guest.all
#user = current_user
end
def show
#guest = Guest.find(params[:id])
#user = current_user
end
def edit
#guest = Guest.find(params[:id])
end
def update
#guest = Guest.find(params[:id])
if #guest.update(guest_params)
flash[:success] = "Profile updated"
redirect_to #guest
else
render 'edit'
end
end
def destroy
Guest.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to guests_url
end
def guest_params
params.require(:guest).permit(:experience,:interaction,:mood,:guest_name,:room_num,:arrival_date,:departure_date,:opportunity_string,:employee,:notes,:opportunity)
end
end
Models:
has_and_belongs_to_many :users
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_and_belongs_to_many :guests
end
Views:
body{background-color:white;}
</style>
<h1 class="text-center mt-3">Guests</h1>
<div class="container-fluid" style="overflow-x: auto; mb-3">
<table class="table table-bordered text-center ">
<thead>
<tr style="background-color:#CFD2CF;font-size:1.4vw">
<th>GUEST</th>
<th>EXPERIENCE</th>
<th>ROOM</th>
<th>ARRIVAL</th>
<th>DEPARTURE</th>
<th>OPPORTUNITY</th>
<th>EMPLOYEE</th>
<th>DEPARTMENT</th>
</tr>
</thead>
<tbody>
<% #guests.each do |guest| %>
<tr style="background-color:<%=guest.mood%>">
<td> <%= link_to guest.guest_name, "/guests/#{guest.id}" %></td>
<td><%= guest.experience %></td>
<td><%= guest.room_num %></td>
<td><%= guest.arrival_date %></td>
<td><%= guest.departure_date %></td>
<td ><%= #user.current_user%></td>
<td><%= %></td>
<td><%= guest.interaction %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
Schema:
create_table "guests", force: :cascade do |t|
t.string "experience"
t.string "interaction"
t.string "mood"
t.string "guest_name"
t.string "room_num"
t.string "arrival_date"
t.string "departure_date"
t.string "opportunity_string"
t.string "employee"
t.string "notes"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "user_id", null: false
t.index ["user_id"], name: "index_guests_on_user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.boolean "superadmin_role", default: false
t.boolean "supervisor_role", default: false
t.boolean "user_role", default: true
t.string "name"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "guests", "users"
end
A few things that will help:
Devise and current_user:
Devise takes care of current_user for you, so you should just be able to call current_user in your controllers and views.
#user.current_user isn't a method unless you've created one in your User model, which I would not recommend doing.
Don't mix #user with current_user
It's possible to set #user = current_user, but I think it's a bad practice as it will get confusing quickly
#user should be tied to the model User and represent the user that the current_user is interacting with.
For example, a URL like /users/1/edit should set #user = User.find(1).
The current_user could be someone else who is editing the #user object.
HABTM Associations
Given an assigned #user, you can call #user.guests to get all guests associated with that user.
E.g. for a route that creates the following URL: /users/1/guests then your controller can have something like this:
# users_controller.rb
class UsersController < ApplicationController
...
def guests
#user = User.find(params[:id])
#guests = #user.guests
end
end
And the reverse is true as well. For a route like guests/1/users you can call #guest.users.
But...
Do you really want a HABTM?
If a User can create many Guests, but a Guest is never associated with many Users, then this isn't really a "has and belongs to many" relationship. You probably just want a simple has_many belongs_to.
I revisit this article often as a refresher when I'm considering my relationships.
Your schema has a user_id on your guest model, which indicates to me that you want to be able to say: "A User has_many guests. A Guest belongs_to a user."
But you wouldn't really say "A Guest can belong_to many Users"
Code fixes
I have a table in my views that will show all guests.
For ALL guests, this would be the URL /guests, which should map to GuestsController#index:
# guests_controller.rb
class GuestsController < ApplicationController
...
def index
#guests = Guest.all # or some scope like Guest.active
end
...
end
For guests related to a give user, this would be the URL /users/:id/guests which should map to UsersController#guests:
# users_controller.rb
class UsersController < ApplicationController
before_action :set_user, only: %i[show edit update guests]
...
def guests
#user.guests
...
end
...
private
# this method sets #user for all views defined in the `:only` hash of the `before_action` callback.
def set_user
#user = User.find(params[:id]
end
I would like each guest row to display the user who has entered their information.
Since you have a user_id field on Guest, if you switch to a has_many belongs_to relationship, then you can just call the user:
<tbody>
<% #guests.each do |guest| %>
<tr style="background-color:<%=guest.mood%>">
<td> <%= link_to guest.guest_name, "/guests/#{guest.id}" %></td>
<td><%= guest.experience %></td>
<td><%= guest.room_num %></td>
<td><%= guest.arrival_date %></td>
<td><%= guest.departure_date %></td>
<td ><%= guest.user%></td> <!-- guest.user instead of #user.current_user -->
<td></td>
<td><%= guest.interaction %></td>
</tr>
<% end %>
</tbody>
Extra credit: use Includes to pre-load associations
Also, as a pro tip, calling guest.user could get slow because each guest record needs to make a call to the User table.
Rails offers eager loading for just this situation.
Change #guests = Guest.all to #guest = Guest.includes(:user).all and Rails will handle the rest.
I have a form with a select_tag and options_from_collection_for_select that I can't seem to get to pass. In the view, when the upload id is set to uploadzip_id I get a 302 redirect and when it's set to uploadzip_ids, I get a Unknown Attribute error.
I'm a bit confused as I have my relationship set up along with the foreign key. I do have another model with checkboxes called Uploadpdf that works fine.
Here is the set up..
class Campaign < ActiveRecord::Base
has_one :uploadzip
end
class Uploadzip < ActiveRecord::Base
belongs_to :campaign
end
db/schema.rb
create_table "campaigns", force: :cascade do |t|
t.string "name"
t.text "comment"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
create_table "uploadzips", force: :cascade do |t|
t.string "file_name"
t.string "file_type"
t.datetime "date"
t.integer "size"
t.integer "pages"
t.string "file_ident"
t.string "md5"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "campaign_id"
end
add_foreign_key "uploadzips", "campaigns"
app/controllers/campaign_controller.rb
class CampaignsController < ApplicationController
def index
#campaigns = Campaign.all.order("created_at DESC")
end
def new
#campaign = Campaign.new
end
def create
#campaign = Campaign.new(campaign_params)
if #campaign.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
flash[:error] = "There was a problem launching your Campaign."
redirect_to new_campaign_path
end
end
.....
private
def campaign_params
params.require(:campaign).permit(:name, :comment, :uploadzip_ids, uploadpdf_ids: [])
end
end
views/campaigns/_form.rb
<%= form_for #campaign, url: {action: "create"} do |f| %>
.....some typical fields..
<%= f.label :data_file, class: "right-label" %>
<%= select_tag campaign[uploadzip_ids],
options_from_collection_for_select(
Uploadzip.all, :id, :file_name
), { include_blank: "Include a Zip File" } %>
.....some more typical fields
<% end %>
Update
I have changed the code to better reflect the foreign key as suggested. Creating a campaign is now successful but it's not associating with the chosen uploadzip Zip file selected. When calling #campaign.uploadzip, it returns nil.
Here is the updated code:
<%= select_tag "uploadzip[campaign_id]",
options_from_collection_for_select(
Uploadzip.all, :id, :file_name
), { include_blank: "Include a Zip File" } %>
I also changed the controller params.require to..
def campaign_params
params.require(:campaign).permit(:name, :comment, :campaign_id, uploadpdf_ids: [])
end
As per your association set-up,the foreign_key should be campaign_id not uploadzip_id. You should either change your associations or foreign_key according to your use-case.
And also I recommend you to follow these Guides to know more about associations.
The 302 redirect may not be a bad thing, since you're doing a redirect_to new_campaign_path. Are the records created correctly when you use uploadzip_id in both the view and controller params.permit section?
Member of a FaceBook group helped me figure it out by adding a little
extra logic in the controller..
if #campaign.save
zip = Uploadzip.find(params[:uploadzip_id])
zip.campaign = #campaign
zip.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
flash[:error] = "There was a problem launching your Campaign."
redirect_to new_campaign_path
end
..which was met with changing the select_tag's name.
<%= select_tag :uploadzip_id,
options_from_collection_for_select(
Uploadzip.all, :id, :file_name
), { include_blank: "Include a Zip File" } %>
I have a model "votes" which belongs_to two models by polymorphous association, and has the attributes user_id and comment_id. Previously, I had a voting system in place for users that would create a new vote for a specific user every time a button was pressed:
<%= form_for [#user, #vote] do |f| %>
<input type="hidden" id="user_id" name="user_id" value="#{#user.id}" />
<%= f.submit ": )", :onclick => 'alert("Voted up!")' %>
<% end %>
and #user.votes.count would return the number of times the button was pressed. However, I switched to a different method:
View:
<%= link_to "voteuser", vote_user_path(#user.id), method: :post, :class => "btn btn-small" %>
Controller:
def vote
#user = User.find(params[:id])
Vote.create!(user_id: #user.id)
redirect_to #user
end
Routes:
Website::Application.routes.draw do
root 'home_page#home'
get "votes/new"
get 'users/random'
post 'users/vote/:id' => 'users#vote', as: 'vote_user'
get 'users/users/random' => 'users#random'
resources :users
get "all/allusers"
get "all/users/new" => 'users#new'
get 'all/all/allusers' => 'all#allusers'
end
and a Vote is still created, with a user_id equal to the current User.id, but now #user.votes.count returns 0, so the application isn't registering that the vote belongs to the user. How can I remedy this?
Vote Model:
class Vote < ActiveRecord::Base
belongs_to :voteable, polymorphic: true
end
Votes Schema:
create_table "votes", force: true do |t|
t.integer "thing_id"
t.integer "comment_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "voteable_id"
t.string "voteable_type"
end
User Model:
class User < ActiveRecord::Base
has_many :votes, as: :voteable
end
User Schema:
create_table "users", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
end
It should be Vote.create!(voteable_id: params[:id], voteable_type: 'User') for users or in short
Vote.create!(vote: User.find(params[:id])) # Vote.create!(vote: Comment.find(params[:id]))
check if you retrieve the user id in your controller and you can in your view
<%= form_for [#user, #vote] do |f| %>
<%= f.hidden_field :user_id, value: f.model.user.id %>
<%= f.submit ": )", :onclick => 'alert("Voted up!")' %>
<% end %>
on your controller you can simply do:
def vote
Vote.create!(user_id: params[:id])
redirect_to #user
end
but you use the polymorphous association so you have to specify the user_id and the user_type.
So i'm trying to record the number of times a link is clicked but can't get over the last hurdle.
I have the following so far:
config/routes.rb
resources :papers do
resources :articles do
resources :clicks
end
end
click.rb
class Click < ActiveRecord::Base
belongs_to :article, counter_cache: true
validates :ip_address, uniqueness: {scope: :article_id}
end
clicks_controller.rb
class ClicksController < ApplicationController
def create
#article = Article.find(params[:article_id])
#click = #article.clicks.new(ip_address: request.ip)
#click.save
end
end
article.rb
class Article < ActiveRecord::Base
has_many :clicks
end
schema.rb
create_table "clicks", force: true do |t|
t.integer "article_id"
t.string "ip_address"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "articles", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.text "title"
t.string "url"
t.integer "paper_id"
t.integer "clicks_count"
end
index.html.erb -- articles
<% #articles.each do |article| %>
<div class="articles col-md-4">
<%= link_to article.url, target: '_blank' do %>
<h4><%= article.title %></h4>
<h5><%= article.paper.name.upcase %></h5>
<h6><%= article.created_at.strftime("%d %B %y") %></h6>
<% end %>
Firstly, does this setup look correct, does anyone see where i may have gone wrong?
Secondly, what i don't know how to set up my view so that when the existing link is clicked the click is registered and the count goes up?
Thanks
Solved with the following.
clicks_controller.rb
Original:
def create
#article = Article.find(params[:article_id])
#click = #article.clicks.new(ip_address: request.ip)
#click.save
end
end
Amended:
def create
#article = Article.find(params[:article_id])
#click = #article.clicks.new(ip_address: request.ip)
#click.save
redirect_to #article.url
end
end
index.html.erb -- articles
Original:
<%= link_to article.url, target: '_blank' do %>
Amended:
<%= link_to paper_article_views_path(article.id, article), method: :post, target: '_blank' do %>
Also, i edited the original question to include the routes.rb file.
In my opinion, you should do 2 things :
1) Set all the methods for "clicks" into a Model
For example, you can remove your ClicksController and add this :
class Article
def create_click(ip_address)
self.clicks.create({ :ip_address => ip_address })
end
end
A little note with this code : you have a uniqueness validation in your code. Indeed, when a click already exists for an article and an ip address, the create method will return false. Do not use create! instead, or it will raise an exception.
2) Add a filter :
You can simply add a filter in your ArticlesController. At each show, it will create a click instance for the viewed article
class ArticlesController
before_filter :create_click, :only => [ :show ]
def create_click
#article.create_click(ip_address)
end
end
I'm trying to follow along the Rails getting started guide except for the fact that I'm using Stocks instead of articles and TimeDeltas instead of comments. Now when I go to the view I keep getting this error
ActiveRecord::StatementInvalid in StocksController#show Could not find
table 'time_deltas'
on line: #time_delta = #stock.time_deltas.build
Heres my view:
<h1> Stock </h1>
<table>
<tr>
<th>Stock</th>
<th>Hashtag</th>
</tr>
<tr>
<td><%= #stock.name %></td>
<td><%= #stock.hashtag %></td>
</tr>
</table>
<h2>Deltas: </h2>
<table>
<tr>
<th>Stock</th>
<th>Hashtag</th>
</tr>
<% #stock.deltas.each do |delta| %>
<tr>
<td><%= #delta.start %></td>
<td><%= #delta.length %></td>
</tr>
<% end %>
</table>
<h2>Add a TimeDelta:</h2>
<%= form_for([#stock, #stock.time_deltas.build]) do |f| %>
<p>
<%= f.label :start %><br>
<%= f.text_field :start %>
</p>
<p>
<%= f.label :length %><br>
<%= f.text_area :length %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Back', stocks_path%>
<%= link_to 'Edit', edit_stock_path(#stock)%>
Heres my stocks controller:
class StocksController < ApplicationController
def new
#stock = Stock.new
end
def index
#stocks = Stock.all
end
def create
# XXX Add columns for delta and current standing when we get there
# they can intiate to nil
#stock = Stock.new(stock_params)
if #stock.save
redirect_to #stock
else
render 'new'
end
end
def show
#stock = find_stock
#time_delta = #stock.time_deltas.build
end
def edit
#stock = find_stock
end
def update
#stock = find_stock
if #stock.update(stock_params)
redirect_to #stock
else
render 'edit'
end
end
def destroy
#stock = find_stock
#stock.destroy
redirect_to stocks_path
end
private
def stock_params
params.require(:stock).permit(:name, :hashtag)
end
def find_stock
return Stock.find(params[:id])
end
end
Heres my TimeDelta controller
class TimeDeltasController < ApplicationController
def new
#stock = Stock.find(params[:stock_id])
#time_delta = #stock.time_deltas.build
# respond_with(#stock, #time_delta)
end
def create
#stock = Stock.find(time_delta_params)
#time_delta = #stock.time_deltas.create(params[:stock])
redirect_to stock_path(#stock)
end
private
def time_delta_params
params.require(:time_delta).permit(:start, :length)
end
end
I've tried re running my migrations as well and to no-avail, any suggestions?
EDIT: Both models
Stock:
class Stock < ActiveRecord::Base
has_many :deltas
has_many :time_deltas
validates :hashtag, :name , presence: true, :uniqueness => true, length: { minimum: 2 }
end
Time Delta:
class TimeDelta < ActiveRecord::Base
belongs_to :stock
end
Database Schema:
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20140412204548) do
create_table "delta", force: true do |t|
t.datetime "start"
t.integer "length"
t.integer "stock_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "delta", ["stock_id"], name: "index_delta_on_stock_id"
create_table "queries", force: true do |t|
t.text "tweet"
t.integer "tweetId"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "stocks", force: true do |t|
t.text "name"
t.text "hashtag"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "time_delta", force: true do |t|
t.datetime "start"
t.integer "length"
t.integer "stock_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "time_delta", ["stock_id"], name: "index_time_delta_on_stock_id"
end
When you created your table via migration the name of your "time_deltas" table was actually created as "time_delta". Keeping the database name in singular form could bring up troubles in the future. The best suggestion is to rename your database to "time_deltas" and it'd work just fine. Also, i noticed that there's another table with a singular name form (delta). I suggest you rename it to "deltas".
Happy coding!