How to use ahoy to track views of posts? - ruby-on-rails

I have multiple posts and I just want to track all the views (including people without accounts) of the posts (so the page itself). I have two methods that I have tried:
1st method
I have added this ahoy.track "Viewed Post", title: #post.id in my controller and <%= Ahoy::Event.joins(:visit).where(name: "Viewed Post").uniq.count("visits.visitor_id") %> to my view.```. The only problem is that it is displaying 0 and not changing.
2nd method
Added visitable to my post model. Ran a migration to add visit id to posts. Also added <%= Post.joins(:visit).distinct.count(:visit_id) %> to my view. The only problem is that the view count is stuck at 1 and it is the same for all the posts.
What am I doing wrong?

I struggled with this for a while aswell. I was able to mostly get it to work.
Your second method won't do what you want because it is tracking something else. Instead of tracking how many views the post has, it tracks 1 single view. It tracks the view that was used to create the post. It does this by attaching the view to the model.
Your first method is close to what you want. However all the events you store arent actually storing the visit id in them, as events do not do this by default. You need to add the visit_id yourself, usually into the properties variable of the Event.
Here is you would need to do:
First you would place the tracking code in the controller(most likely in the "show" portion):
if not Ahoy::Event.where(name: "Post:#{#post.id}", properties: current_visit.visit_token).exists?
ahoy.track "Post:#{#post.id}", current_visit.visit_token
end
By placing the post id in the name, as well as the text "Post:" it will let it track only views to the Post controller, with the specific id. visit_tokens are unique to a user, and the expire after a given configured time(default 8 hours) so it will only track repeat users if they view the page after the configured time
Next to read the view count you can place something like this in the controller wherever you want to see views(in show, edit, etc):
#views = Ahoy::Event.where(name: "Post:#{#post.id}").count
And then in your views you can just use #views
Note: You aren't supposed to set :properties as a single value, but rather its supposed to hold a hash. However I was not able to figure out how to get it to work.

Related

Render a form in a modal(bootstrap) and make it available on another controller's view

It's been a while since I've used Rails and I think I've gotten a little rusty. Is there a way to do this?
I'm trying to make a messaging feature that allows one user type to message another. I want the button to display on the User index page and the user show page. When the button is clicked a modal will popup with a form contained therein.
Currently I've made a Message model with three columns: user_type1_id, user_type2_id and message_body.
Should I make a distinct controller for this new model? Or should I put the logic in the controller of user_type1 (the usertype that will be messaged)?
Any other suggestions would be welcome.
Controllers are there primarily to get data from the database and get it ready for the views. So if you have user#index and user#show pages, then you should use the UsersController for all the logic associated with those views, even though it uses other modals. It really is the "Rails Way". If, however, you were to create a message#index page, then you should create the associated MessagesController.
Also, there is nothing wrong with creating a partial and sticking in the messages view directory (the filename would be, say, messages/_form.html.erb). Then, whenever you needed that form (throughout the entire site), all you would need to do was type:
<%= render 'messages/form' %>

Rails 4 new form defaults from params

I am using form_for in the _form.html.erb view in order to create my form for both the edit and new actions, as per a standard scaffold.
I have a model Owner which has_many pets.
I would like to put an html link on my views/owners/show.html.erb to create a new pet for said owner. This link will point to the new action of pets_controller.rb which when accessed will render the view in pets/new.html.erb
What I want to happen is for the the owner_id to be passed with the link in the url to the new action of pets_controller.rb and then be used as the default for a collection_select in pets/new.html.erb
So I have a link to create a new pet but because that link was on a specific owner page, I want the form to create a new pet to have that owner already set, so the user does not have to select from the list.
This has to be done without changing the behaviour of the edit action/view in pets.
I know that I can pass GET arguments then access them in the controller via params, then create variables in the action which are passed to the view. I can then manually check for a default and set it in the view. I do not need assistance in coding if this is the only solution.
Is there is a better way to do this? A format with which I can pass the params such that the view will just pick them up? Without manually editing my controllers and views?
While my personal inclination would be to do as you said and pass a parameter in the link helper and then access the params array in the pets view, you may find that this is the perfect opportunity to explore Nested Resources. Essentially, you could declare owners/:owner_id/pets/:pet_id route with:
resources :owners do
resources :pets
end
You could then link to this route, and reference :owner_id without having to append the query string to the URI (making somewhat cleaner for reuse).
This is likely more work for you, but also potentially more extensible (and certainly more inline with the Rails way of doing things).
REVISION
Added the following regarding link helpers to the comments, but wanted to reflect it in the answer as well.
To show a pet should be:
<%= link_to owner_pet_path( owner_variable, pet_variable) %>
To view pets' index index should be:
<%= link_to owner_pet_path( owner_variable ) %>
The answer given to this question is fantastic.
As #ConnorCMcKee suggests it would be wise to consider nesting your routes. However, if you are a beginner as myself I found that it helped my learning to simply nest my second controller into the first (i.e. nest PetsController into OwnersController) as a first step. Then afterwards I would continue with the routes.
The method would be something like:
1./ In owners/index.html.erb:
Links to PetsController index action
The key to make this work is to send the :owner_id in your link parameters. Then that Pets index action will have access to that :owner_id and know which :owner_id called it.
2./ In PetsController you would then be able to find that Owner using that id, like so:
params[:owner_id]
Then your actions can start to take advantage of knowing what Owner called them. Remember though that all your redirects inside your PetsController need to preserve params[:owner_id]. That is because once you are inside that nested structure you have to maintain it and stay inside it and always know which :owner_id you are working with.

Copy a dynamic page in a view

My knowledge of Rails is pretty basic, but I have to fix a problem in a Rails project and the programmer can not be reached. So I'm trying to fix it myself, but I'm stuck.
The project revolves around user being able to add pictures to competitions, and to be able to vote on those pictures. The plan was to have to voting on the same page as the images, but this gives a few bugs in the JS and slow performance. So I want to have the voting and the overview on two different pages.
The problem is that I can't figure out how to create another page inside the views > competitions folder and link it up with the rest of the project. The easiest solution for me would be to copy the show.html.haml and paste it like votepage.html.haml but obviously that isn't so easy.
in the view > competitions folder there's an index.html.haml file, this displays a list of current competitions and gives a admin the ability to remove, add or edit certain competitions. When a user clicks on a link to a competition this gets rendered in the show.html.haml. On this page all the images that have been uploaded in that competition are shown. On that page I want a link that refers to the voting section. When a user clicks that link it should go to the votepage.html.haml (which is 100% the same as the show.html.haml but with different styling and added javascript). For now there's no need to actually make the voting work, "faking" it through front-end is good enough.
TLDR: I want to copy/paste a page in a view, but I don't know how to hook it up to the project.
Update1. I've used the console command rails generate controller competitions votepage which created a votepage for me. I could visit this one as well on http://localhost:3000/competitions/votepage
With the following code
- #competitions.each do |competition|
#container.js-masonry
.painting.item
= link_to competition do
- competition.pictures.shuffle.each do |picture|
= image_tag(picture.image_url)
I can insert images from the competitions in the page. But the problem is that I gets images from all competitions. And not so much competitions/1 , competitions/2 etc.
What you're missing is updating the routes file so that Rails knows what you want
Views:
competitions/
show.html.haml
vote.html.haml
...
Routes:
resources :competitions do
get :vote, on: :member
end
Member makes it behave like the show action, requiring a format like competitions/:id/vote
Edit:
You want to do the routes like above, but in the controller, make sure you get the competition from the id that will get passed
Controller:
def vote
#competition = Competition.find(params[:id])
end
And then instead of looping through all the competitions, you can just take the loop out and reference #competition
The basic answer is that you also need to copy the show method from app/controllers/competitions and make a votepage method with the contents in the same file.
This guide will help explain how views get wired (by the controller) to models: http://guides.rubyonrails.org/action_controller_overview.html

Ruby on Rails - Adding a second (extremely) simple Auth layer in app

Once a user logs into their account, they are presented with a list of 'Employees'.
As of right now, when you click an employee, it takes the user to the 'show' page of that specific employee, however I want to add a 'pin-protected' aspect to that list before it renders the show page.
I want to add a simple layer of authentication that would go like this:
When a user clicks their name on a list, a text-field appears that asks for the selected employee's pin.
The user types in the pin. On submit, it compares the inputted pin against the 'pin' column for that employees' record. If it's correct it grants access to the selected employee's show page.
Is this something that is easily done in RoR? This is the first real app I have worked on, so I am having trouble wrapping my mind around a couple concepts like these.
Thanks so much!
Take a look at devise, it's most definitely your best bet for Ruby on Rails 3 authentication layer.
You're best bet if you just want to add a little functionality to your existing model class would be to add a method along the lines of:
def validate_pin(pin_to_check)
self.pin == pin_to_check
end
And then you just need to modify your employee controller so that show method checks to see if the pin has been provided (ideally via a session variable), otherwise redirect and request the pin with an additional method and route Employee#request_pin in the controller which asks the user to enter the pin, on success redirecting to the Employee#show route.
Session handling in the controller
To write the session variable, you'd need an Employee#check_pin method (as a POST route) and you'd just use the code:
session[:pin_valid] = true
Then you'd check session[:pin_valid] in your Employee#show method

custom action for a nested resource without adding to routes

This is probably really simple but I have a nested resource lets say:
map. resources :book, :has_many => :pages
I write an action called "turn" that increases page.count by 1. How do I call this action using a link_to? Thanks a lot in advance.
It's hard to tell where your page.count comes in. In Railish, you would find pages.count (note the 's'). Further, count (and also size) is a read-only attribute on arrays and hashes et.al. provided by ruby that returns the number of elements. You don't set count.
Next, I'm not sure where your turn action is supposed to live, on the Book or the Page? And what is supposed to happen after it does what it does? Finally, a route is what makes an action an action -- without it, it's just a function.
For a moment, we'll assume you are trying to store the number of times a Page in a Book has been visited. It would be a better idea to have an instance variable called reads or times_viewed etc. in your Page model. Assuming your Book model is using restful routing, in Book's show action, you create an instance variable of the Page model being viewed and increment its reads attribute before rendering the view.
If you are trying to make a link sort of how 'Like' works in Facebook, meaning you want to update a record in a table without sending the user to a different page, you'll need to use link_to_remote* and some javascript. In that case, I'd just refer you to the Railscasts on that subject.
*I think as of Rails 3, link_to_remote became just link_to with :remote => true.

Resources