Query fetches wrong value and NilClass in rails controller - ruby-on-rails

Hi in the following code although the where query inside create method i.e. variable #count_of_fav_texts_present fetches 1 record in the rails console, but in controller the value of #count_of_fav_texts_present is zero and going inside the first if clause.
Also in the internal if clause the query for find_by i.e variable var_fav_text is giving NilClass. Although when I check it in console the value is not Nil, and it has one record.
I am very new to Rails and I am not sure what mistake I am making. Please help.
class NewfavoriteTextsController < ApplicationController
before_action :set_text
before_action :set_favgroup
before_action :authenticate_user!
def create
#count_of_fav_texts_present = Favorite.where(favorited_id: #text_id, user_id: current_user.id).count
if #count_of_fav_texts_present == 0
if Favorite.create(favorited: #text, user: current_user)
if Newfavorite.create(favorite_group_id: #fav_group, newfavorited: #text)
var_fav_text = Favorite.find_by(favorited_id: #text_id, user_id: current_user.id)
cnt_of_var = var_fav_text.counter
var_fav_text.counter = cnt_of_var + 1
var_fav_text.save
else
# do something
end
else
# do something
end
else
# for condition when var is greater than 0
if Newfavorite.create(favorite_group_id: #fav_group, newfavorited: #text)
var_fav_text = Favorite.find_by(favorited_id: #text_id, user_id: current_user.id)
cnt_of_var = var_fav_text.counter
var_fav_text.counter = cnt_of_var + 1
var_fav_text.save
else
# do something
end
end
end
def destroy
# do something
end
private
def set_text
#text = Text.find(params[:text_id] || params[:id])
end
def set_favgroup
#fav_group = params[:fav_group_id]
end
end
Thanks in advance.

Related

Trigger rails controller function - Paypal Website Standard IPN

I've got a Paypal IPN that comes into a PaymentNotificationsController in my app. However, some variables depend on the number of items in a cart, so i want to extract them before creating the PaymentNotification.
So far, i've got:
class PaymentNotificationsController < ApplicationController
protect_from_forgery except: [:create]
def create
PaymentNotification.create!(params: params,
item_number: params[:item_number], item_name: params[:item_name], quantity: params[:quantity]
render nothing: true
end
end
However, when the notification comes from PayPal, it comes in the form of item_name1, item_number1, quantity1, item_name2, item_number2, quantity2 and so on.
Even if its just one item, it would come as item_name1, item_number1, quantity1, option1 and so on.
I have this function to try and extract the variables, but i don't know how to trigger the function. I tried using a before_action at the top of the controller but it didn't work. Returned wrong number of arguments(0 for 1):
ITEM_PARAM_PREFIXES = ["item_name", "item_number", "quantity"]
def extract_ipn_items_params(params)
item_params = []
loop do
item_num_to_test = item_params.length + 1
item_num_suffix = item_num_to_test.to_s
possible_param_name = ITEM_PARAM_PREFIXES[0] + item_num_suffix
if params.include?(possible_param_name)
this_item_params = {}
ITEM_PARAM_PREFIXES.each do |prefix|
this_item_params[prefix] = params[prefix + item_num_suffix]
end
item_params.push this_item_params
else
return item_params
end
end
end
So i'm asking, how do i trigger the function to extract the variables and put them into params[:item_number], params[:item_name], params[:quantity] for each item in the cart so if there are two items, two separate PaymentNotifications would be created?
Note: Both methods are in the same PaymentNotificationsController.
Any help would be appreciated. Thanks in advance!
I assume your method extract_ipn_items_params already fetches the data you require, you can remove the params argument to the method, as the params is always available in the actions/methods of the controller.
ITEM_PARAM_PREFIXES = ["item_name", "item_number", "quantity"]
def extract_ipn_items_params
mod_params = Hash.new{|k, v| k[v] = {} }
ITEM_PARAM_PREFIXES.each do |item_data_key|
key_tracker = 1
loop do
current_key = (item_data_key + key_tracker.to_s).to_sym
if params.include? current_key
mod_params[key_tracker][item_data_key] = params[current_key]
else
break
end
key_tracker += 1
end
end
mod_params
end
The method returns a hash of hashes like:
{1 => {item_name: 'Item 1', item_number: 1084, quantity: 15}}, if you have nested attributes set up for a user, I think you should be able to do something like, not really sure, but should be possible:
user.update(payment_notifications_attributes: extract_ipn_items_params)
Let me know if that works for you.
UPDATE
Based on the Github Gist, here's something I was able to come up with:
class PaymentNotificationsController < ApplicationController
protect_from_forgery except: [:create]
ITEM_PARAM_PREFIXES = ["item_name", "item_number", "quantity", "option_name"]
def create
extract_ipn_items_params.each do |key, values|
# this approach loops through all the returned results, nested attributes may help abstract this though
PaymentNotification.create(values)
render nothing: true
end
def details
# params.extract_ipn_items_params #this doesn't exist as params is an instance of ActionController::Parameters
PaymentNotification.update_attributes(line_item_id: params[:item_number], product_title: params[:item_name], option_name: params[:option_name], quantity: params[:quantity])
end
private
def additional_attributes
# create this for additional merge attributes. A better place for these would be the parent of this
{
params: params,
cart_id: params[:invoice],
status: params[:payment_status],
transaction_id: params[:txn_id],
first_name: params[:first_name],
last_name: params[:last_name],
email: params[:payer_email],
address_name: params[:address_name],
address_street: params[:address_street],
address_city: params[:address_city],
address_state: params[:address_state],
address_zip: params[:address_zip],
address_country: params[:address_country]
}
end
def extract_ipn_items_params
mod_params = Hash.new{|k, v| k[v] = {}.merge(additional_attributes) }
ITEM_PARAM_PREFIXES.each do |item_data_key|
key_tracker = 1
loop do
current_key = (item_data_key + key_tracker.to_s).to_sym
if params.include? current_key
mod_params[key_tracker][item_data_key] = params[current_key]
else
break
end
key_tracker += 1
end
end
mod_params
end
end
Let me know if that fixes your problem.
You should have payment_id so you can find it by using gem 'paypal-sdk-rest'
payment = PayPal::SDK::REST::Payment.find payment_id
then you could see all details in payment object

undefined method `<<' for #<Answer::ActiveRecord_Relation:0x007fada31c7430>

Hi I create a controller Game to display a Q/A game
And I am blocked with <<, here is the code
def play
lvlup(lvl)
if lvl == 1
set_questions
else
get_questions
end
#answers = Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()")
#answer ||= []
#answers << question.answer
#answers = #answers.shuffle
render 'play'
end
I create an array and I put the related answer in the global answers I want to display 4 Max.
Why does the undefined is here?
Here is the total code
class GamesController < ApplicationController
attr_accessor :lvl
def welcome
end
def congrat
end
def play
lvlup(lvl)
if lvl == 1
set_questions
else
get_questions
end
#answers = Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()")
#answer ||= []
#answers << question.answer
#answers = #answers.shuffle
render 'play'
end
def loose
#question = Question.find(params[:question])
flash.now[:alert] = "Miss..."
render 'loose'
end
def check
#lvl = params[:lvl].to_i
answer_id = params[:id].to_i
question = Question.find(params[:question])
if #lvl == lvlmax
render action: 'congrat' and return
elsif answer_id == question.answer_id
flash.now[:notice] = "Well done !"
play
else answer_id != question.answer_id
loose
end
end
private
def lvlup(value)
#lvl = 1 + value.to_i
end
def lvlmax
#lvlmax = Question.all.count
end
def set_questions
#questionsids = []
Question.all.shuffle.each do |d|
#questionsids << d.id
end
cookies[:questions] = #questionsids
end
def get_questions
#questions = cookies[:questions].split('&')
end
def questions
#questions = cookies[:questions]
end
def question
#question = Question.find(questions[lvl])
end
end
Thank you for your help.
You are trying to append to the #answers result - this is an ActiveRecord relation, you cannot append data to that array.
Add .to_a in the end of your line where you set #answers to allow you to append to the array.
#answers = Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()").to_a
mtrolle's answer might be correct, but I have my doubts as to why ActiveRecord::Relation was not returned as Array by default. (Also as mentioned by BroiStatse in his comment.)
I too noticed the same problem with my local setup however it was attributed to another issue all together. I am sharing this here in case you too happen to use MySQL.
Answer.where.not(id: question.answer_id).limit(2).order("RANDOM()")
returns an ActiveRecord::Relation object. And it translates to following SQL:
SELECT `answers`.* FROM `answers` WHERE (id != ID) ORDER BY RANDOM() LIMIT 2
When I try running the same in MySQL, I get:
ERROR 1305 (42000): FUNCTION database.RANDOM does not exist
Apparently MySQL does not have RANDOM() function, instead it uses RAND().
Converting ActiveRecord query accordingly returned correct Array to me:
Answer.where.not(id: question.answer_id).limit(2).order("RAND()")

One method for two models. How to pass name of model as variable to controller?

I have two methods in two different controllers (Posts & Boards). They are almost same. The difference is only model-instance-association name. To DRY this I think to write the method in module, but how to share it between Post and Board?
def init_post_comments
#user = current_user
a = #user.posts.pluck(:id) # not very nice...
b=params[:post_ids] ||= []
b = b.map(&:to_i)
follow = b - a
unfollow = a - b
follow.each do |id| # checkbox just checked
#post = Post.find_by_id(id)
if #post.users.empty?
#post.update_attribute(:new_follow, true)
end
#user.posts << #post
end
unfollow.each do |id| # if checkbox was unchecked
#post = Post.find_by_id(id)
remove_post_from_user(#post)# here we destroy association
end
if follow.size > 0
get_post_comments_data
end
redirect_to :back
end
UPDATE Ok, if I'll move the methods to model's concern how I should work with associations here? Here #user.posts.pluck(:id) and here #user.boards.pluck(:id) with what I can replace posts and boards so it can work with both of them?
So, I did it! I don't know if it's right way, but I DRY this code.
Two controllers:
posts_controller.rb
def init_comments
if Post.comments_manipulator(current_user, params[:post_ids] ||= []) > 0
#posts = Post.new_post_to_follow
code = []
#posts.each do |post|
group = post.group
code = code_constructor('API.call')
end
Post.comments_init(get_request(code), #posts)
end
redirect_to :back
end
boards_controller.rb
def init_comments
if Board.comments_manipulator(current_user, params[:board_ids] ||= []) > 0
#boards = Board.new_board_to_follow
code = []
#boards.each do |board|# подготовка запроса
group = board.group
code = code_constructor('API.call')
end
Board.comments_init(get_request(code), #boards)
end
redirect_to :back
end
As you can see they are absolutely same.
In models board.rb and post.rb - include CommentsInitializer
And in models\concerns
module CommentsInitializer
extend ActiveSupport::Concern
module ClassMethods
def comments_manipulator(user, ids)
relationship = self.name.downcase + 's'
a = user.send(relationship).pluck(:id)
b = ids.map(&:to_i)
follow = b - a
unfollow = a - b
follow.each do |id| # start to follow newly checked obj
#obj = self.find_by_id(id)
if #obj.users.empty?
#obj.update_attribute(:new_follow, true)
end
user.send(relationship) << #obj
end
unfollow.each do |id| # remove from following
#obj = self.find_by_id(id)
remove_assoc_from_user(#obj, user)#destroy relation with current user
end
follow.size
end
def comments_init(comments, objs)
i = 0
objs.each do |obj| # updating comments data
if comments[i]['count'] == 0
obj.update(new_follow: false)
else
obj.update(new_follow: false, last_comment_id: comments[i]['items'][0]['id'])
end
i += 1
end
end
def remove_assoc_from_user(obj, user)
user = user.id
if user
obj.users.delete(user)
end
end
end
My code works. If you know how to make it better please answer!

NoMethodError undefined method

Working on final class project. I need to calculate the GPA of my major credits and non major credits separately for a transcript page. When I run this code below as a controller it works fine and show my total credit hours for major and non major but when I put this code
#GPA_for_major = (course.credits * course.grade.scale) / course.credits
in the If statement I get NoMethodError in TransController#transcript
undefined method 'credits' for # Course::ActiveRecord_Relation:0x00000007b99798>
class Transcript
def initialize (course_array)
#course = course_array
#total_non_major_credits = 0
#total_major_credits = 0
#GPA_for_major = 0
#GPA_for_non_major = 0
for item in #course
if item.is_for_major
#total_major_credits = #total_major_credits + item.credits
else
#total_non_major_credits = #total_non_major_credits + item.credits
end
end
end
def course
#course
end
def total_non_major_credits
#total_non_major_credits
end
def total_major_credits
#total_major_credits
end
def GPA_for_major
#GPA_for_major
end
def GPA_for_non_major
#GPA_for_non_major
end
end
This is the Controller for my transcript page
class TransController < ApplicationController
def transcript
#courses = Course.all
#transcript =Transcript.new(#courses)
end
end
I'm not sure what else to include because this is my first post but any help will be awesome! Thanks!
#course appears to refer to an array of courses and the if statement is within a loop that iterates over the items setting a local variable item for each course. Given that, you should use item instead of course:
#GPA_for_major = (item.credits * item.grade.scale) / item.credits

How to handle multiple conditions of instance variable assignment

I have the following in my controller that will assign a different collection of results depending on what params are received with an Ajax call. It is messy and i would like to just call a function with all the logic in rather than all this in my index controller
class PublicController < ApplicationController
def index
if params[:literacy_param].present?
#skills = Skill.search(params)
elsif params[:numeracy_param].present?
#skills = Skill.numeracy_default_params
elsif params[:numeracy_number_skills].present?
#skills = Skill.numeracy_number_skills
elsif params[:numeracy_measuring_skills].present?
#skills = Skill.numeracy_measuring_skills
elsif params[:numeracy_data_skills].present?
#skills = Skill.numeracy_data_skills
else
#skills = Skill.default_params
end
end
end
Im just a bit unsure on how to set out my function so that it can read the params that are being sent,
I have come up with this so far
private
def skills(params)
if params[:literacy_param].present?
#skills = Skill.search(params)
elsif params[:numeracy_param].present?
#skills = Skill.numeracy_default_params
elsif params[:numeracy_number_skills].present?
#skills = Skill.numeracy_number_skills
elsif params[:numeracy_measuring_skills].present?
#skills = Skill.numeracy_measuring_skills
elsif params[:numeracy_data_skills].present?
#skills = Skill.numeracy_data_skills
else
#skills = Skill.default_params
end
end
Then in my index action i would do
#skills = skills(params)
would this be an efficient way?
Thanks
You can do this
class PublicController < ApplicationController
def index
skills = ['literacy_param', 'numeracy_param', 'numeracy_number_skills', 'numeracy_measuring_skills', 'numeracy_data_skills']
common_in_params = (skills & params).first
#skills = common_in_params.present? ? (common_in_params.eql?('literacy_param') ? Skill.search(params) : Skill.send(common_in_params)) : Skill.default_params
end
end
You can define skills array in an initializer for resusability
One way of doing it would be this:
def skills(params)
set_of_skills = params.slice(
:numeracy_param,
:numeracy_number_skills,
:numeracy_measuring_skills,
:numeracy_data_skills,
).first
#skills = if params[:literacy_param]
Skill.search(params)
elsif set_of_skills
Skill.public_send(set_of_skills)
else
Skill.default_params
end
end
I would also advise to have this extracted into a lib/ folder, and unit-tested. So that in your controller you could perform the following:
def index
#skills = SkillSearch.new(params).search
end
Two ways I can think of doing this right now:
Wrap the params in a unique key. As in params = { :keyword => :literacy_param }, and then use this unique key to identify the right operation.
In you skill.rb:
def self.filter(params)
if params[:keyword] == :literacy_param
search(params)
elsif available_filters.include?(params[:keyword])
public_send(params[:keyword])
else
default_params
end
end
private
def self.available_filters
%i{numeracy_default_params numeracy_number_skills numeracy_measuring_skills numeracy_data_skills}
end
considering that instead of :numeracy_param, you send :numeracy_default_params in :keyword key. Otherwise you'll have to make another elsif inside filter method.
then in your index method:
def index
#skilles = Skill.filter(params)
end
You create a separate filter class, which is an expandable solution, just in case when you need to go for complex search queries and filtering.
Let's call it SkillSeacrher, inside you app/models/skill_searcher.rb:
class SkillSearcher
attr_reader :keyword
def initialize(keyword)
#keyword = keyword
end
def filter
if keyword == :literacy_param
Skill.search(params)
elsif available_filters.include?(keyword)
Skill.public_send(keyword)
else
Skill.default_params
end
end
private
def self.available_filters
%i{numeracy_default_params numeracy_number_skills numeracy_measuring_skills numeracy_data_skills}
end
end
then in index method:
def index
#skills = SkillSearcher.new(params[:keyword]).filter
end
However, you can do one more change to filter method(depends on your taste):
def filter
if keyword == :literacy_param
Skill.search(params)
else
Skill.public_send(available_filters.include?(keyword) ? keyword : :default_params)
end
end
And, if you have all these methods accepting params as arguments then it'd be much more sleek:
def filter
Skill.public_send(available_filters.include?(keyword) ? keyword : :default_params, params)
end

Resources