Using user input to query MySQL database and move to next page or display error - mysql

When accessing the first page of our site, users are prompted to enter a "coupon code" to continue to the next page. Coupons can be used multiple times, but must exist in the "code" field of the coupon_codes table.
If the user enters a code that exists in the coupon_codes table, they should automatically be redirected to the next page. If the user enters a code that does not exist in the coupon_codes table, an error should be displayed with the option of trying again.
I'm sure I'm making this more difficult than it needs to be, as I've been working on this on and off for days. I've been able to get it to be what I feel is close, but not quite there. There have been multiple variations and trials, but this is where I'm currently at.
Model (coupon_code):
def self.code(code)
if code
self.exists?(['code = ?', "#{code}"])
##move on to /design
else
##display error
end
end
View (index):
<%= form_tag coupon_code, :method => 'get' do %>
<%= text_field_tag :code, params[:code] %>
<%= submit_tag("Check my coupon", :name => nil) %>
<% end %>
Controller:
def index
#coupon_codes = CouponCode.code(params[:code])
end
The log shows the following (after entering the correct code) before re-rendering the page I'm already on:
CouponCode Exists (0.2ms) SELECT 1 AS one FROM `coupon_codes` WHERE (code = 'correct') LIMIT 1
If I use the rails console, it seems like it should work (assuming I'm using it correctly), I'm just not sure how to go about getting it to move on or display an error.
2.1.1 :001 > code = 'correct'
=> "correct"
2.1.1 :002 > CouponCode.exists?(['code = ?', "#{code}"])
CouponCode Exists (0.2ms) SELECT 1 AS one FROM `coupon_codes` WHERE (code = 'T001') LIMIT 1
D, [2017-08-17T11:08:32.730761 #6788] DEBUG -- : CouponCode Exists (0.2ms) SELECT 1 AS one FROM `coupon_codes` WHERE (code = 'correct') LIMIT 1
=> true
2.1.1 :003 > code = 'wrong'
=> "wrong"
2.1.1 :004 > CouponCode.exists?(['code = ?', "#{code}"])
CouponCode Exists (0.2ms) SELECT 1 AS one FROM `coupon_codes` WHERE (code = 'wjorea') LIMIT 1
D, [2017-08-17T11:09:10.611964 #6788] DEBUG -- : CouponCode Exists (0.2ms) SELECT 1 AS one FROM `coupon_codes` WHERE (code = 'wrong') LIMIT 1
=> false
Again, I'm sure it's something simple and I'm just over thinking it. Sorry if I gave way too much detail... I figured too much is better than not enough. Thank you in advance for any help or direction!
My current code is based off of parts of this Stack Overflow question, if that helps any.

The problem here is the model is not and should not be concerned with controller or view issues. Redirecting to another page is explicitly a controller issue and must be handled directly in the controller.
What you want to do is reduce your model's code to an advisory position, it will advise the controller on how to handle the request. It will not take direct action.
For example:
class CouponCode
def self.code_exists?(code)
self.exists?(code: code)
end
end
Then in the controller:
if (Coupon.code_exists?(params[:code]))
redirect_to(coupon_exists_path(...))
end
Given how simple your check is, though, it's not clear if having such a method in the model is of any use since the alternative is simply:
if (Coupon.exists?(code: params[:code]))
redirect_to(coupon_exists_path(...))
end
That's one character more code on the controller and three lines less on the model.

I'm just not sure how to go about getting it to move on or display an
error.
You need to put that code in the controller. Its the controller's job to handle such things. Also you don't need a model method here. To keep the things straight forward and simple, try the below
def index
if CouponCode.exists?(code: params[:code])
#code for redirect to next page
else
flash.now[:notice] = "Your error message"
end
end

Related

Update table from database using ajax in rails

I have a problem. I want update my table data from database with selected value's for select box (dropdown). And i think ajax is solution. But how can i do it? Here is my select tag in VIEW. My view is writtin in haml
%select{:name => "options", :id=>"select_other_month"}
%option{:value => "0"} Other Months
%option{:value => "5"} 5
%option{:value => "4"} 4
%option{:value => "3"} 3
%option{:value => "2"} 2
%option{:value => "1"} 1
and here is my table
.table{:id=>"admin_time_table"}
%table{:border=>"1"}
%tr
%th No
%th Name
%th Total day
%th Total time
-#i = 1
-#users.each do |u|
%tr
%td=#i
%td=u.firstname
%td=u.total_day
%td=u.total_time
-#i = #i +1
and my controller looks like
#users = User.where(:type_id=>0)
I mean when i select 4 in dropdown, table data reloaded with April data from database.
Note: My database is mysql.
Sorry for my bad English.
1) Put your select into the form with remote: true option. That will make it submit though ajax. Can also set up a javascript listener to submit it every time it changes. You should see it hitting rails server at this point and getting routing error
2) Set up the route for this action.
3) In a controller action read the select_other_month value from params
4) In a view action.js.erb (has to be js). do
$("#admin_time_table").html("<%= j render partial: 'table' %>"). Where the table partial is the replacement with data for the specified type_id
You'll need to play and tweak it to make it work with your system. But you get the idea

can't create a record in a database

I am using rails version 4.2 and ruby version 2.2.0. I am trying to save a record to lollypops table. No exceptions indicating reasons.
TASK: As soon as a member is created and saved, I want to populate the lollypops table by calling the create_lollypop(#member.id) in members controller's create method like this:
# POST /members
# POST /members.json
def create
#member = Member.create(members_params)
return unless request.post?
#member.save!
self.current_user = #member
c = Country.find(#member.country_id)
#member.update_attributes(
:country_code=>c.code)
create_lollypop(#member.id) #From here I want to create lollypop
MemberMailer.signup_notification(#member).deliver_now
redirect_to(:controller => '/admin/members', :action => 'show',
:id=> #member.id)
flash[:notice] = "Thanks for signing up! Check your email now to
confirm that your email is correct!"
rescue ActiveRecord::RecordInvalid
load_data
render :action => 'new'
end
def create_lollypop(member_id)
#member = Member.find(member_id)
Lollypop.create(
:member_id=>#member.id,
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country,
:postcode_index=>#member.postcode_index,
:name=>#member.name)
end
The 'member' is created but the 'lollypops' table is not populated. The associations are:
MEMBER model:
has_one :lollypop, :dependent=>:destroy
LOLLYPOP model
belongs_to :member
If I use generic SQL command then the lollypops table gets populated but I do not want to do that:
def self.create_lollypop(member_id)
member = Member.find(member_id)
ActiveRecord::Base.connection.execute("insert into lollypops (member_id,product_name,product_price,email,house_flat,street,city_town,country,postcode_index,name)
values(#{member.id},'lollypop',#{100},'#{member.email}','#{member.house_flat}','#{member.street}','#{member.city_town}','#{member.country_code}','#{member.postcode_index}','#{member.name}')")
end
Any advice would be welcomed. Thank you.
In your create_lollypop(), You are not defining #member.
def create_lollypop(member_id)
#member = Member.find member_id
Lollypop.create!(
:member_id=>#member.id,
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country,
:postcode_index=>#member.postcode_index,
:name=>#member.name
)
end
Also use create! so in case any validation failed then it will raise exception. So it will help you sort out issue.
For the moment try to create lollypop using the association method create_lollypop directly in your controller. use this code in you create controller method, note that create_lollypop method will fill (member_id field automatically):
#member = Member.create(members_params)
return unless request.post?
#member.save!
self.current_user = #member
c = Country.find(#member.country_id)
#member.update_attributes(
:country_code=>c.code)
#From here I want to create lollypop
#member.create_lollypop(
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country,
:postcode_index=>#member.postcode_index,
:name=>#member.name
)
MemberMailer.signup_notification(#member).deliver_now
redirect_to(:controller => '/admin/members', :action => 'show',
:id=> #member.id)
flash[:notice] = "Thanks for signing up! Check your email now to
confirm that your email is correct!"
rescue ActiveRecord::RecordInvalid
load_data
render :action => 'new'
This is not exactly an answer, more like tips and notes, it's a little long and I hope you don't mind.
return unless request.post?
This is more of a php thing not a rails thing, in rails already the routing is checking this, so you don't need to do this check inside the controller, if it isn't a post it will be routed elsewhere.
#member = Member.create(members_params)
return unless request.post?
#member.save!
Saving after creating is meaningless, because create already saves the data, if you are doing it for the sake of the bang save!, then you could use the create with bang create!, not to mention that you do the redirection check after the member's create, so if this did work, it would leave you with stray members.
c = Country.find(#member.country_id)
#member.update_attributes(:country_code=>c.code)
If you have your assocciations correctly, you don't need to save the code like this, because the member knows that this country_id belongs to a country.
So add this to the member model
class Member < ActiveRecord::Base
has_one :lollypop, dependent: :destroy
belongs_to :country
end
This way you could always call #member.country to return the country object, then the code could come from there, like #member.country.code, or you could just write a method to shorten that up
def country_code
country.code
end
this way will get the code through an extra query, but it has an advantage, if you for any reason change a country's code, you don't need to loop on all members who have that country and update their codes too, you could also shorten this up even more using #delegate
#member.save!
#member.update_attributes(:country_code=>c.code)
Here you are updating the attributes of member after saving the member, which is kinda a waste, because you are doing 2 queries for what could be done with 1 query, programmatically it is correct and it will work, but it's bad for scaling, when more users start using your app the database will be more busy and the responses will be slower.
Instead i would recommend to postpone the creation of member till you have all the data you want
#member = Member.new(members_params) # this won't save to the database yet
#memeber.code = Country.find(#member.country_id).code
#member.save
This will only do 1 query at the end when all data is ready to be saved.
redirect_to(:controller => '/admin/members', :action => 'show', :id=> #member.id)
This is ok, but you probably have a better shorter path name in your routes, something like members_admin_path, check your routes name by doing a bin/rake routes in your terminal.
redirect_to members_admin_path(id: #member)
redirect_to ...
flash[:notice] = "message"
I'm not sure this will work, because the redirection needs to be returned, but when you added the flash after it, either the redirection will happen without the flash, or the flash will be set and returned as it's the last statement, but the redirection won't happen, not sure which will happen, to fix it you can simply swap the two statements, create the flash first and then redirect, or use the more convenient way of setting the flash while redirecting, cause that's supported
redirect_to ....., notice: 'my message'
rescue ActiveRecord::RecordInvalid
load_data
render :action => 'new'
This will do the job, but it isn't conventional, people tend to use the soft save and then do an if condition on the return value, either true or false, here's a short layout
# prepare #member's data
if #member.save
# set flash and redirect
else
load_data
render :new
end
The lollypop creation
Now there's a few things about this, first you have the method in the controller, which is bad cause it shouldn't be the controller's concern, the second method the self.create_lollypop is better cause it's created on the model level, but it's a class method, then the better way is creating it as a member method, this way the member who creates the lollypop already knows the data because it's his own self, notice i don't need to call #member because i am already inside member, so simple calls like id, email will return the member's data
# inside member.rb
def create_lollypop
Lollypop.create!(
member_id: id,
product_name: 'lollypop',
product_price: 100,
email: email,
house_flat: house_flat,
street: street,
city_town: city_town,
country: country,
postcode_index: postcode_index,
name: name
)
end
if you want you can also add this as an after create callback
after_create :create_lollypop
ps: This method name will probably conflict with the ActiveRecords create_lollypop method, so maybe you should pick a different name for this method.
As Mohammad had suggested to me, I changed Lollypop.create to Lollypop.create! and
while running my code, one validation error popped up. After correcting it and
altering my code to:
Lollypop.create!(
:member_id=> #member.id,
:product_name=>'lollypop',
:product_price=>100,
:email=>#member.email,
:house_flat => #member.house_flat,
:street=>#member.street,
:city_town=>#member.city_town,
:country =>#member.country_code,
:postcode_index=>#member.postcode_index,
:name=>#member.name
)
The 'lollypops' table got populated.

.collect method get an error undefined method `collect' for nil:NilClass

I'm trying to have a drop down list but when i try it it give me
undefined method `collect' for nil:NilClass
the controller:
def existing
#courses = Course.all
end
def duplicate
course = Course.find_by_id(permitd_up[:id])
new_course = course.dup
if new_course.save
redirect_to :action => 'show'
else
redirect_to :back
end
end
the view:
<h3>Choose a Course</h3>
<%= form_for :course , url: {:action => "duplicate" , method: "post"} do |f|%>
<%= f.select :id , #courses.collect{|c| [c.id , c.name]} %>
<br><br>
<%= f.submit%>
<%end%>
You will receive the following error
undefined method `collect' for nil:NilClass
on
<%= f.select :id , #courses.collect{|c| [c.id , c.name]} %>
Only when #courses instance variable was not set in the action that rendered this particular view.
I see that #courses variable is set in the existing method. If you are using existing as an action which renders this view then your view name must be existing.html.erb.
Or if you are rendering the view from a different action then in that case you should set #courses value in that particular action by either directly setting the value within action OR by calling existing method from there.
If you have your courses as a database table, you might want to try using rails' built in field helper collection_select. It will populate your select field with all of the data available in your model. If you want a drop-down like the one you are describing, I believe using collection select is the best way to handle it.
You can read up on it here: http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html
Alternatively, if you have a ton of courses, maybe try looking into using a text field with autocomplete. Jquery UI has a plugin that makes this very easy. You can check out the railscasts for it here: http://railscasts.com/episodes/102-auto-complete-association-revised.
It requires a pro account but if you do a lot of rails developing it will be the best $9 you spend every month.
If you would like to continue to do it this way, make sure that you are defining
#courses = Courses(:all) in the correct controller action, otherwise you will have nothing to render.

rails 2.3 convert hash into mysql query

I'm trying to find out how rails converts a hash such as (This is an example please do not take this literally I threw something together to get the concept by I know this query is the same as User.find(1)):
{
:select => "users.*",
:conditions => "users.id = 1",
:order => "username"
}
Into:
SELECT users.* FROM users where users.id = 1 ORDER BY username
The closest thing I can find is ActiveRecord::Base#find_every
def find_every(options)
begin
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
instantiate_collection( (format.decode(connection.get(path, headers).body) || []), prefix_options )
end
rescue ActiveResource::ResourceNotFound
# Swallowing ResourceNotFound exceptions and return nil - as per
# ActiveRecord.
nil
end
end
I'm unsure as to how to modify this to just return what the raw mysql statement would be.
So after a few hours of digging I came up with an answer although its not great.
class ActiveRecord::Base
def self._get_finder_options options
_get_construct_finder_sql(options)
end
private
def self._get_construct_finder_sql(options)
return (construct_finder_sql(options).inspect)
end
end
adding this as an extension gives you a publicly accessible method _get_finder_options which returns the raw sql statement.
In my case this is for a complex query to be wrapped as so
SELECT COUNT(*) as count FROM (INSERT_QUERY) as count_table
So that I could still use this with the will_paginate gem. This has only been tested in my current project so if you are trying to replicate please keep that in mind.

why pageObject based on Cheezy does not work?

I'm new to ruby (1.9.3)
I have intermediate experience with Selenium WebDriver plus C#. I want to move to Watir-Webdriver.
I'd be grateful to find out why the first block of IRB code works, but the second block simply loads the correct page, then does nothing. The page is active and responds to manual input.
The second block of code is based on the PageObject example here:
https://github.com/cheezy/page-object/wiki/Get-me-started-right-now%21
require 'watir-webdriver'
browser = Watir::Browser.start 'http://x.com/'
browser.select_list(:id, "ddlInterestType").select("Deferred")
browser.select_list(:id, "ddlCompanyName").select("XYZ")
browser.button(:value,"Enter Transactions").click
Second block
require 'watir-webdriver'
browser = Watir::Browser.new :firefox
browser.goto "http://x.com/"
deferredPage = DeferredPage.new(browser)
deferredPage.interestType.select = 'Deferred'
deferredPage.company.select = 'XYZ'
deferredPage.enterTransactions
class DeferredPage
include PageObject
select_list(:interestType, :id => 'ddlInterestType')
select_list(:company, :id => 'ddlCompanyName')
button(:enterTransactions, :id => 'btnEnterTransactions')
end
In your page-object code example, after loading the page, an exception is likely being thrown (which makes it seem like nothing happens). That code should throw an no method exception:
undefined method `select=' for "stuff":String
When you declare a select list there are three methods created:
your_select= - this is for setting the select list
your_select - this is for getting the select list value
your_select_element - this is for getting the page-object gem element
When you do deferredPage.interestType, it returns a string that is the value of the select list. Strings do not have a select= method, which is why you get the exception (and does nothing).
The two selections should be done without the .select:
deferredPage.interestType = 'Deferred'
deferredPage.company = 'XYZ'
As you can see the page-object API is slightly different than the watir API.
While googling for info on page objects, I found this page by Alister Scott. :
http://watirmelon.com/2012/06/04/roll-your-own-page-objects/
For an idiot++ such as me, I think I'll use his method until I know more about Watir-Webdriver. Based on #justinko's comment, I'll stick to one API for the present. I tried rolling my own, and it works fine:
require 'watir-webdriver'
browser = Watir::Browser.new :ie
class DeferredPage
def initialize( browser )
#browser = browser
end
def enterIntType(intType)
#browser.select_list(:id, "ddlInterestType").select(intType)
end
def clickEnter()
#browser.button(:value,"Enter Transactions").click
end
end
dp = DeferredPage.new(browser)
browser.goto "http://x.com"
dp.enterIntType( "Deferred" )
dp.clickEnter
Could you please let us know what error you are getting? I suspect the problem you are seeing is related to the way the Ruby interpreter reads the code. It reads the file from top to bottom and you are using the DeferredPage class before it is defined. What would happen if you changed your code to this:
require 'watir-webdriver'
require 'page-object'
browser = Watir::Browser.new :firefox
class DeferredPage
include PageObject
select_list(:interestType, :id => 'ddlInterestType')
select_list(:company, :id => 'ddlCompanyName')
button(:enterTransactions, :id => 'btnEnterTransactions')
end
deferredPage = DeferredPage.new(browser)
deferredPage.navigate_to "http://x.com/"
deferredPage.interestType = 'Deferred'
deferredPage.company = 'XYZ'
deferredPage.enterTransactions
In this case I am declaring the class prior to using it.
Another thing I might suggest is creating a higher level method to perform the data entry. For example, you could change your code to this:
require 'watir-webdriver'
require 'page-object'
browser = Watir::Browser.new :firefox
class DeferredPage
include PageObject
select_list(:interestType, :id => 'ddlInterestType')
select_list(:company, :id => 'ddlCompanyName')
button(:enterTransactions, :id => 'btnEnterTransactions')
def do_something(interest, company)
self.interestType = interest
self.company = company
enterTransactions
end
end
deferredPage = DeferredPage.new(browser)
deferredPage.navigate_to "http://x.com/"
deferredPage.do_someting('Deferred', 'XYZ')
This is cleaner - the access to the page is abstracted behind a method that should add some business value.
-Cheezy