I want to allow an update but want to exclude the update of a specific column and create it's own update method.
Question: Is there a way for me to define my order params but exclude a specific column? Or even separate the columns into methods?
def order_params
params.require(:order).permit(:name, :email, :image, :video, :description)
end
My issue is that, I have a stripe charge in my update method so when i simply update an orders "order_status", the customer gets charged.
I have a column "order_status" which can be changed by enum 1,2,3 and i want to avoid that within it the update so no charge is made.
I created:
def order_status
params.permit(:order_status)
end
But since the original order_params has :orders, the order status is still being included.
I have the enum as, "created", "charged", and "cancelled". A seller can cancel an order with:
<%= form_for #order, remote: true do |f| %>
<%= f.hidden_field :order_status, value: "cancelled" %>
<%= f.button type:'submit', class: "btn btn-danger", onclick: "refreshPage()" %>
<% end %>
What should i do to have this "order_status" update be on its own and out of the original update method so customers don't get charged?
Update:
I did find a way to simply create it's own method for charging and taking the charge out of the update method with all the params..
def charge_update
respond_to do |format|
#amount = (#order.order_price).to_i * 100
#amount_seller = (#order.order_price).to_i * 75
if #order.update(params[:tracking_number])
if user_signed_in?
charge = Stripe::Charge.create({
:amount => (#order.order_price).to_i * 100,
:description => 'Rails Stripe customer',
:currency => 'usd',
:customer => #order.stripe_customer_token,
:destination => {
:amount => #amount_seller ,
:account => (#order.seller.stripe_token),
}
})
#order.order_status = "charged"
format.html { redirect_to #order, notice: 'Order was successfully uploaded.' }
format.json { render :show, status: :ok, location: #order }
else
format.html { render :edit }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
end
end
The customer only gets charged once the order is sent.
But, how can i specify the params status itself?
For instance, I want to create a method for the order_status, but only for when the order_status = "cancelled"... and then create 2 more methods for the other 2 possibly status'
?
So when I/seller cancels an order, I can create a custom message, send emails, etc. and do the same for "charged", and "created"....
Is there a way for me to define my order params but exclude a specific
column?
Easy. The #except method can be used to remove a key or keys from the (whitelisted) params hash.
order_params.except(:order_status)
Or even separate the columns into methods?
One thing to remember is that ActionController::Parameters is actually a pretty basic hash like object and you can compose your whitelist functions however you want.
#permit works somewhat like Hash#slice on steroids - it returns a new ActionController::Parameters instance with the permitted attribute set to true. It also takes a hash argument for nested parameters.
You can use also use arrays to compose whitelists from different methods. This can be pretty nifty since all the methods in ActionController::Parameters are reductive. The trick is using the splat(*) operator to convert the array to a list of arguments:
def base_parameters
[:a, :b, :c, :d]
end
def others
[:e, :f]
end
def create_parameters
params.require(:order)
.permit(*base_parameters.concat(others))
end
Related
I'm trying a feature where user can request for offer and it can be accepted or rejected , I'm new to rails. i can't figure out what's the good way to proceed this.
offer create method
def create
#offer = Offer.new(offer_params)
pp offer_params
#barter = Barter.find(params[:barter_id])
#offer = Offer.new(offer_params)
#offer.barter = #barter
#offer.user = current_user
respond_to do |format|
if #offer.save
format.js do
#barter = Barter.find(params[:barter_id])
end
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #review.errors, status: :unprocessable_entity }
end
end
end
offer submission
<%= form_for([ #barter, #barter.offers.new] ) do |form| %>
<%= form.text_area :message %><br>
<%= form.submit "leave" %>
<% end %>
here I want to make it accepted or rejected , I've given a boolean value and simply make it false when rejected
<%= form_tag([ #barter, #barter.offers.new] ) do %>
<%= hidden_field_tag :reject, :value => true %><br>
<%= submit_tag "reject" %>
<% end %>
is there a good way to do this? and how can i make it disappear when i accept this.
Sorry but thats not even close. You're just creating a new offer record in the form when what you should be doing is to update an existing record - and while you potentially do this through PATCH /offers/:id its going to be very ambigeuos in terms of intent.
The simplest way I cn think of handle this would be to simply add two additional RESTful routes to update the offers.
Start by adding the routes:
resources :offers, only: [] do
patch :accept
patch :decline
end
And en enum attribute to the model:
class AddStatusToOffers < ActiveRecord::Migration[7.0]
def change
add_column :offers, :status, :integer, default: 0, index: true
end
end
class Offer < ApplicationRecord
# ...
enum status: {
pending: 0,
accepted: 1,
rejected: 2
}
end
This is a better idea then adding a boolean since your boolean would either need to be a tri-state boolean (nullable) which is regarded as a very bad practice or default to false in which case you can't differentiate between the offers a users has replied to or not.
Then add the controller methods for your new endpoints:
class OffersController
before_action :set_coffer, only: %i{ show edit update destroy accept decline }
# ...
# PATCH /offers/:id/accept
# #TODO authorize that the user should actually be allowed the offer
def accept
if #offer.accepted!
redirect_to #offer, notice: 'Offer accepted'
else
redirect_to #offer, notice: 'Offer could not be accepted - please try again'
end
end
# PATCH /offers/:id/reject
# #TODO authorize that the user should actually be reject the offer
def reject
if #offer.rejected!
redirect_to #offer, notice: 'Offer rejected'
else
redirect_to #offer, notice: 'Offer could not be rejected - please try again'
end
end
private
def set_offer
#offer = Offer.find(params[:id])
end
end
You can then simply add buttons/links that send the request to update the offer:
<%= button_to "Accept", accept_offer_path(offer), method: :patch %>
<%= button_to "Reject", reject_offer_path(offer), method: :patch %>
This is not the only way to solve the issue. If you for example want to record a message where the user can say why they rejected an offer I would model replies to an offer as a completely seperate resource.
First steps with RoR, trying to wrap my head around basic concepts. Following excercise: I have pupils and schoolclasses, both Active Record entities with a many to many (has_and_belongs_to_many) to each other. Now I have a form to create a new pupil. On this form there is also a form.select to pick the class for the pupil, but I can´t get this to work, I can´t get the controller to create a new record for the join table.
Schoolclass.rb
class Schoolclass < ApplicationRecord
has_and_belongs_to_many :pupils
end
Pupil.rb
class Pupil < ApplicationRecord
has_and_belongs_to_many :schoolclasses
end
Relevant part of the _form.html.erb
<div class="field">
<%= form.label :schoolclass %>
<%= form.select(schoolclass.id, schoolclasses_for_select) %>
</div>
schoolclasses_for_select is just a helper for populating the select box
def schoolclasses_for_select
Schoolclass.all.collect{ |s| [s.name, s.schoolyear] }
end
Everything I have tried on the controller has failed miserably. Somehow, I mostly end up with the controller trying to pass the schoolclass (as a String) as an attribute to the new Pupil, or with a MethodNotFound error. In my understanding it should work something like this :
#klass = params[:schoolclass]
pupil.schoolclasses << #klass
but it doesn´t.
Thanks in advance for any help.
Edit1: the create code
def create
#pupil = Pupil.new(pupil_params)
respond_to do |format|
if #pupil.save
format.html { redirect_to #pupil, notice: 'Pupil was successfully created.' }
format.json { render :show, status: :created, location: #pupil }
else
format.html { render :new }
format.json { render json: #pupil.errors, status: :unprocessable_entity }
end
end
end
def pupil_params
params.require(:pupil).permit(:nachname, :vorname, :schoolclass)
end
That is the part that works. What I haven't managed is to find the correct Schoolclass record and pass it to the pupil.
Issues
First argument to your form.select should be the field name i.e. :schoolclass_id. You can still keep the label Schoolclass.
I believe you want id of schoolclass to be passed in params when selected. For that to happen, change your options for select to Schoolclass.all.collect{ |s| [s.name, s.id] }
Biggest, Your association says a pupil can have multiple schoolclasses but your form doesn't support it. Have you handled it some other way?
Fixes
So, do something like (this does not support multiple schoolclasses selection):
<%= form.select :schoolclass_id, Schoolclass.all.collect{ |s| [s.name, s.id] } %>
And in your controller
def create
#pupil = Pupil.new(pupil_params)
# Find schoolclass from `schoolclass_id` and associate it to `#pupil`
schoolclass = Schoolclass.find(params[:pupil][:schoolclass_id]) # Handle case when schoolclass not selected in form
#pupil.schoolclasses |= [schoolclass]
respond_to do |format|
...
end
end
private
def pupil_params
params.require(:pupil).permit(:nachname, :vorname)
end
I have an instance where I am recording prices for water from vendors. My vendor model has :price. However, I want to give users the option to input a price for different volumes, and do the simple division for them rather than having them to do it. In other words, users should be able to input $1.99 per liter or $3.99 for a gallon and so on. To do this, I need a virtual attribute in my form for :unit, since I don't want to be storing units in the table. Everything works well, except that I cannot seem to update vendor_params[:price] before I update the record or create a new record. This seems like it should be a cake walk, but I Googled most of the day and can't figure out how to make it work.
Here is what I have:
Model:
class Vendor < ActiveRecord::Base
attr_accessor :unit
...
end
Form:
<%= form_for(#vendor) do |f| %>
...
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price %>
<%= select( "unit", "id", { "1 Liter" => "1", "Bottle (2 liters)" => "2", "Jerry Can (20 liters)" => "20"}) %>
</div>
...
<% end %>
Controller:
...
def update
vendor_params[:price] = vendor_params[:price].to_f/params[:unit][:id].to_f
respond_to do |format|
if #vendor.update(vendor_params)
format.html { redirect_to #vendor, notice: 'Vendor was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #vendor.errors, status: :unprocessable_entity }
end
end
...
end
I know that vendor_params[:price].to_f/params[:unit][:id].to_f returns the correct value. I just can't seem to assign that value to vendor_params[:price] before I update the record. I also tried the following which throws an error:
#vendor_params[:price] = vendor_params[:price].to_f/params[:unit][:id].to_f
It seems like this should be trivial! I guess I could use form_tag instead of form_for, but that seems odd when updating the full record. (The edit form has all fields for all the object attributes.) Anywho, I'm open to ideas and suggestions.
Thanks!!
If vendor_params is a strong_params method (which I'm assuming it is), it actually creates a new hash. So when you alter vendor_params... you're not actually changing your original params hash.
OK, why isn't vendor_params changing though... I dont care about params? WELL, vendor_params still points the original params hash assuming it looks something like:
def vendor_params
params.require(:vendor).permit(:price)
end
I think the link below is a similar issue and may present a useful solution. Hope I understood your problem correctly!
Modify ruby hash in place( rails strong params)
I have a table CLIENTS with id, name and email fields and I am sending them emails using ActionMailer with 3rd party SMTP.
Now I want the clients to have subscription option too so I added "subscription" column with default value as true.
Now how to generate a link which can be put in views mailer template so when the user clicks on it, the subscription value changes to false so in future the client dont' get any email ? Do note that these clients are not my rails app users so I can't uses what is been suggested here Rails 3.2 ActionMailer handle unsubscribe link in emails
I found this link how to generate link for unsubscribing from email too which looked helpful but I thought may be in 3 years, we might have got a better solution
Here is my Complete Code -
#client.rb
attr_accessible :name, :company, :email
belongs_to :user
has_many :email_ids
has_many :emails, :through => :email_ids
before_create :add_unsubscribe_hash
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
Here is Clients_controller.rb file
# clients_controller.rb
def new
#client = Client.new
respond_to do |format|
format.html
format.json { render json: #client }
format.js
end
end
def create
#client = current_user.clients.new(params[:client])
respond_to do |format|
if #client.save
#clients = current_user.clientss.all
format.html { redirect_to #client }
format.json { render json: #client }
format.js
else
#clients = current_user.clients.all
format.html { render action: "new" }
format.json { render json: #client.errors, status: :error }
format.js
end
end
end
def unsubscribe
#client = Client.find_by_unsubscribe_hash(params[:unsubscribe_hash])
#client.update_attribute(:subscription, false)
end
The code is working fine for existing records and the unsubscription is working perfectly, I am only having problem in creating new clients.
I have used #client in unsubscribe method as I am using this object in client_mailer.rb template (using #client or just using client, both are working!)
EDIT 2 -
_form.html.erb
<%= simple_form_for(#client, :html => {class: 'form-horizontal'}) do |f| %>
<%= f.input :name, :label => "Full Name" %>
<%= f.input :company %>
<%= f.input :email %>
<%= f.button :submit, class: 'btn btn-success' %>
<% end %>
I have copied the full track stack at http://jsfiddle.net/icyborg7/dadGS/
Try associating each client with a unique, but obscure, identifier which can be used to look up (and unsubscribe) the user via the unsubscribe link contained within the email.
Start by adding another column to your clients table called unsubscribe_hash:
# from command line
rails g migration AddUnsubscribeHashToClients unsubscribe_hash:string
Then, associate a random hash with each client:
# app/models/client.rb
before_create :add_unsubscribe_hash
private
def add_unsubscribe_hash
self.unsubscribe_hash = SecureRandom.hex
end
Create a controller action that will toggle the subscription boolean to true:
# app/controllers/clients_controller.rb
def unsubscribe
client = Client.find_by_unsubscribe_hash(params[:unsubscribe_hash])
client.update_attribute(:subscription, false)
end
Hook it up to a route:
# config/routes.rb
match 'clients/unsubscribe/:unsubscribe_hash' => 'clients#unsubscribe', :as => 'unsubscribe'
Then, when a client object is passed to ActionMailer, you'll have access to the unsubscribe_hash attribute, which you can pass to a link in the following manner:
# ActionMailer view
<%= link_to 'Unsubscribe Me!', unsubscribe_url(#user.unsubscribe_hash) %>
When the link is clicked, the unsubscribe action will be triggered. The client will be looked up via the passed in unsubscribe_hash and the subscription attribute will be turned to false.
UPDATE:
To add a value for the unsubscribe_hash attribute for existing clients:
# from Rails console
Client.all.each { |client| client.update_attribute(:unsubscribe_hash, SecureRandom.hex) }
All, I have two dropdown boxes, which are populated from two different database tables and a form with a single submit button. My goal is to concatenate the two values upon form submit and write the single value back to the database into the form associated with the model.
More simply: two dropboxes allowing to select ['red','green','blue'] and ['dog','cat']. The user selects 'red' and 'cat', and the submit button creates a new record 'red-cat' (under the blogname model) as a result.
ENTIRE Form (new.html.erb) code:
<%= select("subdomainw1", "blognamew1", Subdomainw1.order("blognamew1 ASC").collect {|p| [ p.blognamew1 ] }, {:prompt => 'Select Adjective'}) %>
<%= select("subdomainw2", "blognamew2", Subdomainw2.order("blognamew2 ASC").collect {|p| [ p.blognamew2 ] }, {:prompt => 'Select Noun'}) %>
<%= simple_form_for (#blogname) do |f| %>
<%= f.button :submit %>
<% end %>
with the associated controller def create being:
def create
#blogname = Blogname.new(params[:blogname])
respond_to do |format|
#blogname.blogname = ?? THIS SHOULD BE A CONCATENATION OF THE VALUES FROM ABOVE SELECTS
if #blogname.save
format.html { redirect_to #blogname, notice: 'Blog was successfully created.' }
else
format.html { render action: "new" }
end
end
end
Any ideas here?
There are a lot of ways to do this, the Rails way would be to do it in your model and keep your controllers skinny.
I think the most common way in rails you'll see this done is a callback. So, for this example you could set up a before_validation (or perhaps before_create if you don't want it to be changed if the blog is edited) call back in your mode, and assign your blogname from the two other attributes.
model.rb
before_validation :generate_blogname
def generate_blogname
self.blogname ||= "#{blognamew1}-#{blognamew2}".parameterize
end
Then in your controller:
controller.rb
def create
#blogname = Blogname.new(params[:blogname])
respond_to do |format|
if #blogname.save
format.html { redirect_to #blogname, notice: 'Blog was successfully created.' }
else
format.html { render action: "new" }
end
end
end
The parameterize method will make this work for subdomains by taking out special characters. The model shouldn't probably be called blogname, it should probably be a table blog with an attribute of name. So #blog = Blog.new, then #blog.name = "Two Subdomain Values"