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
Related
I am trying to add a comments section to posts on my web applications but getting an error when saving the comment
I have been following a tutorial to add a comments section to my posts and have been modifying as I go to work with my app. I am still relatively new to rails and I am still learning. I understand what the error message is telling me, but I am unsure as to how to proceed
Comments Controller:
class CommentsController < ApplicationController
def create
#micropost = Micropost.find_by(id: params[:id])
#comment =
#micropost.comments.create(params[:comment].permit(:body))
end
end
Microposts Contoller:
class MicropostsController < ApplicationController
before_action :logged_in_user, :upvote, :downvote, only:
[:create, :destroy]
before_action :correct_user, :upvote, :downvote, only:
:destroy, allow_destroy: true
def create
#micropost = current_user.microposts.build(micropost_params)
#maximum_length = Micropost.validators_on( :content,
:headline).first.options[:maximum]
if #micropost.save
flash[:success] = "Article Posted"
redirect_to root_url
else
#feed_items = []
render 'articles/home'
end
end
def destroy
#micropost.destroy
flash[:success] = "Micropost deleted"
redirect_to request.referrer || current_user
end
def show
#micropost = Micropost.find(params[:id])
end
private
def micropost_params
params.require(:micropost).permit(:content, :headline)
end
def correct_user
#micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if #micropost.nil?
end
end
Comments Form being rendered on post:
<%= form_for([#micropost, #micropost.comments.build]) do |f| %>
<br>
<p>
<%= current_user.name %>
<%= f.text_area :body %>
</p>
<br>
<p>
<%= f.submit %>
</p>
<% end %>
Comments Model:
class Comment < ApplicationRecord
belongs_to :micropost
end
Microposts Model:
class Micropost < ApplicationRecord
acts_as_votable
has_many :comments
belongs_to :user
validates :user_id, presence: true
validates :headline, presence: true, length: { maximum: 200 }
validates :content, presence: true, length: { maximum: 5000 }
end
Tables:
create_table "comments", force: :cascade do |t|
t.string "name"
t.text "body"
t.bigint "microposts_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "micropost_id"
t.index ["microposts_id"], name:
"index_comments_on_microposts_id"
end
create_table "microposts", force: :cascade do |t|
t.text "content"
t.bigint "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "headline"
t.index ["user_id", "created_at"], name:
"index_microposts_on_user_id_and_created_at"
t.index ["user_id"], name: "index_microposts_on_user_id"
end
The form is being rendered on the micropost show view. I can see the form fine.
When I press to save the comment on the post I get an error stating undefined method comments for nil:NilClass and it highlights the Comments controller and the line #comment = #micropost.comments.create(params[:comment].permit(:body))
I know there probably should a method somewhere for comments. The tutorial I watched didn't add anything like that. So I unsure do I need to modify some existing code, or do I need to add a method somewhere called comments?
where is the form to create comments, you are not sending the parameter params [: id], and with this parameter you are looking for the # micropost .. you must send the id in that form, a quick solution could be
<% = f.hidden_field: id, value: # micropost.id%>
in the comment creation form
Okay so the answers you all gave pointed me in the right direction. I wasn't actually passing an proper id. Instead of #micropost = Micropost.find_by(id: params[:id]) it should have been #micropost = Micropost.find_by(id: params[:micropost_id])
Many thanks all for getting me there.
UPDATE: Problem solved, thanks to Sebastian and Gabriel for the helpful pointers.
The relevant changes to my code are as follows:
app/controllers/pomodoro_cycles_controller.rb
def pomodoro_collections
{
pomodoro_collection_0: Pomodoro.offset(0).first(100),
pomodoro_collection_1: Pomodoro.offset(100).first(100)
}
end
app/views/pomodoro_cycles/show.html.erb
<% #pomodoros_collections.each do |pomodoros_collection_hash| %>
<h2><%= pomodoros_collection_hash[0] %></h2>
<% pomodoros_collection_hash[1].each do |pomodoro| %>
<p>
<%= pomodoro.id %>
<%= pomodoro.color %>
</p>
<% end %>
<% end %>
NOTA BENE:
The #first method in ActiveRecord returns an Array, so the keys in my original Hash were nested Arrays. Instead, the following was sufficient to return an Array of Pomodoro objects:
Pomodoro.offset(0).first(100)
DESCRIPTION OF ORIGINAL PROBLEM
Rails 5, PostgreSQL
PROBLEM: I cannot access Pomodoro.all from within PomodoroCycleController
I have two scaffolds: Pomodoro and PomodoroCycle, and I want to access the full list of Pomodoros within the PomdoroCycle controller.
The following code is kept simple, in order to make as clear as possible what I'm trying to do. If I can do these things, then I'll be able to do much more, but one step at a time.
Regarding the db migration files, I have already run bundle exec rails db:migrate
I want to display a full list of Pomodoros in the PomodoroCycle Show View (later to be displayed in Index), but I don't know what is missing.
From app/controllers/pomodoro_cycles_controller.rb
def show
#pomodoros_collections = pomodoro_collections
end
def pomodoro_collections
{
pomodoro_collection_0 => [Pomodoro.offset(0).first(100)],
pomodoro_collection_1 => [Pomodoro.offset(100).first(100)]
}
end
From app/views/pomodoro_cycles/show.html.erb
<% #pomodoros_collections.each do |collection| %>
<p><%= collection %></p>
<% end %>
However, this displays nothing in the browser.
app/models/pomodoro_cycle.rb
class PomodoroCycle < ApplicationRecord
has_many :pomodoros
end
app/models/pomodoro.rb
class Pomodoro < ApplicationRecord
belongs_to :pomodoro_cycle
end
Updated db/migrate/20180103032759_create_pomodoro_cycles.rb:
class CreatePomodoroCycles < ActiveRecord::Migration[5.1]
def change
create_table :pomodoro_cycles do |t|
t.string :activity
t.integer :iteration
t.integer :matrix_side_length
t.datetime :created_at
t.datetime :completed_at
t.string :category_labels, array:true, default: []
t.string :category_colors, array:true, default: []
t.string :username
t.timestamps
end
create table :pomodoros do |t|
t.belongs_to :pomodoro_cycle, index: true
t.datetime :completed_at
t.timestamps
end
add_index :pomodoros, :pomodoro_cycle_id
end
end
Untouched db/migrate/20180103054425_create_pomodoros.rb
class CreatePomodoros < ActiveRecord::Migration[5.1]
def change
create_table :pomodoros do |t|
t.boolean :status
t.string :category
t.string :color
t.datetime :completed_at
t.string :username
t.timestamps
end
end
end
First of all, as #SebastianPalma pointed out in the comments, the syntax is wrong
def pomodoro_collections
{
pomodoro_collection_0 => [Pomodoro.offset(0).first(100)],
pomodoro_collection_1 => [Pomodoro.offset(100).first(100)]
}
end
should be:
def pomodoro_collections
{
pomodoro_collection_0: [Pomodoro.offset(0).first(100)],
pomodoro_collection_1: [Pomodoro.offset(100).first(100)]
}
end
Make the keys in the hash symbols
Then to display each Pomodoro put something like:
<% #pomodoros_collections.each do |pomodoros_collection_hash| %>
<h2><%= pomodoros_collection_hash[0] %></h2>
<% pomodoros_collection_hash[1].each do |pomodoro| %>
<p><%= pomodoro.id %></p> #Or the attribute you want to display
<% end %>
<% end %>
Hope this help
I have a bunch of 'kid' objects saved already and I want to create a parent object which is linked to the kids via a 'relative' model.
This object gives me a many-to-many, through relatives.
To be clear: the user visits the 'parents' page, clicks create parents and is presented with a form that lets them name the parent and add up to four children to this parent (by creating 'relatives'), each of these 'relations' is also named - that's an important part. So, I could name the relation 'step son' or 'son', for instance.
Here's the code I have so far:
class Kid < ActiveRecord::Base
has_many :relatives
has_many :parents, through: :relatives
end
class Parent < ActiveRecord::Base
has_many :relatives
has_many :kids, through: :relatives
accepts_nested_attributes_for :relatives,
:reject_if => lambda { |a| a[:content].blank? },
:allow_destroy => true
end
class Relative < ActiveRecord::Base
belongs_to :parent
belongs_to :kid
end
class ParentsController < ApplicationController
before_action :set_parent, only: [:show, :edit, :update, :destroy]
before_action :lookup_kids, only: [:new, :edit]
# GET /parents
# GET /parents.json
def index
#parents = Parent.all
end
# GET /parents/1
# GET /parents/1.json
def show
end
# GET /parents/new
def new
#parent = Parent.new
4.times { #parent.relatives.build }
end
# GET /parents/1/edit
def edit
end
# POST /parents
# POST /parents.json
def create
#parent = Parent.new(parent_params)
parent_params[:relatives_attributes].each do |k,r|
#parent.relatives.build(r.except(:_destroy))
end
respond_to do |format|
if #parent.save
format.html { redirect_to #parent, notice: 'Parent was successfully created.' }
format.json { render :show, status: :created, location: #parent }
else
format.html { render :new }
format.json { render json: #parent.errors, status: :unprocessable_entity }
end
end
end
# cut for brevity.
private
# Use callbacks to share common setup or constraints between actions.
def set_parent
#parent = Parent.find(params[:id])
end
def parent_params
params.require(:parent).permit(:name,
relatives_attributes: [:parent_id, :kid_id, :relationship, :_destroy])
end
def lookup_kids
#kids = Kid.all #for this nursery.
end
end
<%= form_for(#parent) do |f| %>
<% if #parent.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#parent.errors.count, "error") %> prohibited this parent from being saved:</h2>
<ul>
<% #parent.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<h4>Kids:</h4>
<%= f.fields_for :relatives do |r| %>
<%= r.label :kid %>
<%= r.collection_select :kid_id,
#kids, :id, :name, include_blank: true%>
<%= r.label :relationship %>
<%= r.text_field :relationship %>
<%= r.check_box :_destroy %>
<%= r.label :_destroy, "Remove" %>
<br/>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
ActiveRecord::Schema.define(version: 20151030113634) do
create_table "kids", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "parents", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "relatives", force: :cascade do |t|
t.string "relationship"
t.integer "parent_id"
t.integer "kid_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "relatives", ["kid_id"], name: "index_relatives_on_kid_id"
add_index "relatives", ["parent_id"], name: "index_relatives_on_parent_id"
end
When I get to 'create' in the parents controller, I can see the right parameters are getting through but the relationship records aren't being saved. SHouldn't this happen automatically?
I've tried looping through the :relatives_attributes but that doesn't seem to work with 'build'.
How am I suppsed to get the 'relatives' records to save?
EDIT: adding parameters posted:
parent"=>{
"name"=>"Dad",
"relatives_attributes"=>{
"0"=>{"kid_id"=>"2", "relationship"=>"Son", "_destroy"=>"0"},
"1"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"},
"2"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"},
"3"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"}}}
Edit: I've updated this to show my latest edit - note the 'parent_params[:relatives_attributes].each do |k,r|' in the controller. This now saves the kid records but the only problem is, it also saves the fields that are blank! So I have 'relative' records with null values for kid records. How can I stop it saving empty fields (or creating empty relative records)?
The answer was to build each sub-record of relative, like so:
parent_params[:relatives_attributes].each do |k,r|
#parent.relatives.build(r.except(:_destroy))
end
Before calling #parent.save.
However, I'm still having issues getting rid of the blank records. So if anyone has an answer to that problem, please comment here - or if there's a better or more traditional way of doing this, hit me up. Follow up question here: Why is this reject_if in my model not rejecting blank records?
You are almost there, depending upon how your form submission is, you most likely need an accepts_nested_attribute_for in your Relative associative class as well:
class Relative
belongs_to :parent
accepts_nested_attributes_for :parent
belongs_to :kid
accepts_nested_attributes_for :kid
end
If this doesn't work, then please submit your params that are passed into the controller and we can adjust accordingly
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.
I am currently using Rails 4 to create a website and I need to create a form for a model "candidature" inside a model "post" using another model "reponse".
This the post model :
class Post < ActiveRecord::Base
validates :title, presence: true,
length: { minimum: 5 }
belongs_to :entrepreneur
belongs_to :categorie
has_many :candidatures
accepts_nested_attributes_for :candidatures
has_many :questions
accepts_nested_attributes_for :questions,
:reject_if => lambda { |a| a[:enonce].blank? },
:allow_destroy => true
end
The candidature model :
class Candidature < ActiveRecord::Base
has_many :reponses
accepts_nested_attributes_for :reponses, :allow_destroy => true
end
And the reponse model :
class Reponse < ActiveRecord::Base
belongs_to :candidature
end
I don't have controllers for candidature and reponse, because i don't think i need any (do correct me if I'm wrong).
I have created posts that are projects and in the show view of posts, i need to create a form where a guest can answer some questions through answer and all those answers have to be saved in one candidature.
The schema.rb looks like that :
ActiveRecord::Schema.define(version: 20130718083016) do
create_table "candidatures", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.integer "post_id"
end
create_table "questions", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.integer "post_id"
t.text "enonce"
end
create_table "reponses", force: true do |t|
t.integer "candidature_id"
t.datetime "created_at"
t.datetime "updated_at"
t.text "enonce"
end
create_table "posts", force: true do |t|
t.string "title"
t.datetime "created_at"
t.datetime "updated_at"
t.text "defi"
t.text "mission"
t.text "competences"
t.text "gain"
t.text "lieny"
t.text "liendb"
t.string "link"
end
The controller for post :
class PostsController < ApplicationController
layout :resolve_layout
include Candidaturetopost
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
flash[:notice] = "Successfully created project."
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
#post.candidatures.build
end
def index
#posts = Post.all
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title, :image, :defi, :mission, :competences, :gain, :lieny, :liendb, :link, questions_attributes: [:enonce, :post_id, :id, :_destroy],
candidatures_attributes: [:post_id, :id, reponses_attributes: [:candidature_id, :id, :enonce]])
end
The show view i have tried to get working :
<%= form_for #post do |f| %>
<%= f.fields_for :candidatures do |cform| %>
<ol>
<% for question in #post.questions %>
<li><%= question.enonce %></li>
<%= cform.fields_for :reponses do |rform| %>
<%= rform.text_area :enonce %>
<% end %>
<% end %>
</ol>
<%= cform.submit %>
<% end %>
<% end %>
With the code shown here, the text_area for enonce isn't even displayed.
Is what I want to do even possible ? How can I have something similar if not ?
Try this
#post.candidatures.build
#post.candidatures.each {|candidature| candidature.responses.build }
But as I wrote in comment you overcomplicated the model structure and I belive you should rethink it.
You were right, it was way too complicated.
I used this to help me get it working, I have now the links I wanted between my three models.