ActiveAdmin generates methods to get the paths of each class. For example, if I wanted to make a link to a FooBar object I would call link_to obj.name, admin_foo_bar_path(obj).
How would I do that without needing to hard code the class of the object in admin_foo_bar_path?
For example, if I wanted to make a function usable by any class...
def show_link(obj)
display = obj.try(:name) || obj.id
link_to display, ???
end
Hello such function already exists in active admin
https://github.com/activeadmin/activeadmin/blob/ef4e80ea2f0cb528ea146becd104f7b5b029910d/lib/active_admin/view_helpers/auto_link_helper.rb#L14
example:
index do
column :name, :sortable => :name do |company|
auto_link(company)
end
column :active do |company|
company.active? ? icon(:check) : icon(:x)
end
end
Related
My first post here so I hope I'm including all the necessary information! Happy I've managed to make it two months into learning without having to ask any questions yet. :-)
I have a Callsheets record with a nested Store record that contains a :lastvisit column that I would like to update each time a new Callsheet is submitted every month. The :lastvisit field should be updated where the Store :id == #callsheet.store_id which is already defined. The beginner in me thinks the correct code for the 'update' method would be
#callsheet.update(callsheet_params).where(#callsheet.store_id => #store.id)
but I'm not sure how to access #store in this instance, and this likely just creates a new record anyways.
Any help or points in the right direction are appreciated. Thanks!
I've been trying to get it running in the 'update' method, but would also like to get it running in the 'create' method if that's any different. Relevant info:
callsheet.rb:
class Callsheet < ActiveRecord::Base
belongs_to :store
accepts_nested_attributes_for :store
callsheets_controller.rb:
class CallsheetsController < ApplicationController
def update
#callsheet = Callsheet.find(params[:id])
if #callsheet.update(callsheet_params)
redirect_to callsheet_dashboard_path
else
render action: :edit
end
end
def callsheet_params
params.require(:callsheet).permit(:id, :user_id, :store_id, . . . , store_attributes: [:id, :lastvisit])
edit.html.erb:
<%= form_for #callsheet, url: callsheet_path, remote: true, :html => { :multipart => true} do |f| %>
<%= f.fields_for :store do |s| %>
<%= s.text_field :lastvisit, :id => 'lastvisitHidden' %>
<% end %>
<%= f.hidden_field :store_id, :id => 'storeSelectHidden' %>
//
If you have a store_id column on callsheet that's called a "foreign key", and you can write these associations on the models:
#callsheet
belongs_to :store
#store
has_many :callsheets
Then in your controller action, after #callsheet.update(callsheet_params):
#callsheet.store.update(last_visit: Time.now)
This is simplest, and is what I'd recommend. #callsheet.store returns a store instance (you always need to keep track of whether your variables are model instances, model queries, or arrays.)
You could alternatively say this, it's just showing another way to access the store:
Store.find_by(id: #callsheet.store_id).update(last_visit: Time.now)
find_by is known as a "query method" (see ActiveRecord query interface), but unlike most of the others it returns an instance and not a query. update is only callable on instances, and returns a boolean indicating whether the save was succesful. update also triggers the callback chain - see ActiveRecord Callbacks.
Another way to do it:
Store.where(id: #callsheet.store_id).update_all(last_visit: Time.now)
This uses the .where query method, which returns a query (in this case you know it will be one element), and then the update all query method (which does not trigger the callback chain).
The last is kind of confusing and I'd dissuade you from using it. The reason being that a callsheet has only a single store, so loading it as an array is misleading.
I have a couple models shown below and I'm using the search class method in Thing to filter records
class Category << ActiveRecord::Base
has_many :thing
end
class Thing << ActiveRecord::Base
belongs_to :category
:scope approved -> { where("approved = true") }
def self.search(query)
search_condition = "%" + query + "%"
approved.where('name LIKE ?', search_condition)
end
end
It works fine in my Things controller. The index route looks like so:
def index
if params[:search].present?
#things = Thing.search(params[:seach])
else
#thing = Thing.all
end
end
On the categories show route I display the Things for this category. I also have the search form to search within the category.
def show
#category = Categories.find(params[:id])
if params[:search].present?
#category.things = #category.things.search()
end
end
So the problem is that the category_id attribute of all the filtered things are getting set to nil when I use the search class method in the categories#show route. Why does it save it to database? I thought I would have to call #category.save or update_attribute for that. I'm still new to rails so I'm sure its something easy I'm overlooking or misread.
My current solution is to move the if statement to the view. But now I'm trying to add pages with kaminiri to it and its getting uglier.
<% if params[:search].present? %>
<% #category.things.search(params[:search]) do |thing| %>
... Show the filtered things!
<% end %>
<% else %>
<% #category.things do |thing| %>
... Show all the things!
<% end %>
<% end %>
The other solution I thought of was using an #things = #categories.things.search(params[:search]) but that means I'm duplicated things passed to the view.
Take a look at Rails guide. A has_many association creates a number of methods on the model to which collection=(objects) also belongs. According to the guide:
The collection= method makes the collection contain only the supplied
objects, by adding and deleting as appropriate.
In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id]).
Like Yan said, "In your example you are actually assigning all the things found using #category.things.search() to the Category which has previously been queried using Categories.find(params[:id])". Validations will solve this problem.
Records are being saved as nil because you have no validations on your model. Read about active record validations.
Here's the example they provide. You want to validate presence as well because records are being created without values.
class Person < ActiveRecord::Base
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
My Rails site has different categories that the user can browse through. Right now they're rendered in the view through a simple loop, but I'd like to be able to choose the order from left to right in which they're displayed.
Here's the current code:
<h4>Explore Listings</h4>
<% #categories.each do |f| %>
<p class="category-page-category"><%= link_to f.name, view_category_path(f.id) %></p>
<% end %>
What would be an easy solution to accomplish what I want to do?
The easiest way I know of how to do this is to set up an AJAX form which will pass an order parameter which will be handled by the controller to re-render the form. Your controller would then listen for the order parameter and append that to #categories.
For example, your controller code might look something like:
def index
#categories = Category.<some_other_scope>
#categories = #categories.order(order_param) if order_param.present?
respond_to do |format
format.js { render :index }
end
end
private
def order_param
params.permit(:order)
end
First of all native sorting by the DB is to be preferred in every case (it's much faster).
If your sorting does not depend on already existing Category attributes, migrate a new attribute like position:
add_column :categories, :position, :integer
and add a scope to the model:
class Category < ActiveRecord::Base
def self.ordered default_order=nil
return scoped if default_order.nil? or
not( %w(asc desc).include?(default_order.to_s) )
order("position #{default_order}")
end
end
and then in the controller:
def index
#categories = Category.ordered params[:order]
end
following the sorting attribute approach you also should ensure your position attribute is valid defined. So like:
class Category < ActiveRecord::Base
before_create :default_position
validates :position,
presence: true,
numericality: true,
uniqueness: true,
on: :update
def self.ordered default_order=nil
return scoped if default_order.nil? or
not( %w(asc desc).include?(default_order.to_s) )
order("position #{default_order}")
end
private
def default_position
self.position = self.class.maximum("position") + 1
end
end
Changing positions has to be implemented.
Otherwise if positions never change, this means Categories have to be sorted by their creation datetime. Then you also could sort by created_at instead of position.
It sounds like you want to sort the array of categories before they are shown, no?
If so you can use http://www.ruby-doc.org/core-2.1.2/Enumerable.html#method-i-sort_by or http://www.ruby-doc.org/core-2.1.2/Enumerable.html#method-i-sort to sort your categories however you want before rendering them.
So this could end up looking something like:
<% #categories.sort_by(&:name).each do |f| %>
assuming categories have a name field. This should probably be done in the controller or in the model with a scope (if you are sorting based on a database field) to keep this logic out of the view.
looks like acts_as_list is exactly what you need
I am using Paperclip (w/ Amazon s3) on Rails 3. I want to delete an existing attachment without replacing it using an update action.
I've only found one example of this here and could not get that to work, it just wouldn't delete and there was nothing in the logs to say why. I wanted to do something like this on the form:
<%- unless #page.new_record? || !#page.image? -%>
<%= f.check_box :image_delete, :label => 'Delete Image' %>
<%- end -%>
(page is the name of the model, image is the attribute name which holds the attachment)
But how do I detect that checkbox and more importantly, how do I delete the image? I appreciate any help!
First off, when you create a check_box in a form_for (which it looks like you are), then the form should by default send :image_delete as "1" if checked and "0" if unchecked. The method declaration looks like this:
def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
Which shows that you can assign other values if you want to, but that is of course optional.
Secondly, the call to manually delete an attachment without deleting the model instance to which it is attached to is:
#page.image.destroy #Will remove the attachment and save the model
#page.image.clear #Will queue the attachment to be deleted
And to accomplish your way of deleting the images through a checkbox, perhaps add something like this to your Page model:
class Page < ActiveRecord::Base
has_attached_file :image
before_save :destroy_image?
def image_delete
#image_delete ||= "0"
end
def image_delete=(value)
#image_delete = value
end
private
def destroy_image?
self.image.clear if #image_delete == "1"
end
end
This way, when you create your form and add the :image_delete checkbox, it will load the default value "0" from the User instance. And if that field is checked then the controller will update the image_delete to "1" and when the User is saved, it will check if the image is to be deleted.
has_attached_file :asset
=>
attr_accessor :delete_asset
before_validation { asset.clear if delete_asset == '1' }
No need to destroy asset, Paperclip will do it.
In the form form.check_box(:delete_asset) will suffice.
This is Benoit's answer, but wrapped in a module, and covering the edge case of nested attribute models where the destroy tickbox is the only thing changed on the model.
It will apply to all attachments on the model.
# This needs to be included after all has_attached_file statements in a class
module DeletableAttachment
extend ActiveSupport::Concern
included do
attachment_definitions.keys.each do |name|
attr_accessor :"delete_#{name}"
before_validation { send(name).clear if send("delete_#{name}") == '1' }
define_method :"delete_#{name}=" do |value|
instance_variable_set :"#delete_#{name}", value
send("#{name}_file_name_will_change!")
end
end
end
end
remember to add this to your Page model too:
attr_accessible :image_delete
Modified version of Paul's solution, to support Rails 5 custom attributes. I just wish there were a way to include the module at the top of the file, before has_attached_file definitions.
module Mixins
module PaperclipRemover
extend ActiveSupport::Concern
included do
attachment_definitions.keys.each do |name|
attribute :"remove_#{name}", :boolean
before_validation do
self.send("#{name}=", nil) if send("remove_#{name}?")
end
end
end
end
end
Was able to achieve this with less code, by just implementing a delete_attachment on the model's side.:
class MyModel < ApplicationRecord
has_attached_file :image
def image_delete=(other)
self.image = nil if other == "1" or other == true
end
end
I have the following models set up:
class Contact < ActiveRecord::Base
belongs_to :band
belongs_to :mode
validates_presence_of :call, :mode
validates_associated :mode, :band
validates_presence_of :band, :if => :no_freq?
validates_presence_of :freq, :if => :no_band?
protected
def no_freq?
freq.nil?
end
def no_band?
band.nil?
end
end
class Band < ActiveRecord::Base
has_many :logs
end
class Mode < ActiveRecord::Base
has_many :logs
end
When I enter a frequency on my new view it allows for no band to be specified if a freq is entered. This creates a problem in my other views though because band is now nil. How do I allow for band not to be specified and just show up as empty on my index and show views, and then in the edit view allow one to be specified at a later point in time.
I have been able to get my index to display a blank by doing:
contact.band && contact.band.name
But I'm not sure if this is a best approach, and I'm unsure of how to apply a similar solution to my other views.
Many thanks from a rails newb!
In my views, I use the following for potentially nil objects in my views:
<%= #contact.band.name unless #contact.band.blank? %>
if your object is an array or hash, you can use the empty? function instead.
<%= unless #contacts.empty? %>
..some code
<% end %>
Hope this helps!
D
A couple years old but still a top Google result for "rails view handle nil" so I'll add my suggestion for use with Rails 3.2.3 and Ruby 1.9.3p0.
In application_helper.rb, add this:
def blank_to_nbsp(value)
value.blank? ? " ".html_safe : value
end
Then to display a value in a view, write something like this:
<%= blank_to_nbsp contact.band %>
Benefits:
"blank" catches both nil values and empty strings (details).
Simply omitting a nil object, or using an empty string, may cause formatting issues. pushes a non-breaking space into the web page and preserves formatting.
With the "if" and "unless" suggestions in other answers, you have to type each object name twice. By using a helper, you only have to type each object name once.
<%= #contact.try(:band).try(:name) %>
This will return nil if band or name do not exist as methods on their respective objects.
You can use Object#andand for this:
<%= #contact.band.andand.name %>
<%= #contact.band if #contact.band %> also works