ActiveAdmin: load and use models from other engine doesn't work - ruby-on-rails

I followed the guide in the wiki to use my models from my engine in my main-app with active admin.
The only thing I must change (that doesn't mentioned in the wiki) was this:
if defined?(ActiveAdmin)
ActiveAdmin.register Blog::Category do
end
end
I just added: Blog::.
In my engine "Blog" I added a model named "category" with a name:string attribute. But when I add one in active admin the field name wasn't saved in the database. My request parameters are:
{"utf8"=>"✓", "authenticity_token"=>"PzKDTcoJZ6Sy2tXgw9WSwXiR7aZp81lOtBvfD5Ec3F72H5L7MEMLjlOFgKWQBo2U4n9mPc7AgjcIS3MTIY2nZA==", "category"=>{"name"=>"asdasd"}, "commit"=>"Update Category", "id"=>"1"}
any ideas why it isn't saved in the databse?
When I create a new one, the record is created but without my input.

I ran into the same problem. I was able to get the resources to save properly by overriding the activeadmin controller actions (in e.g. app/admin/category.rb) like this
ActiveAdmin.register Blog::Category do
permit_params :the, :parameters, :for, :your, :resource
controller do
def create
#category = Blog::Category.new permitted_params[:category]
if #category.save
notice = 'Category was successfully created.'
redirect_to admin_blog_category_url(#category), notice: notice
else
render :new
end
end
end
end
I'm not sure exactly what's going on in the default case yet, but my guess is that the activeadmin controller action is not creating the object in the right namespace - that is, it does something like #category = Category.new rather than #category = Blog::Category.new. A similar override appears to be necessary for the :update action as well.

Related

Rails allow user to set default

I am working with a Rails application that allows users to create projects. Inside these projects, users can make lists. I am trying to figure out how to allow users to choose their "Default Working Project" from the projects index page. This would then propagate throughout the app, showing only lists associated with the current project. What is the best approach to making something like this.
You can achieve this easily by adding default_working_project_id field to your users table.
Then in your controller index set:
#default_working_project = current_user.default_working_project
In your user model add:
belongs_to :default_working_project, class_name: Project, foreign_key: :default_working_project_id
You can create your own action. In your routes file:
resources :projects do
member do
get 'set_default'
end
end
In your projects_controller:
def set_default
project.find params[:id]
current_user.default_working_project_id = project.id
respond_to do |format|
if current_user.save
format.html { redirect_to projects_path }
else
format.html { render 'index', notice: "your error message" }
end
end
end
In your views just add
link_to 'set default', set_default_project_path(project.id)
UPDATED
To remove current default project id from user:
You can make some methods to achieve this, like:
In your Project model
If you have a relation that project belongs_to user try this.
def is_a_current_project?
self.id == self.user.default_working_project_id
end
Then create an after_destroy :remove_current_project_relation callback method.
And the method, I recommend to add it inside your private methods:
def remove_current_project_relation
if is_a_current_project?
self.user.default_working_project = nil
end
end
Create a database field in Users table with default_project_id and set it.
On the model set:
def default_project
return projects.find_by_id(default_project_id) if default_project_id
false
end
And then, you can use something like this:
lists = user.default_project ? user.default_project.lists : user.lists
If only one user can see his projects and other users won't be able to see other user's projects, my suggestion is:
Make a boolean value is_default in the projects table. Add it with a migration.
Add :is_default to def project_params in the controller.
In the projects index page use:
<%= render #projects %>
Create file _project.html.erb in views/projects folder, add to it:
<%= form_for project, remote: true do |f| %>
# some project data
<%= f.check_box :is_default, class: 'project_default' %>
<% end %>
In projects_controller:
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
respond_to do |format|
format.html { redirect_to project_page } # this will run when you update project from edit page in form without 'remote: true'
format.js # this will run if you update project with 'remote: true' form
end
end
end
In projects.coffee in assets/javascripts folder:
$('input.project_default').change -> $(this).closest('form').submit()
Create update.js.erb in the views/projects folder, add to it:
$('#edit_project_<%= #project.id %>').replaceWith("<%= j render 'project' %>");
In projects_helper
def current_project
current_user.projects.find_by(is_default: true)
end
Maybe you'll need to change these a little, based on your tasks. This solution will update projects through JavaScript.
Also it would be great to add a method in the Project model, which will make the previous default project not default when the user makes other project default and so on.
When you need to use lists from default project you can use default_project.lists in your views.

How to write a duplicate record method in Ruby on Rails?

In my Rails app I have an invoices_controller.rb with these actions:
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
#invoice.build_item(current_user)
#invoice.set_number(current_user)
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice created."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
Essentially, the new method instantiates a new invoice record plus one associated item record.
Now, what sort of method do I need if I want to duplicate an existing invoice?
I am a big fan of Rails's RESTful approach, so I wonder if I should add a new method like
def duplicate
end
or if I can use the existing new method and pass in the values of the invoice to be duplicated there?
What is the best approach and what might that method look like?
Naturally, you can extend RESTful routes and controllers.
To be rally RESTful, it is important to look exactly, what you want.
i.e. if you want a new invoice and use an existing one as a kind of template, then it is comparable to a new action, and the verb should be GET (get the input form). As is it based on an existing invoice, it should reference that object. After that you would create the new invoice in the usual way.
So in you routes:
resources :invoices do
member do
get 'duplicate'
end
end
giving you a route duplicate_invoice GET /invoices/:id/duplicate(.format) invoices#duplicate
So in your view you can say
<%= link_to 'duplicate this', duplicate_invoice_path(#invoice) %>
and in your controller
def duplicate
template = Invoice.find(params[:id])
#invoice= template.duplicate # define in Invoice.duplicate how to create a dup
render action: 'new'
end
If I understand correctly your question you can:
resources :invoices do
collection do
get 'duplicate'
end
end
and with this you can do:
def duplicate
# #invoice = [get the invoice]
#invoice.clone_invoice
render 'edit' # or 'new', depends on your needs
end
clone_invoice could be a custom method which should have a invoice.clone call in your custom method.
If you question if you can use additional methods except REST, you absolutely can. Google, for example, encourage developers to use something, what they call "extended RESTful" on GoogleIO, http://www.youtube.com/watch?v=nyu5ZxGUfgs
So use additional method duplicate, but don't forget about "Thin controllers, fat models" approach to incapsulate your duplicating logic inside model.

Duplicate entries on nested attributes with decent-exposure

This is a re-edit of a previous post
I previously thought that this problem was cocoon related but now I don't think so because the following code doesn't even invoke cocoon
Every time I update my form that contains nested attributes the number of nested records doubles. From what I can gather this happens when the form is called because I immediately see an update before I do anything and the form is presented with with duplicate entries
I have the relevant code for my view in HAML below -
%h3 Household Members
= f.simple_fields_for :neighbors do |neighbor|
= render 'neighbor_fields', :f => neighbor
I am using decent-exposure with my controller so the controller looks like this:
class HouseholdsController < ApplicationController
expose(:households)
expose(:household, strategy: StrongParametersStrategy)
def create
if household.save
redirect_to households_path, notice: 'Household was successfully created.'
else
render 'new'
end
end
def update
if household.save
redirect_to households_path, notice: 'Household was successfully updated.'
else
render 'edit'
end
end
def destroy
household.destroy
redirect_to households_path, notice: 'Household deleted.'
end
How can I keep my nested attributes from doubling?
I've never used decent exposure before, but I have come across the same problem using nested forms, with and without cocoon, the cause was the same in both cases. It has to do with strong parameters, and not white listing the :id of the nested attribute.
I'm not sure I exactly get what you're trying to do, so I'll give a classic kind of posts/comments example. If you had a form for posts, and you wanted to dynamically add comment fields, the strong parameters in your controller would look something like this.
params.require(:post).permit(:content, comments_attributes: [:id, :content, :_destroy])
You need to white list :id, :_destroy, and whatever other attributes your nested field has. If there's no :id associated with the comment, then rails considers that it's a new comment and makes a new record for it. When you white list the :id, then rails knows it's an existing object, and then just updates it.

Ruby on Rails controller design

When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.

Rails 3 Making a Form with two Models and Controllers

What I am trying to do is kinda complicated. Basically I have an order form and my client would like to be able to add and delete fields himself, such as different services you can purchase along with your item. So what I have done is I have made an orders controller and order model along with a field model and fields controller. How would I implement this now? My order model has a has_many :fields and my field model has a belongs_to :order, but aside from that I am stuck on how to implement this. So far in my orders controller i have a new and create method and heres what inside:
def new
#order = Order.new
#maybe i should put something like: #fields = Field.find(:all)
#title = "Order Form"
end
def create
#order = Order.new params[:order]
if #order.save
flash[:notice] = "Your order has been created"
redirect_to root_path
else
#title = "Order Form"
render 'new'
end
end
and in my fields controller I have a show new create edit update functions with nothing in them. What is the best practice to accomplish what I am trying to do?
Thanks in advance guys
You're looking for nested forms.
Check two screencasts:
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
It will even answer your need:
my client would like to be able to add
and delete fields himself

Resources