best strategy to moderate uploaded images? - ruby-on-rails

I'm trying to finalize my image moderation code,
I have just a simple assets model with a column moderated.
Images will not be shown unless the moderated flag is set to true ( boolean )
In addition to this I have the idea to store a is_moderated (integer) inside User model and store a value there like
0 = not moderated
1 = moderated and inappropriate image
2 = moderated and correct image
Then in application controller I do something like, in before filter:
def is_moderated
if user_signed_in?
#moderate = Moderate.find(current_user) rescue nil
if #user.is_moderated == "2"
render :template => "shared/moderated_bad_images"
end
end
end
end

How about you are only allowed to upload 1 image initially.
Then after that image is deemed appropriate you can add more.

Related

Ruby on Rails - Undefined methods for NilClass

I'm creating a picture-rating app where users can click on pictures and rate them on a scale from 1 to 5. I'm trying to calculate the average rating of a picture. Before when users clicked on a rating value, that value became the picture's rating.
Rating: 5
If a user clicked on 1, the rating would change to 1
Rating: 1
When reality, the rating should have been 3.
(5 + 1) / 2
=> 3
Here's what I've accomplished so far in implementing this feature.
I added a migration to create two new columns for my Pictures Table
rails g migration AddRatingsToPictures ratings_count: integer, rating_total: integer
Both the new attributes, ratings_count and rating_total are integer types, meaning they are assigned a nil value at default.
p = Picture.first
p.attribute_names
=> ['id', 'title', 'category', 'stars', 'updated_at', 'created_at',
'ratings_count', 'rating_total']
p.ratings_count
=> nil
p.rating_total
=> nil
My only problem is the NilClass Error.
Here is my update method in my PicturesController.
def update
#picture = Picture.find(params[:id])
#picture.ratings_count = 0 if #picture.stars.nil?
#picture.rating_total = #picture.stars
#picture.rating_total += #picture.stars if #picture.stars_changed?
#picture.ratings_count += 1 if #picture.rating_total_changed?
if #picture.update_attributes(picture_params)
unless current_user.pictures.include?(#picture)
#picture = Picture.find(params[:id])
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
Here is my picture_param method in my PicturesController
def picture_params
params.require(:picture).permit(:title, :category, :genre, :stars)
end
Here is what the two new columns do
ratings_count: Calculates the number of times a picture has been rated
rating_total: Calculates the sum of the stars a picture has received
In the above code, I first set the ratings_count to 0 if the picture doesn't have a rating. This means that the picture hasn't been rated yet.
I then need to initially set the rating_total to the number of stars a picture has. If a user changed the star rating, I would add those stars to the rating_total. And if the total increased, that's my cue to increase the number of ratings.
Obviously, to calculate the average, I'd do something like this.
(#picture.rating_total / #picture.ratings_count).to_f
Now, I think I have the right idea but I know why this doesn't work. When columns are created with an integer value, by default they are set to nil. This leads to a NilClass Error when I load the web page.
undefined method `/' for nil:NilClass
Here is my code in the View
<li><strong>Rating:</strong> <%= pluralize((#picture.rating_total / #picture.ratings_count), 'Star') %></li>
Ok, the main reason it is not working is because
you fetch the picture
you check the stars from the database, and the NOT the passed form-parameters
you do update_attributes, which if I am not mistaken, used to set attributes and then save the complete object, but since rails 4 only updates the passed attributes (which is what you would expect)
One small remark: keeping the rating correct is a function I would place in the model, NOT in the controller.
Furthermore, how to handle the if nil, initialise to zero I wrote a short blogpost about. In short: overrule the getter.
So I would propose the following solution. In your model write
class Picture < ActiveRecord::Base
def ratings_count
self[:ratings_count] || 0
end
def ratings_total
self[:ratings_total] || 0
end
def add_rating(rating)
return if rating.nil? || rating == 0
self.ratings_count += 1
self.ratings_total += rating
self.stars = self.ratings_total.to_f / self.ratings_count
self.save
end
def rating
return 0 if self.ratings_count == 0
self.ratings_total.to_f / self.ratings_count
end
and then the code in your controller becomes much cleaner and readable:
def update
#picture = Picture.find(params[:id])
stars = picture_params.delete(:stars)
if #picture.update_attributes(picture_params)
#picture.add_rating stars
unless current_user.pictures.include?(#picture)
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
I first delete the :stars from the parameters, because I do not want to save those, I want to use those for the add_rating. I then try to update_attributes, which will fail if there are any failing validations, and if that is ok, I will add_rating which itself will handle nil or zero correctly. Well granted: I do not know how you handle a "non-rating" (nil? zero?). It is possible a rating of zero should be added, because it will add a rating, but most UI I know do not allow to select 0 as rating, so you might want to change the zero handling.
This will handle the case of uninitialized (nil) values in your attributes...
def update
#picture = Picture.find(params[:id])
if #picture.stars_changed?
#picture.ratings_count = (#picture.ratings_count || 0) + 1
#picture.rating_total = (#picture.rating_total || 0) + ( #picture.stars || 0)
end
You don't need an array of ratings or ratings persisted to database, assuming you only count votes where the rating changes, you can accumulate the count and the total and divide the two (which is, in fact, what you're doing so I'm preaching to the converted).
Although it seems to me that if I change a picture from 5 to 1 and it only changes to 3, I'm gonna keep clicking 1 :)
You could set the default value on the migration when you created it. But no worries, you can create a new migration to change it:
# Console
rails g migration change_default_for_ratings_count_and_rating_total
# Migration Code
class ChangeDefaultForRatingsCountAndRatingTotal < ActiveRecord::Migration
def change
change_column :pictures, :ratings_count, :integer, default: 0
change_column :pictures, :rating_total, :integer, default: 0
end
end
Keep in mind that some databases don't automatically assign newly updated default values to existing column entries, so maybe you will have to iterate over every picture already created with nil values and set to 0.
Ok, an alternative...
Do an after_initialize so the fields are never, never, ever nil. Even if you're creating a new Picture object, they'll be initialized as zero. Problem will go away.
class Picture << ActiveRecord::Base
after_initialize do |picture|
picture.ratings_count ||= 0
picture.rating_total ||= 0
end
...
end

Ruby on Rails 4.0 - Retrieving a parameter from within a model

In my records index, I wish to insert an icon, of which name is calculated in the model, using parameters thanks to a parameters helper.
Retrieving parameters actually does not work.
In the BusinessRules index table, I specified an image tag:
<td><%= image_tag(business_rule.index_audit_tag, :alt => "Quality hit") %>
Which I extract from a public function in the model:
### display audit tag filename
def index_audit_tag
ratio = (1-self.bad_records / (self.all_records+1)) * 100
image_file = case ratio
when 0..60 then "red.png"
when 60..90 then "yellow-png"
# when red_threshold..yellow_threshold then red_image
# when yellow_threshold..green_threshold then yellow_image
else "green.png"
end
return image_file
end
It works fine when hard-coded, but I would like to use the red_threshold etc. parameters which are available through the parameters_helper:
def red_threshold
list_id = ParametersList.where("code=?", 'LIST_OF_DISPLAY_PARAMETERS').take!
#myparam = Parameter.where("parameters_list_id=? AND name=? AND ? BETWEEN active_from AND active_to", list_id, 'Tag 1-Green light', Time.now ).take!
#myparam.param_value.to_i
end
If I try using the parameters, I get the error:
undefined local variable or method `red_threshold'
How can I do that?
You can't call a helper method from within a model. Helpers are in the view layer of MVC and models are in thee model layer. To fix this you need to put both halves of the logic in the same layer.
If you want to keep index_audit_tag in the model layer:
In the Parameter model:
class Parameter
def self.red_threshold
list_id = ParametersList.where("code=?", 'LIST_OF_DISPLAY_PARAMETERS').take!
myparam = Parameter.where("parameters_list_id=? AND name=? AND ? BETWEEN active_from AND active_to", list_id, 'Tag 1-Green light', Time.now ).take!
myparam.param_value.to_i
end
end
(Note: You can probably improve this to do one query, but I'm not clear on your data model so I didn't try.)
And in your BusinessRule model:
def index_audit_tag
ratio = (1-self.bad_records / (self.all_records+1)) * 100
image_file = case ratio
when 0..60 then "red.png"
when 60..90 then "yellow-png"
when Parameter.red_threshold..Parameter.yellow_threshold then red_image
when Parameter.yellow_threshold..Parameter.green_threshold then yellow_image
else "green.png"
end
return image_file
end
If you want to put the icon logic in the view layer:
Many people would argue that the logic for choosing the right icon doesn't belong in the model. So another way to do this (and probably how I would do it) is to remove index_audit_tag from the model instead, and put it in a helper:
def index_audit_tag_for(business_rule)
ratio = (1-business_rule.bad_records / (business_rule.all_records+1)) * 100
image_file = case ratio
when 0..60 then "red.png"
when 60..90 then "yellow-png"
when red_threshold..yellow_threshold then red_image
when yellow_threshold..green_threshold then yellow_image
else "green.png"
end
return image_file
end
Then it will have no trouble finding the *_threshold methods which are also in the view.

howto create multiple records in a single form with same values

I am looking for a clean solution to create multiple database records from a single form, that all have the same values specified in the form. Only the ID should obviously be different.
I need this function to let the user create 100+ records at once as a kind of templating.
So ideally in the form the user can type a number for the count of records she/he would like to create with the filled in values.
Use an iterator. Example:
def create_many
count = params[:count].to_i
# count within reasonable limits, check if object will validate
if (1..100) === count && Object.new(params[:object]).valid?
count.times { Object.create(params[:object]) } # <= the iterator
redirect_to my_custom_view # <= custom 'show' view
else
render :text => "Couldn't do it." # <= failure message
end
end
This example expects two parameters, :object which contains resource attributes, and :count which specifies how many records to create.
You need a custom show view to handle getting and displaying all of the newly created records.

How to store the result of my algorithm?

I have an algorithm that searches through all of my sites users, finding those which share a common property with the user using the algorithm (by going to a certain page). It can find multiple users, each can have multiple shared properties. The algorithm works fine, in terms of finding the matches, but I'm having trouble working out how to store the data so that later I'll be able to use each unit of information. I need to be able to access both the found users, and each of the respective shared properties, so I can't just build a string. This is an example of the output, being run from the perspective of user 1:
user 4
sharedproperty3
sharedproperty6
user 6
sharedproperty6
sharedproperty10
shareproperty11
What do I need to do to be able to store this data, and have access to any bit of it for further manipulation? I was thinking of a hash of a hash, but I can't really wrap my head around it. I'm pretty new to programming, and Ruby in particular. Thanks for reading!
EDIT - Here's the code. I'm fully expecting this to be the most incorrect way to do this, but it's my first try so be gentle :)
So if I'm understanding you guys correctly, instead of adding the interests to a string, I should be creating an array or a hash, adding each interest as I find it, then storing each of these in an array or hash? Thanks so much for the help.
def getMatchedUsers
matched_user_html = nil
combined_properties = nil
online_user_list = User.logged_in.all
shared_interest = false
online_user_list.each do |n| # for every online user
combined_properties = nil
if n.email != current_user.email # that is not the current user
current_user.properties.each do |o| # go through all of the current users properties
n.properties.each do |p| # go through the online users properties
if p.interestname.eql?(o.interestname) # if the online users property matches the current user
shared_interest = true
if combined_properties == nil
combined_properties = o.interestname
else
combined_properties = combined_properties + ", " + o.interestname
end
end
end
if shared_interest == true
matched_user_html = n.actualname + ": " + combined_properties
end
end
end
end
return matched_user_html
render :nothing => true
end
This returns an array of hashes with all users and their corresponding sharedproperties.
class User
def find_matching_users
returning Array.new do |matching_users|
self.logged_in.each do |other_user|
next if current_user == other_user # jump if current_user
# see http://ruby-doc.org/core/classes/Array.html#M002212 for more details on the & opreator
unless (common_properties = current_user.properties & other_user.properties).empty?
matching_users << { :user => other_user, :common_properties => common_properties }
end
end
end
end
end
In your view you can do something like this:
<%- current_user.find_matching_users.each do |matching_user| -%>
<%-# you can acccess the user with matching_user[:user] -%>
<%-# you can acccess the common properties with matching_user[:common_properties] -%>
<%- end -%>
You can use a hash table with the key being the user object and the value being an array of the shared properties . This is assuming that you first need to do a lookup based on the user .
Something like this :
#user_results = { user1 => [sharedproperty3,sharedproperty7] , user2 => [sharedproperty10,sharedproperty11,sharedproperty12]}
You can then acces the values like :
#user_results[user1]
or you can also iterate over all the keys using #user_results.keys

Rails Dynamic Range Validations

How can I validate a number within a range dynamically using existing data?
For example - I have certain discounts on bulk ordering of products. If a customer buys 10-50 units they get X off and if they order 51-200 units Y off.
How can I validate this so that users can't put in quantity discounts over the same range?
I don't quite understand your question but I'm sure a custom validation would be one way to solve whatever you are trying to achieve. Simply add a validate method in your model like so:
def validate
self.errors.add(:amount, "is out of range") unless self.amount_in_allowed_range
end
private
def amount_in_allowed_range
# logic to return true or false
end
If I understand your question correctly then you are trying to avoid the creation of a discount range that overlaps an already existing one. The following code should do this for you
class QtyDiscount < ActiveRecord::Base
def validate
self.errors.add(:amount, "overlaps an existing range")
unless self.amount_in_allowed_range
end
def amount_in_allowed_range
# Check for overlapping ranges where our record either
# - overlaps the start of another
# - or overlaps the end of another
conditions = "
id != :id AND (
( min_value BETWEEN :min_value AND :max_value) OR
( max_value BETWEEN :min_value AND :max_value))"
puts "Conditions #{conditions}"
overlaps = QtyDiscount.find(:all, :conditions =>
[ conditions, { :id => self.id.nil? ? 0 : self.id,
:min_value => self.min_value,
:max_value => self.max_value} ])
overlaps.size == 0
end
end
EDITED
Removed an extraneous condition and added some checking for self.id to ensure we are not getting a false negative from our own record

Resources