Rails 4: gem 'impressionist' to count view on pins/show.html.erb - ruby-on-rails

I have rails have where users can create pins. And I would like to render a number of views on the pin show page. Lets say that pins are profiles, I want to render the number of times a profile has been viewed.
So I installed the impressionist gem
And here is how I implemented it:
App/models/pin.rb
is_impressionable
App/controllers/pin_controller.rb
impressionist actions: [:show]
App/views/pins/show.html.erb
<%= #pin.impressionist_count %>
Unfortunately it doesn't render the number of views, it shows 0 instead. It is worth noticing that I don't care to get unique IP adress views, I want the number of views being updated every time a user click on the show action of a pin.
Any ideas to put me on track ?
UPDATE 1:
As I am using friendly ID on my pins model I removed the following line from my pin_controller:
impressionist actions: [:show]
And I updated the show view of the controller:
def show
#disable_nav = true
#pin.upvoted_users
impressionist(#pin)
#pin = Pin.friendly.find(params[:id])
#commentable = #pin
#comments = #commentable.comments
#comment = Comment.new
end
But now here is the error I have:
undefined method `impressions_count' for #
SOLUTION:
My bad... I forget to run the impressions_count to my model:
Rails generate migration add_impressions_count_to_pins impressions_count:integer
Rake db:migrate
Thanks :-)

In View
<%= #pin.impressionist_count %>
change above one to the below one
<%= #pin.impression_count %>
or you can do
<%= #pin.impressionist_count(:filter=>:ip_address) %>
As per README

Related

How to display create page/form within a separate controllers show page?

I would like to have the ability to create an order directly from the listings show page instead of having to be directed to a new orders page.
I have a Listing (listingcontroller show method) which can be purchased by clicking a button to go to an orders page (orderscontroller create method).
In what way can I have the order form directly on the listings show page?
I have tried adding the form, but I get error:
First argument in form cannot contain nil or be empty
<%= form_for([#listing, #order]) do |form| %>
When I take the Orders controller create method and put it in the Listings Controller Show method i get this error:
Couldn't find Listing without an ID
Here's the form_for I want within the Listings Show Page:
<%= form_for([#listing, #order]) do |form| %>
....
Orders Controller create:
#order = Order.new(order_params)
#listing = Listing.find(params[:listing_id])
#seller = #listing.user
#order.listing_id = #listing.id
#order.buyer_id = current_user.id
#order.seller_id = #seller.id
...
Routes:
resources :listings do
resources :orders
end
listing model:
has_many :orders
category model:
has_and_belongs_to_many :listings
I tried taking the orders create method and injecting it into the Listings show method with "def create" and without. I put "#listing = Listing.find(params[:listing_id])" ahead of the create method (when using "def create" and i would still get the error it needs an id. Even when I get that error, at the bottom of the webpage the request shows the listing ID is there.
I tried using a hidden field in the form but didn't work for me.
Do I need to do something to the controllers or is there a way to load the :listing_id into the form somehow. This is probably something very quick and simple for some of you but why won't it load on the listings show page, but loads fine in the orders create page?
Easy approach.
Your show action in listing_controller.rb should have the following code:
def show
#listing = Listing.find(params[:listing_id])
#order = #listing.orders.build
.
.
.
end
Your views/listings/show.erb should have the following code
<%= form_for(#order, url: listing_orders_path(#listing)) do |f| %>
.
.
.
<%= end %>
This way you create an order to the listing (in memory) before you submit the form. You can add the listing id as a hidden field.
After submit the order you modify your orders_controller.rb this way:
def create
#listing = Listing.find(params[:listing_id])
#order = #listing.orders.build(params[...]) #select the params you need for the order creation. Since you create the order directly to the listing you don't need to add the listing_id to the order.
if #order.save
#do something
else
#do something
end
end
Keep in mind that using params[] directly you have security problems, please check about mass assignment: https://guides.rubyonrails.org/v3.2.8/security.html
You can achieve that by using AJAX call, where you will pass the url of orders action and other params. There will be no reload of page and you'll get the functionality right on the listings page.
Here is the link to have a look - How AJAX calls work.

Ruby on Rails: What to call in view?

I have a next method in the model.
def self.next(comment, key = :id)
self.where("#{key} > ?", comment.send(key)).first
end
In my view I can say for example: (does not work)
= link_to "next", #comment.next(#comment)
What's the correct way to call this method?
routes.rb:
Rails.application.routes.draw do
resources :articles do
resources :comments do
end
end
end
You've defined next as a class method (vs an instance method), so you need:
= link_to "next", Comment.next(#comment)
If you want to be able to call #comment.next, define the instance method as:
def next(key = :id)
Comment.where("#{key} > ?", self.send(key)).first
end
It is not good style that the model knows this, you should put it in the controller. You should try a gem called kaminari, this gem lets you paginate over the elements, so in your comments controller you could have something like:
def show
#comment = Comment.order(id: :asc).page(params[:page]).per(1)
end
Then in your view, by just adding this kaminari helper:
<%= paginate #comment %>
You get the pagination bar below and everything works fine (gem's magic).
If you don't like this you could try to add that next method in the controller or find both next and current elements and link to the next element.
In my opinion the model is just a class that knows how to save and get information from the database and maybe some calculations with it's information, so all that logic related to the view should be elsewhere.

rails best way to display button only for admin

I have an application with Post model with associated PostsController and Admin::PostsController under the admin namespace in routes. Controllers share the same index action with controller concern, similar to this approach. I'm using a shared view partial under the shared/posts/_posts_list to list all posts on site and also in the admin dashboard. All this is working as I expected.
I'm asking what is the best approach to add for instance: edit post button only for admin user, that view doesn't get bloated with conditionals like <% if current_user.admin? %> to display this edit button.
If you don't want to put conditionals in your view go for two
separate views (one for regular users, one for admins).
Use conditionals in view and
include necessary elements depending on is_admin?. This is the shortest way to go if the only conditional is is_admin? check, imho. If there are more conditionals (roles, etc.) you are right, this will bloat views. You can check redmine (it is a big, open source rails project) and see that it uses conditionals in views too. Check its base layout here.
You can use helpers to generate elements depending on is_admin?. Lets say you need to show menu elements in your view. You can generate menu elements in your helper. And you can call the helper from your view. Such as:
view.html.erb
<ul>
<%= main_menu_items("main_menu", current_user) %>
</ul>
some_helper.rb
def main_menu_items(menu_name, user = current_user)
allowed_items(menu_name, user).map { |menu_item| content_tag(:li, menu_item) }.join
end
def allowed_items(menu_name, user)
menus = {
main_menu: ["Menu Item 1", "Menu Item 2"],
main_menu_admin: ["Menu Admin Item 1"]
}
menu_to_return = menus[menu_name.to_sym]
menu_to_return += menus[(menu_name + "_admin").to_sym] if user.admin?
end
This is just some quick code. If you want to use it in production, you must make some heavy changes (refactoring, getting items from models, nil checks, etc).
I'd say you've got two approaches:
The one you mentioned. Your view shouldn't get too bloated, and this is the one I'd go with.
You could use the current_user.admin? Check to put an "is_admin" CSS class on your page body. Then, you can add "admin_only" classes to your buttons etc, and use CSS to hide the relevant buttons and stuff when no "is_admin" class exists.
Either approach is fine, but 1 is a bit simpler and immediately more obvious to anyone reading the code, so I'd start with that. Kinda depends on how many admin-only features your UI will have though... If there'll be heaps, option 2 might be worth it.
You need an authorization library(https://github.com/CanCanCommunity/cancancan)
Gem install
gem 'cancancan', '~> 1.10'
rails g cancan:ability
config
class Ability #model/ability.rb manage roles
include CanCan::Ability
def initialize(user) # user is devise current_user
if user.blank?
else
if user.role_id == 1 # role_id 1 is admin
can :manage, Post #Post is you model
end
end
end
view
<% if can? :update, #post %>
<%= link_to "Edit", edit_post_path(#post) %>
<% end %>
controller
class ArticlesController < ApplicationController
load_and_authorize_resource
def index
#posts = Post.accessible_by(current_ability).order("id ASC") #posts is filtered by current_user's roles
end
def show
# #post is already loaded and authorized
end
end

Ruby on Rails layouts and rendering

I'm new to RoR and I'm a little bit confused with Rails MWC. I feel like I misunderstand something.
For example, I want to have home page where I could render top 5 articles and top 5 products. Products and articles have no relations at all, it is totally separate data.
So what I try to do is, i crate 2 sacffolds products and articles, and 1 controller for home page. I root to homepage controller. Then in homepage template i try to render products and article template. I get an error that methods which are used in products and articles controllers are undefined.
I don't understand where is problem. Is this kind of template rendering one template inside another is not Rails convention. Or I have bugs in my code.
I don't see your code but in this case I'm quite sure you have bugs in it.
app/controllers/home_controller.rb
class HomeController < ApplicationController
def index
#products = Product.top5 # Your logic to fetch top 5
#articles = Article.top5
end
end
app/views/home/index.html.erb
<% #products.each do |product| %>
<%= product.name %>
<% end %>
<% #articles.each do |article| %>
<%= article.name %>
<% end %>
This is perfectly fine, I've done that multiple times. Consider that in Rails you don't have any relation between controller and models, there are convention but Rails controller is not bound at all to any model
First, you need to instantiate your variables #products and #articles (this is an example) on your controller method. Then you can render the view.
Pay attention to add an # before. Only variables with an # will be available on your rendering view.
By default, when you call a GET for /products you'll arrive on the index method. At the end of this method, if not any view is specified, Rails will render views/products/index. In this view, you'll access all variables instantiate with an # and do whatever you want with.
First, yes, a template rendering another controller's template (not a partial) is not within Rails conventions.
A scaffold is a "single-resource" controller: it takes your model definition and generates a basic controller for editing and displaying that particular model (i. e., Product).
What you really need to do is use the two models you've generated in the home page controller, kinda like this:
class HomePageController < ApplicationController
def index
#articles = Article.top_5
#products = Product.top_5
# Render the #articles and #products in the view.
end
end

Rails routing causing error?

I'm super new to Ruby/Rails and need help with an assignment I'm working on. I'm supposed to add native advertising to Bloccit (project i'm working on for class) in the form of sponsored links. Any help would be very appreciated :)
What needs to be done:
1) Create a new model called Advertisement. It should have the following attributes: title:string, copy:text, price:integer.
2) Generate a controller for the new advertisement model with index and show actions. Should the controller class and file names have a singular (advertisement) or plural (advertisements) prefix? Be consistent with the naming pattern used for the posts controller class and file generated earlier.
3) Update routes.rb to use resourceful routing for Advertisement.
4) Complete the index and show actions in AdvertisementsController.
5) Update the Advertisement index and show views.
This is what I've got:
First thing, create model like so
rails g model Advertisements title:string, copy:text, price:integer
Then, create controller like so
rails g controller Advertisements index show
Resourceful routing:
Rails.application.routes.draw do
resources :advertisement
resources :posts
get 'about' => 'welcome#about'
root to: 'welcome#index'
end
Index and show actions:
class AdvertisementController < ApplicationController
def index
#advertisements = Advertisement.all
end
def show
#advertisements = Advertisement.find(params[:id])
end
end
Index view:
<h1>Advertisements</h1>
<% #advertisements.each do |advertisement| %>
<% end %>
Show view:
<h1><%= #advertisements.title %></h1>
<p><%= #advertisements.copy %></p>
<p><%= #advertisements.price %></p>
When I go to localhost:3000/advertisement I see a blank page page with "Advertisements" at the top. When I go to to localhost:3000/advertisement/index I see an error message:
Couldn't find Advertisement with 'id'=index
def show
#advertisements = Advertisement.find(params[:id]) #second line highlighted in red
end
end
Why I can't I view an individual advertisement or see an index of all advertisements like I'm supposed to?
You are seeing an index of all the Advertisements when you see the page with just Advertisements at the top, it isn't repeating & printing any because none exist.
Not being able to see just one is because the route defaults to
localhost:3000/advertisements/:id
So it's trying to lookup an Advertisement with the :id of "index", which most likely doesn't exist.
Try to create an advertisement
You don't appear to be outputting anything for each advertisement in your index view and are just looping through the advertisements:
<h1>Advertisements</h1>
<% #advertisements.each do |advertisement| %>
<% end %>
Inside the loop, you will need to add mark up and ERB that you wish to see for each advertisement.
You can seed your database or create advertisements through the Rails console to test. As Alfred implies, you should be able to see id=1 at:
localhost:3000/advertisements/1
Minor point, for show action and view, #advertisement (singular) makes more sense semantically (you are showing a single advertisement).
You will find that rails is your friend when creating the Routes, Controllers and views you are looking for. Here is the simpliest way to solve you problem:
In terminal cd into your rails program and then:
rails g scaffold advertisement title:string, copy:text, price:integer
press enter and then rake db:migrate.
When you go to localhost:3000/advertisements you on on the index for advertisements localhost:3000/advertisements/[some :id] is the show page for a particular advertisement
additionally in your index view you have to use the block variable you are creating which in this case is advertisement to call the attributes you want on the page:
<%#advertisements.each do |advertisement|%>
(Which is all the advertisement being looped)
<%= advertisement.title %>
<%= advertisement.copy %>
<%= advertisement.price %>
<% end%>
And you will see a display of individual advertisements with their respective elements
You forgot to Seed some Data.
In you file: db/seeds.rb add:
# Create Advertisements
10.times do
Advertisement.create!(
title: Faker::Internet.domain_name,
copy: Faker::Company.catch_phrase,
price: 0
)
end
Then enter this to your terminal: rake db:seed
In case you didn't do it, rake db:migrate
It should be working now.

Resources