Formtastic checkboxes not getting checked when editing resource, when using MongoMapper - ruby-on-rails

With the following Store and Service models, managed with MongoMapper:
class Store
include MongoMapper::Document
key :service_ids, Array, :typecast => 'ObjectId'
many :services, :in => :service_ids
end
class Service
include MongoMapper::Document
key :name, String
many :stores, :foreign_key => :service_ids
end
I have this form, done with Formtastic:
<%= semantic_form_for #store, :url => admin_store_path(#store), :method => :put do |form| %>
<%= form.input :service_ids, :label => "Select Store Services",
:as => :check_boxes,
:collection => Service.all %>
<% end -%>
The controller uses Inherited Resources, and the edit action is implicit.
When editing a #store with services already associated with it, the checkboxes for the latter don't show as checked.
Formtastic's README warns it doesn't support MongoMapper officially, but it also says people have been using both together successfully, and I've seen some examples of this online.
I suspect Inherited Resources also doesn't support it, from what I've seen from Devise + Simple Form, both from the same authors and which don't support MM. They're working towards using an ORM adapter in their gems, but it isn't ready yet AFAIK.
And I've had problems with it already, I'm overriding the update action to get it to work:
def update
store = Store.find(params[:id])
if store.update_attributes!(params[:store])
flash[:notice] = 'Store was successfully updated.'
redirect_to admin_store_path(store)
else
redirect_to new_store_path
end
end
Does anybody know where the conflict with MM is, either in Formtastic or IR, and a hack just to get these checkboxes checking?

Most likely a Formtastic issue. It looks like the problem is here: https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/inputs/check_boxes_input.rb#L122
Formtastic calls #store.service_ids to find the selected boxes. Service_ids returns an array of ObjectId's, but Formtastic was expecting an array of Store objects. If we follow Formtastic's code we'll see it tries a couple methods to find out how to get the "value" out of those ObjectId's and will eventually settle on "to_s" (see https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/form_builder.rb#L20). Unfortunately, the to_s of an ObjectId is not the same as the id of your Store objects.
A hack that might make it work is to add an "id" method to ObjectId that returns self (Formtastic looks for id before it looks for to_s). A more appropriate patch would be to override this method https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/inputs/base.rb#L104 to properly introspect MongoMapper associations, so that you could write form.input :services and it would turn that into an input with name of "service_ids" while still using the services method of your object. With that change it would still properly call #store.services and find the same kind of objects as Store.all and just work.
If you want to go that route, Store.associations[:services] should get you MongoMapper's definition of the association which you can introspect (see https://github.com/jnunemaker/mongomapper/blob/master/lib/mongo_mapper/plugins/associations/base.rb) but note that associations have been refactored a bit since the 0.8.6 gem, they're now in the separate classes BelongsToAssociation, OneAssociation, and ManyAssociation that each inherit from Associations::Base.
So, it doesn't seem like there's a simple fix. The other option is to generate your checkboxes by hand.
(Aside: I'm a little confused by your update method because I'd expect IR to do exactly what you've written internally, but if you had to write it that way to get it to work, so it is...)

Related

Rails - Dynamically build deeply nested objects (Cocoon / nested_form)

I currently have a complex form with deep nesting, and I am using the Cocoon gem to dynamically add sections as required (e.g. if a user wants to add another vehicle to the sale form). Code looks like this:
<%= sale.fields_for :sale_vehicles do |sale_vehicles_builder| %>
<%= render :partial => "sale_vehicles/form", :locals => {:f => sale_vehicles_builder, :form_actions_visible => false} %>
<% end -%>
<div class="add-field-links">
<%= link_to_add_association '<i></i> Add Vehicle'.html_safe, sale, :sale_vehicles, :partial => 'sale_vehicles/form', :render_options => {:locals => {:form_actions_visible => 'false', :show_features => true, :fieldset_label => 'Vehicle Details'}}, :class => 'btn' %>
</div>
This works very nicely for the first level of nesting - the sale_vehicle object is built correctly by Cocoon, and the form renders as expected.
The problem comes when there is another level of nesting - the sale_vehicle partial looks like this:
<%= f.fields_for :vehicle do |vehicle_builder| %>
<%= render :partial => "vehicles/form", :locals => {:f => vehicle_builder, :f_parent => f, :form_actions_visible => false, :show_features => true, :fieldset_label => 'Vehicle Details'} %>
<% end -%>
The partial for the vehicle is rendered with no fields, because no sale_vehicle.vehicle object has been built.
What I need to do is thus build the nested object along with the main object (Cocoon does not currently build any nested objects), but how best to do this? Is there any way to select nested forms from the helper code so that these can be built?
Cocoon currently build the main object like this:
if instance.collection?
f.object.send(association).build
else
f.object.send("build_#{association}")
end
If I could do something like the following, it would keep things nice and simple, but I'm not sure how to get f.children - is there any way to access nested form builders from the parent form builder?
f.children.each do |child|
child.object.build
end
Any help appreciated to get this working, or suggest another way of building these objects dynamically.
Thanks!
EDIT: Probably worth mentioning that this question appears to be relevant to both the Cocoon gem mentioned above, and also Ryan Bates' nested_form gem. Issue #91 for the Cocoon gem appears to be the same problem as this one, but the workaround suggested by dnagir (delegating the building of the objects) is not ideal in this situation, as that will cause issues on other forms.
I can see in your second nested form there is no link_to_add_association.
Inside cocoon, the link_to_add_association does the building of a new element, for when a user wants to dynamically add it.
Or, are you implying that once a sale_vehicle is built, it should automatically contain a vehicle? I would assume a user would have to select the vehicle that is sold?
I have a test-project that demonstrates the double nested forms: a project has tasks, which can have sub-tasks.
But maybe that does not relate good enough to what you want to do?
You do not show your models, but if I understand correctly the relations are
sale
has_many :sale_vehicles
sale_vehicle
has_one :vehicle (has_many?)
So if you have a sale_vehicle that can have a vehicle, then I would assume your user would first add the sale_vehicle to the sale and then, click the link to add the vehicle. That is what cocoon can do perfectly well. If, on the other hand, you want that when cocoon dynamically creates a sale_vehicle, a vehicle is also created, I see a few different options.
Use after_initialize
Can't say I am a real fan of this, but in the after_initialize callback of your sale_vehicle, you could always build the required vehicle model.
I am assuming here that since your sale_Vehicle is not valid/cannot exist without a vehicle model, it is the responsability of the model to create the nested model immediately on build.
Note that after_initialize is executed for each object creation, so this could be costly. But this could be a quick fix. If you reject empty nested models, this should work imho.
Use Decorator/Presenter
For the user, the sale_vehicle and vehicle seem one object, so why not create a decorator, composed of a sale_vehicle and a vehicle, which is presented into one (nested) form and when saving this, the decorator knows it needs to be saved into the correct models.
Note: there are different terms for this. A decorator usually only extends a single class with some view-methods, but it could as well be a composition of different models. Alternativd terms: presenter, view-model.
Anyway, the function of a decorator/presenter is to abstract away the underlying datamodel for your users. So, for whatever reason you needed to split up a single entity into two database models (e.g. to limit nr of columns, to keep models readable, ...) but for the user it still is a single entity. So "present" it as one.
Allow cocoon to call a custom build method
Not sure if I am a fan of this, but this definitely is a possibility. It is already supported if the "nested model" is not an ActiveRecord::Association, so this should not be too hard to add that. But I am hesitant about that addition. All those options make it more complicated.
EDIT: The simplest fix
Inside your partial just build the needed child object. This has to happen before the fields_for and then you are good to go. Something like
<% f.object.build_vehicle %>
<%= f.fields_for :vehicle do |vehicle_builder| %>
<%= render :partial => "vehicles/form", :locals => {:f => vehicle_builder, :f_parent => f, :form_actions_visible => false, :show_features => true, :fieldset_label => 'Vehicle Details'} %>
<% end -%>
Conclusion
I personally really like the decorator approach, but it could be a bit heavy. Just build the object before you render call the fields_for, that way you are always sure there is at least one.
I am interested to hear your thoughts.
Hope this helps.

Populate a form with the new()-method

Problem solved. HTML5 localStorage messed with me.
I'm trying to populate a form with parameters from the new()-method, and I can't get it to work.
Every user has default values for the form saved in the database(in a table called defaults), and when you create a new record I want it to be populated with the values from that table.
#default = Default.find_by_user_id(current_user.id)
#invoice = Invoice.new(:title => #default.title, :company_information => #default.company_information)
render 'create'
and then in my view:
form_for #invoice, :url => { :action => "create"} do |f| ...
What happens is that the values that are default for invoice are created, but not the ones created in the new()-method.
The weirdest part is that when I check the source code after the page is loaded, the inputs value attributes is filled with the correct information, but not rendered on the page...
What you're doing here:
Invoice.new(:title => #default.title, :company_information => #default.company_information)
Makes sense and should work…unless those fields are protected from mass assignment.
class Invoice << ActiveRecord::Base
attr_accessible :some, :other, :fields
...
end
This would allow you to set :some, :other, (and) :fields when you initialize your Invoice object, but it will prevent you from setting any other "attributes".
Strange, I don't see anything wrong with what you are trying to do... maybe something on the browser side (javascript, css, etc) is fowling things up?
Check to see if there is something selectable inside the form inputs or try creating a vanilla form without any javascript or css. Or, you might even try simply printing the contents of the attribute in the html (without using input/textarea tags) using something like:
<%= #invoice.title %>
This will at least help confirm that the default values where indeed set. Additionally, using:
<%= f.object.title %> # place me inside the form_for block
will help you confirm that the form builder instance also has the correct value.
Good luck.

Rails - default value in text_field but only for new_record?

On a Content model have an attribute named slug. When creating a new record, I want to use a helper to populate this field, but on an existing record I want to use the value from the database.
Currently I have:
<% if #content.new_record? %>
<%= f.text_field :slug, :value => "#{generate_slug(6)}" %>
<% else %>
<%= f.text_field :slug %>
<% end %>
But that seems a bit verbose. Is this the best way, or is there no other way? (Rails newb just trying to find the "Rails way" on issues I'm unsure of)
Edit
I should note that the helper is currently in /app/helpers/application_helper.rb Moved to be a private action in the Contents controller. David's answer worked great.
In your controller
#content.slug ||= generate_slug(6)
This will assign a value to the slug attribute if none is present
Then, in your view you can simply use
<%= f.text_field :slug %>
Options
Try after_initialize callback in your model.
Try creating a method in your model where you set defaults and call it in your new action in the controller. Also call this method if your create fails and you render new. Remember to set default only when no value exists by using the ||= operator.
Example to follow. I'm typing on phone!
I happen to use jQuery in my projects, so when I want some functionality like this, I usually use something like labelify. Then, I'd use something like <%= f.text_field :slug, :title => generate_slug(6) %>. (Hot tip, you don't need to put the #generate_slug call inside of a string if it returns something that will resolve to a string by itself, in fact it's more performant if you don't.)
If you don't want to go with jQuery approach, you might want to wrap this piece of logic in your model.
def Content < ActiveRecord::Base
def slug
self.new_record? ? self.slug_for_new_record : attributes[:slug]
end
private
def slug_for_new_record
# I don't know what you're doing in generate_slug, but it sounds model-
# related, so if so, put it here and not in a helper
end
end
If it really belongs in the view, still another option is to just make your Ruby a little bit more concise (you'll have to judge if this is more readable):
<%= f.text_field :slug, :value => (generate_slug(6) if #content.new_record?) %>
Don't forget the parens surrounding (generate_slug(6) if #content.new_record?). If you do, the if will be applied to the text_field, which is not what you want.
But there are still more ways to do it. The above line of code isn't great if your logic might change and you're pasting this code all over your rails project. When I wanted to add a 'required' class to my text fields but only if they were a new record (we had some legacy data that we didn't want to make people clean up), I created my own form builder with a required_field method that just called text_field and added a 'required' class if the item was a new record. This might seem like a work, but we have around 20 different forms, each with potentially multiple required fields, and it's a lot easier to change the business logic in one place. So if you really think this logic belongs in the view but you've got a ton of these lines of code and you don't want to have to change it in a million places, then FormBuilder is the way to go. I think this is in most cases prettier and more appropriate than a helper, but again, beauty is in the eye of the beholder. Here's my code somewhat adapted for your case:
# config/environment.rb
ActionView::Base.default_form_builder = NamespacesAreFun::FormBuilder
# lib/namespaces_are_fun/form_builder.rb
module NamespacesAreFun
class FormBuilder < ActionView::Helpers::FormBuilder
def slug_field(method, options = {})
opts = options.to_options
opts.merge!(:value => generate_slug) if self.object.new_record?
text_field(method, opts)
end
end
end
# views/.../your_view.html.erb
<%= f.slug_field :slug %>
Hopefully in all of these different approaches is one that fits your project.

One model and controller but slightly different views and routes in rails3

I have a model Notifications, and it basically handles the same few things. A simple contact form, an invitations form, etc. They all have the same generic items... ie Name, email, comment, blah.
There might be one slightly different field but they are optional, so I'd like to treat them as one model with a differentiating field called: notification_type so Invitation or Feedback, etc. And only render different views which i created in subfolders under notifications (notifications/invitations/).
I have it working fine with something like this in my routes:
routes
resources :notifications
match 'invites' => 'notifications#new', :defaults => { :notification_type => 'invitation' }
I pass the notification_type...
new.html.erb
<% if params[:notification_type] or params[:notification][:notification_type] == "invitation" %>
<%= render "notifications/invitations/form" %>
<% end %>
form.html.erb
I pass a hidden field for the notification_type
<%= f.input :notification_type, :as => 'hidden', :input_html => { :value => #notification.notification_type ||= params[:notification_type] } %>
It all seems to work.. the only caveat being that if they create an error, it sends them to the /notification route instead of being in invites.. but it still works correctly otherwise but I'm wondering it there's a simpler way to do the same thing? From within the controller layer? I feel like something's going to surprise me later as it stands.
I'd do the inheritance in the model and have two controllers. My controllers are as small as possible so normally I'm ok with adding many of them. But this is a pretty neat solution outside of the form errors. I think your hunch might be right as it gets more complex.
So to be clear, I'd have to models Invitation and Feedback. Each model would set_table_name to "notifications", inherit from Notification and then set the default notification in the initialize method or use the :defaults block you have in your routes already. This is a bit more flexible and explicit imo.

Composite keys with ActiveScaffold in Ruby On Rails

I am developing RoR application that works with legacy database and uses ActiveScaffold plugin for fancy CRUD interface.
However one of the tables of my legacy db has composite primary key. I tried using Composite Keys plugin to handle it, but it seems to have conflicts with ACtiveScaffold: I get the following error:
ActionView::TemplateError (Could not find column contact,type) on line #3 of ven
dor/plugins/active_scaffold/frontends/default/views/_form.rhtml:
1: <ol class="form" <%= 'style="display: none;"' if columns.collapsed -%>>
2: <% columns.each :for => #record do |column| -%>
3: <% if is_subsection? column -%>
4: <li class="sub-section">
5: <h5><%= column.label %> (<%= link_to_visibility_toggle(:default_visible =
> !column.collapsed) -%>)</h5>
6: <%= render :partial => 'form', :locals => { :columns => column } %>
vendor/plugins/active_scaffold/lib/data_structures/sorting.rb:16:in `add'
while having in model code smth like:
set_primary_keys :contact, :type
I highly appreciate any idea how I can get composite keys capability with ActiveScaffold.
I think your best bet may be checking the ActiveScaffold Google Group as it's monitored by core developers of ActiveScaffold and they would ultimately be able to solve your problem and explain why composite keys with the plugin won't work with ActiveScaffold.
Good luck and be sure to post a follow-up if you do get results from the Google Group (which I have posted on before and received feedback very quickly).
One quick result I did find was this.
What I did was to create a facade class that does not inherit from
ActiveRecord then make the "id" show the primary key. In my case the
primary key was computed from other data and could change as a result
of an edit, so I had to override ActiveScaffold in a few places to
allow for the primary key changing after an update. But, all in all
it works and is fairly straightforward. Start with an empty class
and just resolve messages that are not understood. In your case you
could even just redirect all messages to a wrapped ActiveRecord while
replacing the id and id= methods, and filtering the [] and []= methods.
That may do the trick for you.
No, I have not received any reply from the group and I am not sure if ActiveScaffold is actively maintained yet.
After some time playing with ActiveScaffold, I ended up implementing my own CRUD interface from the scratch.
I have this working, with read-only models, using ActiveScaffold on a legacy DB.
The trick was to override the default 'id' field in the model and return a concatenated PK string.
If that's good enough, then here you go:
class CPKReadonlyModel < ActiveRecord::Base
set_primary_key :id_one # only half of it, but id overridden below...
def id
self.id_one.to_s + ',' + self.id_two.to_s
end
def readonly?
true
end
def before_destroy
raise ActiveRecord::ReadOnlyRecord
end
def delete
raise ActiveRecord::ReadOnlyRecord
end
def self.delete_all
raise ActiveRecord::ReadOnlyRecord
end
end
The controller has the following in the active_scaffold config block:
config.actions.exclude :create, :update, :delete

Resources