How to reload code in Active Admin Custom Batch Actions? - ruby-on-rails

I use active admin in a Rails app with custom batch actions.
The batch actions are created from the last 5 records from the database. Please find attached the code below.
However when a new record (Event) is created the batch actions do not refresh. I would like to know how to force a refresh? Is there a function I can call to make the batch actions refresh from new records? Thanks
ActiveAdmin.register TimeLineMessage do
menu
menu label: 'Rundown page'
Event.order("created_at DESC").limit(5).reload.each do |event|
batch_action ("Move to " + event.name.to_s).to_sym do |ids|
TimeLineMessage.find(ids).each do |tlm|
tlm.event_id = event.id
tlm.save
end
redirect_to collection_path, alert: "The content tiles have been moved to "+ event.name.to_s + " event "
end
end
Ref: http://activeadmin.info/docs/9-batch-actions.html

The way you want to go can't work, because of the fact that the code is only executed one time at the boot of Rails/ActiveAdmin.
But there is a other way that you can go:
batch_action :attach_to_event, form: {
event_id: -> { Event.order("created_at DESC").limit(5).pluck(:id, :name) }
} do |ids, inputs|
event = Event.find(inputs[:event_id])
TimeLineMessage.find(ids).each do |tlm|
tlm.event_id = event.id
tlm.save
end
redirect_to collection_path, alert: "The content tiles have been moved to "+ event.name.to_s + " event "
end
The code isn't tested by me, but the idea should work.

The approach that worked for me instead of trying to force a refresh/attach_to_event is to re-calculate on each load, pass a lambda as the value of form.
For details see : ActiveAdmin Batch Action Dynamic Form

Related

Rails 4: After updating an attribute post a message

How do I go about posting or rendering a message after updating an attribute to reflect the changes?
(I couldn't find anything online in respect to this task)
For example: Once a user updates the ticket status there will be a message posted in the comment section reporting "Ticket has been closed". (No flash message, but a permanent one).
tickets_controller.rb
def open
#ticket = Ticket.find(params[:ticket_id])
#ticket.update_attributes(:ticket_status, "closed")
redirect_to ticket_path
end
You need to store the message in database if you want it to persist. Say for ex- There is a ticket raised (Ticket model) and someone closes it. The attribute on backend will update attribute status for ticket model.
def update
tkt = Ticket.where(id: 1)
tkt.update_attributes(status: 1)
end
If this is an ajax call, you can send a response data with message like "Ticket closed " and display it accordingly on html page in the success callback of ajax call. If it's a server call, refresh the page and use the status attribute from Ticket model to create the message.
You can use some enums like
{0 => "Ticket Open", 1 => "Ticket closed", 2 => "Ticket in progress" }
In case messages are not generic and every time some more customization is required, better to create and save the entire message as an attribute in the related model and no need to have enums.
You can use the session for this purspose.
def open
#ticket = Ticket.find(params[:ticket_id])
#ticket.update_attributes(:ticket_status, "closed")
session[:close_message] = "Ticket has been closed"
redirect_to ticket_path
end
After that diaplay it in the view like
<%= session[:close_message] %>
but make sure to clear the session like session[:close_message] = nil after displaying the message to free the memory

ActiveAdmin Access filtered collection

I'm trying to create a collection_action where I'm going to do something with the entire collection of filtered items. My problem is that from within the collection_action I don't seem to have access to the filtered collection. When I access collection it is only the items that are on the first page of records. In my action_item I have access to collection_before_scope which is exactly the filtered record I want, but this is empty when I try to access it from within my collection_action.
Below is my current setup attempting to find the correct collection.
collection_action :dosmth, :method => :get do
# collection_before_scope will be empty
puts "collection_before_scope = " + collection_before_scope.to_yaml
# collection will return only the filtered items on the front page
puts "collection = " + collection.to_yaml
redirect_to :back, notice: 'Something happening'
end
action_item :only => :index do
# collection_before_scope will return the full collection that I'm looking for.
puts "collection_before_scope = " + collection_before_scope.to_yaml
link_to "Export", dosmth_admin_earned_points_path(controller.params)
end
The closest related question I could find was this, ActiveAdmin Collection action on filtered data, which didn't seem to help me out.
Any help would be greatly appreciated.
Thank you,
Update:
I still have the same problem, but I have figured something out. If I try to access the collection before the collection_before_scope then the correct filtered items are in collection_before_scope. I don't want to have to access the collection just to get the correct collection_before_scope though. Not sure why this would happen.
collection_action :dosmth, :method => :get d0
# collection will return only the filtered items on the front page
puts "collection = " + collection.to_yaml
# collection_before_scope will be correct because I accessed the collection first. why???
puts "collection_before_scope = " + collection_before_scope.to_yaml
redirect_to :back, notice: 'Something happening'
end
Try this:
puts "filtered collection = " + apply_filtering(collection).to_yaml (before you call collection)
Why do you reach the correct filtered collection after you accessed the collection first?
The collection method will invoke the find_collection method: https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/resource_controller/data_access.rb#L32
The find_collection method will invoke the apply_filter method: https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/resource_controller/data_access.rb#L50
And once the collection method has been called:
https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/resource_controller/data_access.rb#L22-L27
I know this is old, but I just ran into this issue when trying to access the filtered collection for a custom CSV download.
Because ActiveAdmin uses Ransack for search, you can grab the filtered collection using their params.
ModelName.ransack(params[:q]).result worked for me.

ActiveAdmin batch_action template error and no update

My ActiveAdmin registered model has an "active" boolean field. I want to include a batch action to "activate" multiple records at once.
I am trying to follow the instructions at:
http://activeadmin.info/docs/9-batch-actions.html
for doing a custom batch action and I am having two problems.
I have this:
ActiveAdmin.register Venue do
batch_action :deactivate do |selection|
Venue.find(selection).each do |v|
v.active = false
end
end
end
When I try to activate something I get a template not found error. It is looking for a "batch_action" template. I didn't see anything in that doc about needing to also add a template. If I add a template with that name the error goes away and it displays the template...this is of course not what I want. I want it to just redisplay the index.
In either case (with or without a template in place), the model is not being updated. I can see in the log where it just does a select for the selected records and does nothing else.
I got rid of the issues by doing the following:
batch_action :activate do |selection|
Venue.find(selection).each do |v|
v.active = true
v.save
end
redirect_to :back #this ensures any current filter stays active
end
The 'save' part seems obvious but the example in the docs threw me off on my first attempt. It seems like this would be a more relevant example for the docs.

Rails "please wait" page

I have a rails app that imports all your Facebook contacts. This takes some time. I would like to be able to show a "please wait" page while the importing keeps happening in the back.
It seems that I cannot put render and redirect_to on the same action in the controller. How can I do this?
if #not_first_time
Authentication.delay.update_contact_list(current_user)
else
render 'some page telling the user to wait'
Authentication.import_contact_list(current_user)
end
redirect_to :root_path, :notice => 'Succesfully logged in'
If it is the users first time in the site, i want to render a "please wait page", start importing, and once its done redirect to the root path, where a lot of processing with this data happens
If it is not the first time, then put the contact update in the background (using the delayed_jobs gem) and go straight to the home page
I'm using the fb_graph gem to import the contacts. Here's the method
def self.import_contact_list(user)
user.facebook.friends.each do |contact|
contact_hash = { 'provider' => 'facebook', 'uid' => contact.identifier, 'name' => contact.name, 'image' => contact.picture(size='large') }
unless new_contact = Authentication.find_from_hash(contact_hash)
##create the new contact
new_contact = Authentication.create_contact_from_hash(contact_hash)
end
unless relationship = Relationship.find_from_hash(user, new_contact)
#create the relationship if it is inexistent
relationship = Relationship.create_from_hash(user, new_contact)
end
end
end
Edit
I added the solution suggested below, it works!
Here's is my 'wait while we import contacts' view from the action "wait"
<script>
jQuery(document).ready(function() {
$.get( "/import_contacts", function(data) {
window.location.replace("/")
});
});
</script>
<% title 'importing your contacts' %>
<h1>Please wait while we import your contacts</h1>
<%= image_tag('images/saving.gif') %>
Thanks!
A single request receives a single response - you can't render content and redirect.
If I were you I would always do the lengthy process in delayed job - tying up passenger/unicorn instances is never a great idea. Render a 'please wait page' that refreshes periodically to check whether the delayed job has completed (if you stash the id of the delayed job you can test whether it is still in the db. When te job completes it will be deleted). When the job is complete, redirect to the results page. You could also do the periodic checking bit via ajax.

Rails: Prevent duplicate inserts due to pressing back button and save again

Think about a simple Rails scaffold application with a "new" action containing a form to add records to a database with a "save" button. After the "create" action the controller redirects to the "show" action, where the user can use the "edit" link to edit the just inserted record. So far, so simple.
But if the user instead uses the browser's back button after creating a record to get back to the "new" action, the browser shows the form with the values the user just has entered. Now he changes some values and presses "save" again. He thinks that this would change the record, but of course this creates a new record.
What is the preferred way to prevent such duplicate entries? I'm looking for a general solution, maybe based on cookies or JavaScript.
After some investigations I found a suitable solution based on cookies. Here it is:
In the controller's "new" action, a timestamp with the current time is generated and rendered in the form as hidden field. When the user submits the form, this timestamps gets back to the controller's "create" action. After creating the record, this timestamp is stored in the session cookie. If the user goes back to the "new" form via browser's back button, he gets a stale form, which means its timestamp is older than the one stored in the cookie. This is checked before creating the record and results in an error message.
Here is the controller code:
def new
#post = Post.new
#stale_form_check_timestamp = Time.now.to_i
end
def create
#post = Post.new(params[:post])
if session[:last_created_at].to_i > params[:timestamp].to_i
flash[:error] = 'This form is stale!'
render 'new'
else
#post.save!
#stale_form_check_timestamp = Time.now.to_i
session[:last_created_at] = #stale_form_check_timestamp
end
end
And here the form code:
- form_for #post do |f|
= tag :input, :type => 'hidden', :name => 'timestamp', :value => #stale_form_check_timestamp
= f.input :some_field
= .......
When I had that same problem I created this little gem that solves it. When the user hits back, he's redirected to the edit_path of the record, instead of going back to the new_path.
https://github.com/yossi-shasho/redirect_on_back
You can do something like:
def create
#user = User.new(params[:user])
if result = #user.save
redirect_on_back_to edit_user_path(#user) # If user hits 'back' he'll be redirected to edit_user_path
redirect_to #user
end
end
Your model validations will ensure things like email addresses are unique, but I think this is more about usability and experience than anything else.
Say you are talking about an account creation form. First of all, your form submit button should say something like "Create Account", instead of just "Submit". Then depending on whether it was successful or not, show a message like either "Account successfully created" or "There were errors creating your account". If the user sees this message, they will know what happened.
Sure you can't prevent someone from hitting the back button and hitting enter again, but you should design for the majority of use cases. If they happen to hit back, they will see the button that says "Create Account". You should probably have some other text on the page that says "Please sign up for a new account to get started".
Just my $0.02.
Session or cookie may result in sides effects.
I totally agree : if there is a way to validate with your model, it's the safest way to prevent duplicate records.
Still you can do 2 things. Prevent browser caching : fields will appear empty in the form when the user clicks on the back button. And disable the "Create" button when clicked.
= f.submit "Create", :disable_with => "Processing..."
When your user will press the back button the button will be disabled.
You can use validators to make sure that no duplicate values are inserted. In this case validates_uniqueness_of :field
If you for example want to prevent users from having the same email address you could put the following code in your user model.
validates_uniqueness_of :email
This checks the column for any previous entries that are the same as the one your trying to inert.
Good luck
base on #Georg Ledermann answer i make this little snip of code for redirect to edit path if the user hits back and then hits create.
#objects_controller.rb
def new
#object = Object.new
#stale_form_check = Time.now.to_i
end
def create
#object = Object.new(object_params)
#function defined in application_controller.rb
redirect_to_on_back_and_create(#object)
end
#application_controller.rb
private
def redirect_to_on_back_and_create(object)
if session[:last_stale].present? and session[:last_stale_id].present? and session[:last_stale].to_i == params[:stale_form_check].to_i
redirect_to edit_polymorphic_path(object.class.find(session[:last_stale_id].to_i)), alert: "Este #{object.model_name.human} ya ha sido creado, puedes editarlo a continuación"
else
if object.save
session[:last_stale] = params[:stale_form_check].to_i
session[:last_stale_id] = object.id
redirect_to object, notice: "#{object.model_name.human} Creado con éxito"
else
render :new
end
end
end
And finally add the #stale_form_check param to your form
<%= hidden_field_tag :stale_form_check, #stale_form_check %>
You could always abstracts this method where you need it, but in this way you could avoid lots of repetition in your project if you need this behavior in many parts
Hope it helps the next one, i used to use redirect_on_back gem, but it didn't work for me this time, the _usec param that this gem uses, was always been reset, so it can't compare in every time when it was need
Here's something that worked for me.
You will need to do 2 things: Create a method in your controller and add a conditional statement in that same controller under your 'create' method.
1) Your method should return the total count of that object from that user.
EX:
def user
current_user.object.count
end
2) Add conditional statement in your 'create' method.
EXAMPLE:
def create
#object = Object.create(object_params)
#object.save if user == 0
redirect_to x_path
end
I hope this helps!
Add html: { autocomplete: "off" } in your form_for like this:
<%= form_for #object, url: xxx_path, html: { autocomplete: "off" } do |f| %>

Resources