I have the following code in my Rails 3 applications controller:
def like
#suggestion = Suggestion.find(params[:id])
#suggestion.voteip = request.env['REMOTE_ADDR']
#suggestion.update_attribute(:votes, #suggestion.votes + 1)
redirect_to suggestions_url
end
def dislike
#suggestion = Suggestion.find(params[:id])
#suggestion.voteip = request.env['REMOTE_ADDR']
#suggestion.update_attribute(:votes, #suggestion.votes - 1)
redirect_to suggestions_url
end
As you can see the code increments/decrements the vote integer by 1 and adds the users IP address to the column called voteip.
What I am trying to achieve is a simple block on votes from the same IP twice in a row. So if for example my IP was 123.123.123.123 and I voted on something, I couldn't then vote on the same suggestion again (either up or down) from the same IP address.
This is a really simple and nowhere near foolproof method of rate-limiting votes. Although, in the environment it's going to be used in it's almost perfect!
Once I've got this working I'm planning on adding another column for the vote timestamp, then I can do things like only allowing voting from the same IP after 5 minutes have passed.
Any advice would be appreciated!
One way to do it is to find the last vote which has the ip of the REMOTE_ADDR.
Add this in your controller.
def like
#suggestion = Suggestion.find(params[:id])
remote_addr = request.env['REMOTE_ADDR']
#last_vote = Suggestion.find_ip(remote_addr).last
if #last_vote.created_at < 2.minutes.ago
render :text => "get lost"
else
#suggestion.voteip = remote_addr
#suggestion.update_attribute(:votes, #suggestion.votes + 1)
redirect_to suggestions_url
end
end
And add this in your Suggestion model
def self.find_ip(ip)
where('voteip = ?', "#{ip}")
end
I created a quick app and tested it so it does work. Of course you can change the 2.minutes.ago to whatever time frame you want.
Hope this helps!
A vote is really a separate resource. Especially if you want to implement a more robust system in the future you'll want your votes to be a separate table, through a has_many relationship. This way it is super easy to compare when the last vote by a particular ip occurred (used as an index, or if the user is authenticated possibly the user_id). Also this allows you to create voting histories for IP's/Users.
User/ip has many suggestions which has many votes. Just my two cents.
Related
I've created my REST API based on Michael Scott from the Office, so far if you go onto the main part of the API it displays all the quotes /api/v1/quotes/ and you can get individual ones if you add an id number such as /api/v1/quotes/5,but I want users to be able to get a random quote when they put in a GET request.
This would preferably be done with an extra part of the URL such as /api/v1/quotes/random.
I've looked at a lot online but I can't figure this out. Do I put my code into the quotes controller and create another function or do I have to put this somewhere else such as routes.db? Also do I put in the SQL function of RANDOM or is there a better and more efficient way to randomise it. My database only has 50 records in it and it's done in mysql2.
Thanks in advance.
It's my first time posting here as usually I hate asking for help as I think I can always figure it out myself but I'm extremely new to Ruby so I'm fairly clueless on how to solve this. If you need me to clarify my question then just let me know.
You can do that in Model as well as controller.
Model:
class Model < ApplicationRecord
def self.random
Model.limit(1).order("RANDOM()").first
end
end
Controller: In method show
def show
render json: Model.find_by(id: params[:id]) || Model.random
end
I hope that helpful.
I would configure a quotes/random collection route:
# in config/routes.rb
resources :quote do
get 'random', on: :collection
end
and use a controller method like this:
# in controllers/quotes_controller.rb
def random
#quote = Quote.order('RAND()').first
render :show
end
Please note: Returning a single random row from a table is database specific – for MySQL
ORDER BY RAND() LIMIT 1
seems to be the way to go.
I have an integer column on the Users table called rating_number. This number is going to consist of two things.
Impressions on page views
The total number of likes they have on their posts
So far, I have the impression part taken care of. I'm using the gem is_impressionable with a counter_cache like so on my User model:
is_impressionable :counter_cache => true, :column_name => :rating_number, :unique => :all
Now, I'm trying to add to that column the second part, which is the total number of votes they have on their posts. I am getting that integer by:
#user = current_user # or some user
array = #user.posts.map { |post| post.votes.count }
count = array.inject { |sum, x| sum + x }
where count is the total number of votes they have on their posts. How can I automatically update the rating_number column in an efficient way every time a User get's one of their posts voted_on. Should I instead go the direction where I manually add 1 to that column in the post's def vote action after the vote has successfully been saved?
Not sure if this is useful, but I'm also using the thumbs_up gem for voting system.
Lookig at the your need, I am quite sure you need to use callback called after_update in your User model. To understand how call back works, read Callbacks. But I would suggest you to keep the data in 2 separate columns, rather than a single column.
class User < ActiveRecord::Base
after_update :vote_update
# other methods
def vote_update
user = #post.user
user.rating_number = user.rating_number + 1
user.save!
end
end
I'm not sure how to even research this question so maybe some awesome rails developer can point me in the right direction.
I have a model that's holding a question and correct answer. On the show view, I want the user to enter their answer into an input field and upon pressing submit, their answer is compared to the one held in the model. I don't need to save their answer.
Thoughts?
You could use a non ActiveRecord model for that. Something like this:
class UserAnswer # note that this class doesn't inherit from ActiveRecord::Base
attr_accessor :question_id, :answer
def initialize(params)
#question_id = params[:question_id]
#answer = params[:answer]
end
def correct?
q = QuestionAnswerModel.find(self.question_id)
q.answer == self.answer
end
end
Then in your controller you can do something like this:
user_answer = UserAnswer.new(params) # params contains :question_id and :answer
user_answer.correct? # returns true or false
A simple way is to save the answer confirmation only if it is equal to the answer.
Model:
question
answer
answer_confirmation
Then proceed to make the form as you normally would.
In the model add
validate :check_answer
def check_answer
errors.add(:answer, "Must be the same as answer confirmation") if answer!= answer_confirmation
end
I'm stuck on the last exercise in the Hartlt book 12.3.4 where you define the status feed for yourself and the users you are following. I'm a Rails newbie so just let me know if I need to post additional information.
When I tail the development log I can see will_paginate fire the SQL to gather the initial records, and the initial page looks fine when it is served up. When I click a link to go to any another page, it appears will_paginate doesn't fire the SQL to get retrieve more data from the database as the next page is server up fine, but there is no data.
There are also no new entries in the development log and maybe I'm wrong, but I think this indicates will_paginate didn't hit the database.
I tried to include all the relevant code snippits below. Happy to send anything that's missing.
Here is my pages_controller.rb
def home
#title = "Home"
if signed_in?
#micropost = Micropost.new
#feed_items = current_user.feed.paginate(:page => params[:page])
end
end
Here is my user.rb
def feed
Micropost.from_users_followed_by(self)
end
Here is my microposts.rb
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
def self.followed_by(user)
following_ids = %(SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
where("user_id IN (#{following_ids}) OR user_id = :user_id",
:user_id => user)
end
NOTE: In the video tutorial (chapter 12, time = 02:06:00) and the book (page 517, listing 12.44) Harlt uses the variable "followed_ids" instead of "following_ids". In the virgin sample code you can download from his site the variable name is "following_ids", and I have tried it both ways - but it fails.
Bottom line - the dev log shows will_paginate retrieving the first batch of data, but it never goes back to the database for additional data.
Can anyone suggest what I can take a look at to resolve my problem?
Many thanks.
do you have more records than will fit on one page?
If not, it may be that will_paginate is doing a count first.
Then deciding not to load any records.
Get into the console and try it out.
$ rails console
user = User.find(23)
# the same user you're logged in as
user.feed.count
# is it more than one page?
user.feed.paginate(:page => 1)
# 20 records?
user.feed.paginate(:page => 2)
# nothing?
Also:
in your example scope :from_users_followed_by, lambda { |user| followed_by(user) } is redundant.
You may as well just say;
def feed
Micropost.followed_by(self)
end
so i want to make a page the displays a Phrase by select (initially) at random from the database. on that page i want a <%= link_to "next"%> but i was wondering if there was an efficient way to ensure that the next record exists
currently I'm using just
# #phrase is current phrase
<%= link_to "next", phrase_path( Phrase.find( #phrase.id + 1 ) ) %>
yes, i know i should call a #next from the controller, or better yet have a next method in the model to call #phrase.next, but this is for illustrative purposes.
but this often turns up an ActiveRecord::RecordNotFound error because some phrases have been deleted from the db (due to moderation, error, etc...). I could rescue from this and loop that till it works in the controller then pass it or something, but that seems like a bad solution, and not particularly 'railsy'
is there a convenient solution to this anyone has found
figured it out
based on this link which is a little outdated, uses named_scope from back in rails 2. I first rewrote it using the new rails 3 scope style, but then just changed it to a method. just used
def next
Phrase.where("id > ?", self.id).order("id ASC").first
end
def previous
Phrase.where("id < ?", self.id).order("id DESC").first
end
Try creating a next/previous scope on your model, as suggested in http://steve.dynedge.co.uk/2010/01/13/random-previous-and-next-entries-from-active-record-models-using-offset/
This will allow you to do something like:
Phrase.next(5) or Phrase.next(#phrase.id)
Why don't you create a method in the controller called next and pass in the current record id. It would be trivial from there to redirect the user back to the show page for that next resource.
If you are deadset on creating the link in advance, look into creating a helper method to find the next record that exists and make it available in your views. Then you could call that whenever you needed the id of the next available record.
Something like will_paginate might be of help too. I know your page size is just one, but the essence of what you're doing is pagination.