I have several models that I want the user to "disable" it vs destroying it. These models have a disable boolean. trying to make this work.
currently in application_controller.rb
helper_method :disable
def disable(model)
#model = "#{model}".find(params[:id])
#model.update_attribute(:disable => true)
flash[:notice] = "Successfully disabled #{model}."
redirect_to company_ + "#{model}".pluralized + _url(current_company)
end
Do I have to create a new path in routes for each one I want to use this function?
Would be ideal, if I can do something similar like the destroy method.
I would probably extend ActiveRecord with a disable method so that you can call #model.disable() just like you would #model.destroy(). That way you can leave all the default routes as is and just change the destroy action in your controller to try disable() instead of destroy().
Perhaps like this:
module MyDisableModule
def self.included(recipient)
recipient.class_eval do
include ModelInstanceMethods
end
end
# Instance Methods
module ModelInstanceMethods
#Here is the disable()
def disable
if self.attributes.include?(:disabled)
self.update_attributes(:disabled => true)
else
#return false if model does not have disabled attribute
false
end
end
end
end
#This is where your module is being included into ActiveRecord
if Object.const_defined?("ActiveRecord")
ActiveRecord::Base.send(:include, MyDisableModule)
end
And then in your controller:
def destroy
#model = Model.find(params[:id])
if #model.disable #instead of #model.destroy
flash[:notice] = "Successfully disabled #{#model.name}."
redirect_to #wherever
else
flash[:notice] = "Failed to disable #{#model.name}."
render :action => :show
end
end
Note that in this example, disabled is the attribute and disable is the method that makes a model disabled.
Related
I'm in Rails 3. Here's my code for a method which creates Update records in response to certain attributes being changed on a model called Candidate:
before_save :check_changed, on: [:update]
def check_changed
tracked_attributes = ["period_contributions", "total_contributions",
"period_expenditures", "total_expenditures",
"debts_and_loans", "cash_on_hand",
"major_endorsements",
"rating_afl_cio",
"rating_cal_tax",
"rating_cc",
"rating_eqca",
"rating_lcv",
"rating_now"]
changes.each do |key, value|
if tracked_attributes.include?(key)
Update.create(:candidate_id => self.id, :attribute_name => key,
:new_value => value[1], :old_value => value[0])
end
end
end
The issue is that I have some rake tasks I'm running to do batch updates to the data, which end up triggering this callback unintentionally. I'd like for it only to run when a Candidate is updated from within the admin tool aka CRUD interface. Any advice on the best way to do this?
I will only use callbacks when it is something that always needs to happen, no matter the source. Magically skipping or including them normally leads to pain down the road.
My suggestion is to create a different method on the model that does the check and use that for the crud actions.
class Candidate
#...
def check_changed_and_update(attributes)
check_changed
update(attributes)
end
private
def check_changed
tracked_attributes = ["period_contributions", "total_contributions",
"period_expenditures", "total_expenditures",
"debts_and_loans", "cash_on_hand",
"major_endorsements",
"rating_afl_cio",
"rating_cal_tax",
"rating_cc",
"rating_eqca",
"rating_lcv",
"rating_now"]
changes.each do |key, value|
if tracked_attributes.include?(key)
Update.create(:candidate_id => self.id, :attribute_name => key,
:new_value => value[1], :old_value => value[0])
end
end
end
end
Then in the controller for candidate just change update to check_changed_and_update:
class CanidateController < ApplicationController
def update
#...
respond_to do |format|
if #candidate.check_changed_and_update(canidate_params)
format.html { redirect_to #candidate, notice: 'Candidate was successfully updated.' }
else
format.html { render action: 'edit' }
end
end
end
This has an added bonus of making it obvious what is going to happen when update is called.
Now you can just use the normal active record api in your rake tasks.
I want to check some field before save and change it into default browser language.
I want to use before_save filter:
def update
#website = Website.find(params[:id])
if #website.language == "Automatic (by user's browser language)"
#website.language = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
respond_to do |format|
if #website.update_attributes(params[:website])
format.html { redirect_to #website,
notice: 'Note: code has been updated. Please replace the code you have on your website with the code below. Only then changes will take effect.'}
format.js
end
end
end
I need to check:
if #website.language == "Automatic (by user's browser language)"
#website.language = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
And it works in create action, but not in update.
How can I do this ?
if you want to something update after create action but not update action then see example
# write in your model
after_create :xyz
def xyz
...
...
end
above method xyz call after create action. when update call then it will not call.
Before save is used in model and request is usually not available in model.
However if you really want to do it there, check: http://m.onkey.org/how-to-access-session-cookies-params-request-in-model for more detail on how to do it.
-- edit --
There are several way to do it.
First one to cross my mind is that you add: attr_accessor :request_language to model, then pass request.env['HTTP_ACCEPT_LANGUAGE'] from controller to model:
if #website.update_attributes(params[:website])
#website.request_language = request.env['HTTP_ACCEPT_LANGUAGE']
# ...
and now you can proceed like you did before with few modifications:
def auto_language
if self.language == "Automatic (by user's browser language)"
self.language = full_language(self.request_language.scan(/^[a-z]{2}/).first)
end
end
Second way I can think of is to use before/after filters in controller to influence params before they are passed to model.
...
request object isn't available in models. You should do those tweaks on the controller layer.
def action
if #model.language == "Automatic (by user's browser language)"
#model.language = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
if #model.save
...
else
...
end
end
private
def full_language
...
end
You need to change the value of :language in the params hash, since that's what you're ultimately passing to #website.update_attributes. I would also suggest moving that conditional into the model layer to make your controller code more readable:
# app/controllers/websites_controller.rb
def update
#website = Website.find(params[:id])
if #website.language_automatic?
params[:website][:language] = full_language(request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first)
end
respond_to do |format|
if #website.update_attributes(params[:website])
format.html { redirect_to #website,
notice: 'Note: code has been updated. Please replace the code you have on your website with the code below. Only then changes will take effect.'}
format.js
end
end
end
# app/models/website.rb
def language_automatic?
language == "Automatic (by user's browser language)"
end
I have a create method that calls a method in a model that pings some third-party APIs.
What I need to do is if the API sends back a certain message, then I'd display an error.
Below is my current controller and model setup, so how would I get the error back in to the controller (and ultimately the view)?
Here is the method in my controller:
def create
#number = Number.where(:tracking => params[:number][:tracking], :user_id => current_user.id).first
if #number.blank?
#number = Number.new
#number.tracking = params[:number][:tracking]
#number.user_id = current_user.id
#number.notes = params[:number][:notes]
#number.track
end
respond_with(#number) do |format|
format.html { redirect_to root_path }
end
end
Here are the methods in my model:
def track
create_events
end
def create_events(&block)
tracker = fedex.track(:tracking_number => number)
if tracker.valid?
self.assign_tracker(tracker)
tracker.events.each do |e|
self.create_event(e) unless (block_given? && !block.call(e))
end
save
else
# NEED TO THROW THE ERROR HERE
end
end
How about if rather than throwing errors, you just use validation? Something like the following (Just to get your started. This would need work.):
# if you don't cache the tracker in an attribute already, do this so
# you can add errors as if it were a column.
attr_accessor :tracker
def create_events(&block)
tracker = fedex.track(:tracking_number => number)
if tracker.valid?
# ...
else
# add the error with i18n
errors.add(:tracker, :error_type_if_you_know_it)
# or add it from a returned message
errors.add(:tracker, nil, :message => fedex.get_error())
end
end
Then in your controller:
#number.track
respond_with(#number) do |format|
if #number.errors.any?
format.html { redirect_to root_path }
else
format.html { render :some_template_with_errors }
end
end
Alternatively you could do this as part of validation (so calling valid? would work as expected and not destroy your custom "track" errors)
# do your tracking on creation, if number was given
validate :on => :create do
if number.present?
tracker = fedex.track(:tracking_number => number)
unless tracker.valid?
errors.add :tracker, nil, :message => tracker.get_error()
end
end
end
# then do your actual creation of tracking events sometime after validation
before_save :handle_tracker_assignment
def handle_tracker_assignment
self.assign_tracker(tracker)
# note the block method you're using would need to be reworked
# ...
end
Note in the latter case you'd have to change your logic a bit, and simply pass the tracking number and attempt to save a new record, which would trigger the tracking attempt.
You should typically offload the the API calls to a background job and you could either use notifiers (or Rack middleware) to raise self-defined errors and handle them accordingly.
I have a couple different user types (buyers, sellers, admins).
I'd like them all to have the same account_path URL, but to use a different action and view.
I'm trying something like this...
class AccountsController < ApplicationController
before_filter :render_by_user, :only => [:show]
def show
# see *_show below
end
def admin_show
...
end
def buyer_show
...
end
def client_show
...
end
end
This is how I defined render_by_user in ApplicationController...
def render_by_user
action = "#{current_user.class.to_s.downcase}_#{action_name}"
if self.respond_to?(action)
instance_variable_set("##{current_user.class.to_s.downcase}", current_user) # e.g. set #model to current_user
self.send(action)
else
flash[:error] ||= "You're not authorized to do that."
redirect_to root_path
end
end
It calls the correct *_show method in the controller. But still tries to render "show.html.erb" and doesn't look for the correct template I have in there named "admin_show.html.erb" "buyer_show.html.erb" etc.
I know I can just manually call render "admin_show" in each action but I thought there might be a cleaner way to do this all in the before filter.
Or has anyone else seen a plugin or more elegant way to break up actions & views by user type? Thanks!
Btw, I'm using Rails 3 (in case it makes a difference).
Depending on how different the view templates are, it might be beneficial to move some of this logic into the show template instead and do the switching there:
<% if current_user.is_a? Admin %>
<h1> Show Admin Stuff! </h1>
<% end %>
But to answer your question, you need to specify which template to render. This should work if you set up your controller's #action_name. You could do this in your render_by_user method instead of using a local action variable:
def render_by_user
self.action_name = "#{current_user.class.to_s.downcase}_#{self.action_name}"
if self.respond_to?(self.action_name)
instance_variable_set("##{current_user.class.to_s.downcase}", current_user) # e.g. set #model to current_user
self.send(self.action_name)
else
flash[:error] ||= "You're not authorized to do that."
redirect_to root_path
end
end
I'm trying to make attributes equal predetermined values, and I'm not sure if I'm doing that efficiently with the following (in my orders controller):
def create
#order = Order.find(params[:id])
#order.price = 5.99
#order.representative = Product.find(params[:product_id]).representative
#order.shipping_location = SHIPPING_LOCATION
#order.user = current_user
respond_to do |format|
...
end
end
Is there a more efficient way to equate attributes in Rails (maybe using models)? If I'm using two different controllers, do I just repeat what I did above for the new controller?
Use before_create callback in model to assign default values.
Your code is a little off, it looks like a controller action for create, but the code reads like it's for an update.
Regardless...
You could use a parameter hash to update everything at once.
In the case where you're creating:
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
#order = Order.new(order_update)
In the case where you're updating:
#order.update_attributes(order_update) #attempts to save.
Mixing it into your controller code we get:
def create
#order = Order.find(params[:id])
order_update = {:price => 5.99, :representative =>
Product.find(params[:product_id]).representative,
:shipping_location => SHIPPING_LOCATION,
:user => current_user}
respond_to do |format|
if #order.update_attributes(order_update)
# save succeeded. Redirect.
else
# save failed. Render with errors.
end
end
end
Another solution:
class Example < ActiveRecord::Base
DEFAULTS = HashWithIndifferentAccess.new(:some => 'default', :values => 'here')
def initialize(params = {})
super(DEFAULTS.merge(params))
end
end
Either use initialize and merge with params, or use an ActiveRecord hook like before_create etc.