I'm trying to build a game where you guess numbers.
The problem is if you make a mistake it redirects you to a leaderboard(mvc) form where you enter your name plus it`s pre populated with sessions data from a different controller(game) and submits both into the DB.
#round & #points are the two variables I want to access and store as score and level.
class ApplicationController < ActionController::Base
before_filter :set_current_account
def set_current_account
# set #current_account from session data here
Game.current = #round
end
protect_from_forgery
end
-
class Leaderboard < ActiveRecord::Base
cattr_accessor :current
end
# == Schema Information
#
# Table name: leaderboards
#
# id :integer not null, primary key
# name :string(255)
# score :string(255)
# level :string(255)
# created_at :datetime
# updated_at :datetime
#
-
class GameController < ApplicationController
def index
#games = Game.all
respond_to do |format|
format.html
end
end
def start_game
session[:round] ||= 1
session[:points] ||= 0
#round = session[:round]
#points = session[:points]
end
def generate_round
numbers = Array.new(6){rand(9)}
#addition = []
#display = numbers
numbers.inject do |s, i|
#addition << s + i
#addition.last
end
end
def next_round
session[:round] += 1
session[:points] += 1200
#round = session[:round]
#points = session[:points]
end
def destroy_sessions
session[:round] = nil
session[:points] = nil
session[:addition] = nil
#round = session[:round]
#points = session[:points]
#addition = session[:addition]
start_game
end
def submit_name
#game = Game.new(params[:game])
respond_to do |format|
if #game.save
format.html { redirect_to(leaderboard_path, :notice => 'Score was added successfully.') }
else
format.html { render :action => "new" }
end
end
end
def game_over
redirect_to :controller => 'leaderboards', :action => 'new' and return
end
I haven't read the whole thing, but if you just want to access those variables, you can just pass them as parameters.
Pass those values into game_over as params
Use this to redirect
redirect_to :controller => 'leaderboards', :action => 'new' and return, :round => params[:round], :points => params[:points]
Alternatively, you can just keep the session until a new game is started or score is recorded to the leaderboard.
I think you've taken an approach here that shouldn't even work. The Rails MVC framework is structured around the principle that each request is serviced independently, and that, in theory, there is no state transfer from one request to the next except through params passed in, records stored in the database and the persistent user session.
To design a web-based application like you might a single-process, single-user, single-session program is a mistake. Using singletons, like your cattr_accessor called current will be problematic since it is both shared between requests, and not shared between different instances of Rails, of which there are typically many.
Mapping more closely to the REST standard of index, new, create, show, edit, update and destroy would help. For instance start_game should be create and destroy_sessions should probably be destroy.
It's not clear from your design if each game is shared amongst several users, or if it they are created for each user individually, so it's hard to say more about how to solve your problem.
Related
New to rails. Following a tutorial on polymorphic associations, I bump into this to set #client in create and destroy.
#client = Client.find(params[:client_id] || params[:id])
I'm normally only used to that you can only find #client = Client.find(params[:id])
so how does this work with there being two params? How does the || work?
FavoriteClientsController.rb:
class FavoriteClientsController < ApplicationController
def create
#client = Client.find(params[:client_id] || params[:id])
if Favorite.create(favorited: #client, user: current_user)
redirect_to #client, notice: 'Leverandøren er tilføjet til favoritter'
else
redirect_to #client, alert: 'Noget gik galt...*sad panda*'
end
end
def destroy
#client = Client.find(params[:client_id] || params[:id])
Favorite.where(favorited_id: #client.id, user_id: current_user.id).first.destroy
redirect_to #client, notice: 'Leverandøren er nu fjernet fra favoritter'
end
end
Full code for controller, models can be seen here
Using rails 5
Expression: params[:client_id] || params[:id] is the same as:
if params[:client_id]
params[:client_id]
else
params[:id]
end
Wow thats an incredibly bad way to do it.
A very extendable and clean pattern for doing controllers for polymorphic children is to use inheritance:
class FavoritesController < ApplicationController
def create
#favorite = #parent.favorites.new(user: current_user)
if #favorite.save
redirect_to #parent, notice: 'Leverandøren er tilføjet til favoritter'
else
redirect_to #parent, alert: 'Noget gik galt...*sad panda*'
end
end
def destroy
#favorite = #parent.favorites.find_by(user: current_user)
redirect_to #parent, notice: 'Leverandøren er nu fjernet fra favoritter'
end
private
def set_parent
parent_class.includes(:favorites).find(param_key)
end
def parent_class
# this will look up Parent if the controller is Parents::FavoritesController
self.class.name.deconstantize.singularize.constantify
end
def param_key
"#{ parent_class.naming.param_key }_id"
end
end
We then define child classes:
# app/controllers/clients/favorites_controller.rb
module Clients
class FavoritesController < ::FavoritesController; end
end
# just an example
# app/controllers/posts/favorites_controller.rb
module Posts
class FavoritesController < ::FavoritesController; end
end
You can then create the routes by using:
Rails.application.routes.draw do
# this is just a routing helper that proxies resources
def favoritable_resources(*names, **kwargs)
[*names].flatten.each do |name|
resources(name, kwargs) do
scope(module: name) do
resource :favorite, only: [:create, :destroy]
end
yield if block_given?
end
end
end
favoritable_resources :clients, :posts
end
The end result is a customizable pattern based on OOP instead of "clever" code.
The tutorial which teaches you to do
Client.find(params[:client_id] || params[:id])
is a super-duper bad tutorial :) I strongly recommend you to switch to another one.
Back to the topic: it is logical OR: if first expression is neither nil or false, return it, otherwise return second expression.
That thing is just trying to find client by client_id if there is one in the request params. If not it's trying to find client by id.
However such practic can make you much more pain than profit.
I have a Rails 4 App an Order Model where a user fills out a form for necessary data for price calculations etc. What I want is that they are prompted to sign in (with DEVISE gem) at the end of step_3 and are routed to a payment section but they can store their form data from the Order Model that the user has previously filled out, this data being the order form data before they signed in.
Main Issues:
Redirecting them after sign in mid way through process using devise
Saving the form data and associating it with the current user immediately before routed to the payment section
the Order.find_by section. What do I find the order by?
Background:
Rails 4
PostgreSQL
Devise Gem
class OrdersController < ApplicationController
before_action :authenticate_user!, :except => [:step_1,:process_step1,:step_2a,:process_step_2a, :step_2b, :step_2c, :step_3]
def step_1
#order = Order.find_by id: session[:order_id]
if #order.nil?
#order=Order.new
#order.save!
end
end
def process_step1
order = Order.find_by id: session[:order_id]
order.update_attributes(order_params)
if (order.building == 'Residential') || (order.building == 'Commercial' )
redirect_to step_2a_path
elsif (order.building == 'Commercial + Parking') || (order.building == 'Residential + Parking')
redirect_to step_2b_path
else
redirect_to step_2c_path
end
end
def step_2a
#order = Order.find_by id: params[:session_id]
end
def process_step_2a
order= Order.find_by status: 'cart'
# , user_id: current_user.id
order.update_attributes(order_params)
if order.save
redirect_to step_3_path
end
end
def step_2b
#order= Order.find_by status:'cart'
# , user_id: current_user.id
end
def process_step_2b
order= Order.find_by status: 'cart'
# , user_id: current_user.id
order.update_attributes(order_params)
if order.save
redirect_to step_3_path
end
end
def step_2c
#order= Order.find_by status:'cart'
# , user_id: current_user.id
end
def process_step_2c
order= Order.find_by status: 'cart'
order.update_attributes(order_params)
if order.save
redirect_to step_3_path
end
end
def step_3
#order= Order.find_by status:'cart'
# , user_id: current_user.id
end
def process_step_3
order= Order.find_by status: 'cart', user_id: current_user.id
order.update_attributes(order_params)
if order.save
redirect_to payment_path
end
end
def payment
#order= Order.find_by status:'cart', user_id: current_user.id
end
First off you should really refactor you routes. With that structure your application is going to get exponentially more confusing to navigate the larger it grows, and will make it practically impossible for anyone else to work on it/help you with debugging. Take a look at this guide on RESTful routing to learn more.
As for you issue, you can store their form data in one of the rails temporary storage mechanisms (I'd recommend a session variable) while they're logging in then redisplay it after they finish
There is a callback after_sign_in_path_for, you can add it in your ApplicationController. Usage is here
2,3. Continue to use session[:order_id]. Can't figure out why do you use find_by status: 'cart' and Order.find_by id: params[:session_id]. Check this url to save old session after log in.
Personally I do not recommend this method and I think it's better to bind order id to some secure random generated value stored in cookie.
I'm fairly new to rails and struggling on changing database values after the user successfully paid via stripe. Additionally after paying, it somehow redirects me everytime to '/subscriberjobs/1' which doesn't exist. Instead it should direct to the root_path of the application.
Here is what I've got:
Routes
resources :subscriberjobs
resources :jobs
Jobs Controller
def new
if current_user
#job = current_user.jobs.build
else
redirect_to new_user_session_path
end
end
def create
#job = current_user.jobs.build(job_params)
if #job.save
redirect_to '/subscriberjobs/new'
else
render 'new'
end
end
Subscriberjobs Controller (Here is what doesn't work!)
class SubscriberjobsController < ApplicationController
before_filter :authenticate_user!
def new
end
def update
token = params[stripeToken]
customer = Stripe::Customer.create(
card: token,
plan: 1004,
email: current_user.email
)
Job.is_active = true # doesn't work
Job.is_featured = false # doesn't work
Job.stripe_id = customer.id # doesn't work
Job.save # doesn't work
redirect_to root_path # doesn't work
end
end
Please tell me if you need additional information. Every answer is very appreciated. Thanks!
Send saved job id to subscriberjobs/new as a param. You can keep hidden field which will have value job_id in subscriberjobs/new html form, which will call your SubscriberjobsController#update method. There access it using params.
In JobController #create
redirect_to "/subscriberjobs/new?job_id=#{#job.id}"
In your SubScribeJob form
hidden_field_tag 'job_id', params[:job_id]
In your SubScribeJobCotroller
#job = Job.find(params[:job_id])
I'm building a StackOverflow-like clone for studying purposes. Users have this ability to vote for someone's question, bringing its score up or down. My method works fine, however the repetition and the amount of controller logic are bothering me.
User has_many Votes
Question has_many Votes
Votes belong to Question/User
routes:
concern :voteable do
post 'votes/voteup', to: 'votings#voteup', as: :voteup
post 'votes/votedown', to: 'votings#votedown', as: :votedown
end
resources :questions, concerns: [:commentable, :favoriteable, :voteable] do
...
end
votes controller
class VotingsController < ApplicationController
def voteup
#question = Question.find(params[:question_id])
unless #question.user == current_user # checks if the user is the author
if current_user.voted?(#question.id) #checks if user already voted
#vote = current_user.votes.find_by(question_id: #question)
#vote.update_attributes(score: 1)
else
#vote = Vote.create(user: current_user, question: #question, score: 1)
end
end
redirect_to :back
end
def votedown
#question = Question.find(params[:question_id])
unless #question.user == current_user
if current_user.voted?(#question.id)
#vote = current_user.votes.find_by(question_id: #question)
#vote.update_attributes(score: -1)
else
#vote = Vote.create(user: current_user, question: #question, score: -1)
end
end
redirect_to :back
end
end
voted? is a method I've extracted to my User model
def voted?(question)
true if self.votes.where(question_id: question)
end
I would like to get rid of repetition in these two methods, but how?
Should I create one method like VOTE and one route leading to it with specified params (up/down) and then assign the score based on if/else? Sounds dirty to me, but that is the only thing that comes into my mind. I'm sure there has to be a beautiful Rails-way solution to this.
The User class could hold a method called vote_for(question, score) and defined like this:
# class User
def vote_for(question, score)
vote = self.votes.where(question_id: question.id).first || Vote.new(user: self, question: question)
vote.score = score
vote.save
end
class VotingsController < ApplicationController
before_filter :set_question, only: %w(voteup votedown)
def voteup
current_user.vote_for(#question, 1)
end
def votedown
current_user.vote_for(#question, -1)
end
protected
def set_question
#question = Question.find(params[:id])
end
A little tip: You should refactor your .voted? method of the User class to this:
def voted?(question)
self.votes.exists?(question_id: question)
end
This will either return TRUE or FALSE, not a complete object retrieved from the DB and then translated into a Ruby object.
I have he following code in my update action for a Controller. The code works when in the create, but doesn't seem to kick in under update:
def update
#contact = Contact.find(params[:id])
# bug, why isn't this working?
unless #contact.fax.empty?
#contact.fax = "1" + Phony.normalize(#contact.fax)
end
unless #contact.phone.empty?
#contact.phone = "1" + Phony.normalize(#contact.phone)
end
if #contact.update_attributes(params[:contact])
flash[:notice] = "Successfully updated contact."
redirect_to #contact
else
render :action => 'edit'
end
end
these should be in your model. FAT model, SKINNY controller:
# contact.rb
...
# may need require 'phony' and include Phony
before_save :prep
def prep
self.fax = 1+Phony.normalize(self.fax) unless self.fax.empty? || (self.fax.length == 11 && self.fax[0] == 1)
self.phone = 1+Phony.normalize(self.phone) unless self.phone.empty? || (self.phone.length == 11 && self.phone[0] == 1)
end
...
Edit:
As I mentioned in my comment, it's better in terms of storage and efficiency and indexing to store as a bigint unsigned in your database and add the prettiness to the numbers in a method. This way, your site is always normalized (no two phone numbers will ever look different because they are formatted 'on the fly').
# sample methods
def phony
str = self.phone.to_s
"#{str[0..2]}-#{str[3..5]}-#{str[6..10]}"
end
# use a similar method for faxing, but I'll write
# this one differently just to show flexibility
def faxy
str = self.fax.to_s
"+1 (#{str[0..2]}) #{str[3..5]}-#{str[6..10]}"
end
you never call save on #contact in your unless blocks, so your call to #contact.update_attributes(params[:contact]) undoes any change you made in those blocks (because those keys in the params hash correspond to empty values).
def update
#contact = Contact.find(params[:id])
if #contact.update_attributes(params[:contact])
#contact.update_attributes(:fax => "1" + Phony.normalize(#contact.fax)) unless #contact.fax.empty?
#contact.update_attributes(:phone => "1" + Phony.normalize(#contact.phone)) unless #contact.phone.empty?
flash[:notice] = "Successfully updated contact."
redirect_to #contact
else
render :action => 'edit'
end
end
You could use update_attribute but that bypasses validation.
You could also use a before_save callback in the Contact class, but you would have to check if the phone or fax are already "normalized."