Creating a form that breaks up a has_many association? - ruby-on-rails

As referenced in my earlier post rails has_many manager, I am trying to create a polymorphic imaging system which will allow any item to inherit the ability to have a cover photo and additional photos.
To accomplish that kind of imaging system, I sided with a polymorphic model with belongs_to :imageable and extended its active record capabilities out to a module named Imageable.
My main question is, given that for example we have a class called Object, how can I create a form that only targets the first Object's has_many association (the cover), and then separately administer the other has_many associations?
The form would look like..
--- Form for cover photo ----
Upload button for object[image_attributes][0][public_id]
--- Form for additional photos ---
Upload button for object[image_attributes[1][public_id]
image.rb
class Image < ActiveRecord::Base
attr_accessible :public_id
# Setup the interface that models will use
belongs_to :imageable, :polymorphic => true
end
Imageable.rb
module Imageable
extend ActiveSupport::Concern
included do
has_many :images, :as => :imageable, :dependent => :destroy # remove this from your model file
accepts_nested_attributes_for :images
validates :images, :presence => { :message => "At least one image is required" }
end
def cover
cover = images.where(:cover => true).first
if not cover
return Image.new
end
return cover
end
def additional_images
images.where(:cover => false).all
end
end
Form
<%= form.semantic_fields_for :images do |image_fields| %>
<%= image_fields.cl_image_upload(:public_id, :crop => :limit, :width => 1000, :height => 1000,
:html => {:class => "cloudinary-fileupload"}) %>
...
The above produces appropriate routes like object[image_attributes][0][public_id]
Thanks!

I would recommend that you model your relationships slightly differently by using an explicit has_one relationship from the 'Object' to the cover Imageable and a separate has_many relationship for the additional images.
If you want all the images in the same collection then have a look at this post:
How to apply a scope to an association when using fields_for?
It explains how you can specify a 'scope' or subset of the entries in the has_many collection when setting up the fields_for helper. It should work with the semantic_fields_for helper as well since it simply wraps the Rails fields_for helper.

Related

Rails 3.2.8 & JQuery Tokeninput: Trying to add new records for has_many through relationship instead of replacing all current records in a form

I have a database of skills that relate to each other as prerequisites to each other. In an index of skills, I'd like to be able to search through other skills and add 1 or more as prerequisites. It's important to note that I ONLY want the user to be able to add prerequisites, not remove them, as that's taken care of through an up-down voting system. I'm using JQuery Tokeninput and actually have all of this working except for one thing: I can't figure out how to only add prerequisites, rather than replacing all the prerequisites for a particular skill on submit.
Models:
class Skill < ActiveRecord::Base
attr_accessible :skill_relationship_attributes, :prereq_tokens
has_many :skill_relationships
has_many :prereqs, :through => :skill_relationships
has_many :inverse_skill_relationships, :class_name => 'SkillRelationship', :foreign_key => "prereq_id"
has_many :inverse_prereqs, :through => :inverse_skill_relationships, :source => :skill
attr_reader :prereq_tokens
accepts_nested_attributes_for :skill_relationships, :allow_destroy => true
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
end
class SkillRelationship < ActiveRecord::Base
attr_accessible :skill_id, :prereq_id, :skill_attributes, :prereq_attributes
belongs_to :skill
belongs_to :prereq, :class_name => 'Skill'
end
JQuery:
$('#skill_prereq_tokens').tokenInput('/skills.json',
{ theme:'facebook',
propertyToSearch:'title',
queryParam:'search',
preventDuplicates:'true'
});
View:
<%= simple_form_for skill do |f| %>
<%= f.input :prereq_tokens %>
<%= f.submit %>
<% end %>
I feel a bit silly for not getting this before, but I solved my problem by changing how prereq_tokens became prereq_ids in my Skill model.
I just changed this:
def prereq_tokens=(ids)
self.prereq_ids = ids.split(",")
end
to this:
def prereq_tokens=(ids)
self.prereq_ids += ids.split(",")
end
That's it. That little plus sign before the equals sign. I hope this helps anyone else who codes too long without a break!

ActiveAdmin forms with has_many - belongs_to relationships?

I have the models Home and Photo, which have a has_many - belongs_to relationship (a polymorphic relationship, but I dont think that matters in this case). I am now setting up active admin and I would like admins to be able to add photos to homes from the homes form.
The photos are managed by the CarrierWave gem, which I dont know if will make the problem easier or harder.
How can I include form fields for a different model in the Active Admin Home form? Any experience doing something like this?
class Home < ActiveRecord::Base
validates :name, :presence => true,
:length => { :maximum => 100 }
validates :description, :presence => true
has_many :photos, :as => :photographable
end
class Photo < ActiveRecord::Base
belongs_to :photographable, :polymorphic => true
mount_uploader :image, ImageUploader
end
Try something like this in app/admin/home.rb:
form do |f|
f.inputs "Details" do
f.name
end
f.has_many :photos do |photo|
photo.inputs "Photos" do
photo.input :field_name
#repeat as necessary for all fields
end
end
end
Make sure to have this in your home model:
accepts_nested_attributes_for :photos
I modified this from another stack overflow question: How to use ActiveAdmin on models using has_many through association?
You could try this:
form do |f|
f.semantic_errors # shows errors on :base
f.inputs # builds an input field for every attribute
f.inputs 'Photos' do
f.has_many :photos, new_record: false do |p|
p.input :field_name
# or maybe even
p.input :id, label: 'Photo Name', as: :select, collection: Photo.all
end
end
f.actions # adds the 'Submit' and 'Cancel' buttons
end
Also, you can look at https://github.com/activeadmin/activeadmin/blob/master/docs/5-forms.md (See Nested Resources)
I guess you are looking for a form for a nested model. Take a look at following railscasts.
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2
I cannot tell you much about active_admin, but I think this should not make a difference in handling the nested model.
I have a has_one model, like this:
f.has_many :addresses do |a|
a.inputs "Address" do
a.input :street ... etc.
While this correctly reflects our associations for Address (which is a polymorphic model) using f.has_one fails. So I changed over to has_many and all's well. Except now we have to prevent our users from creating multiple addresses for the same entity.

How can I elegantly construct a form for a model that has a polymorphic association?

Here are my models:
class Lesson < ActiveRecord::Base
belongs_to :topic, :polymorphic => true
validates_presence_of :topic_type, :topic_id
end
class Subject < ActiveRecord::Base
has_many :lessons, :as => :topic
end
class Category < ActiveRecord::Base
has_many :lessons, :as => :topic
end
Now, what I need is a form that will allow the user to create or update Lessons. The questions is, how can I provide a select menu that offers a mix of Subjects and Categories? (To the user, on this particular form, Subjects and Categories are interchangeable, but that's not the case elsewhere.)
Ideally, this would look something like this:
views/lessons/_form.html.haml
= simple_form_for(#lesson) do |f|
= f.input :title
= f.association :topic, :collection => (#subjects + #categories)
That won't work because we'd only be specifying the topic_id, and we need the topic_types as well. But how can we specify those values?
I guess the crux of the problem is that I really want a single select menu that specifies two values corresponding to two different attributes (topic_id and topic_type). Is there any elegant railsy way to do this?
A few notes:
a) Single table inheritance would make this issue go away, but I'd like to avoid this, as Categories and Subjects have their own relationship… I'll spare you the details.
b) I might could pull some javascript shenanigans, yes? But that sounds messy, and if there's a cleaner way to do it, some magic form helper or something, then that's certainly preferable.
c) Though I'm using simple_form, I'm not wedded to it, in case that's complicating matters.
Thanks
If you don't wish to use STI, you can do something similar: create a new model Topic(name:string) which will polymorphically reference Subject or Category.
class Lesson < ActiveRecord::Base
belongs_to :topic
validates_presence_of :topic_id
end
class Topic < ActiveRecord::Base
belongs_to :topicable, :polymorphic => true
end
class Subject < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
class Category < ActiveRecord::Base
has_one :topic, :as => :topicable
has_many :lessons, :through => :topic
accepts_nested_attributes_for :topic
end
In the view where you create a new Subject/Category:
<%= form_for #subject do |subject_form| %>
<%= subject_form.fields_for :topic do |topic_fields| %>
<%= topic_fields.text_field :name %>
<% end %>
<% end %>
After thinking this through, the less dirty implementation IMO would be to hire the JS shenanigans (b):
= f.input_field :topic_type, as: :hidden, class: 'topic_type'
- (#subjects + #categories).each do |topic|
= f.radio_button :topic_id, topic.id, {:'data-type' => topic.class.name, class: 'topic_id'}
With a sprinkle of JS (your needs may vary):
$('input:radio.topic_id').change(function() {
$('input:hidden.topic_type').val($(this).attr('data-type'));
});
Notes:
I use a radio button to select a topic (category or subject) in a list
The class name of each of possible topic is stored in an attribute 'data-type'
When a radio button is selected, the class name is copied to the hidden input via JS
Using: HTML5, jQuery, haml, simple_form

Using fields from an association (has_many) model with formtastic in rails

I searched and tried a lot, but I can't accomplish it as I want.. so here's my problem.
class Moving < ActiveRecord::Base
has_many :movingresources, :dependent => :destroy
has_many :resources, :through => :movingresources
end
class Movingresource < ActiveRecord::Base
belongs_to :moving
belongs_to :resource
end
class Resource < ActiveRecord::Base
has_many :movingresources
has_many :movings, :through => :movingresources
end
Movingresources contains additional fields, like quantity. We're working on the views for 'bill'. Thanks to formtastic to simplify the whole relationship thing by just writing
<%= form.input :workers, :as => :check_boxes %>
and i get a real nice checkbox list. But what I haven't found out so far is: How can i use the additional fields from 'movingresource', next or under each checkbox my desired fields from that model?
I saw different approaches, mainly with manually looping through an array of objects and creating the appropriate forms, using :for in a form.inputs part, or not. But none of those solutions were clean (e.g. worked for the edit view but not for new because the required objects were not built or generated and generating them caused a mess).
I want to know your solutions for this!
Okay, I missed the revolution of accepts_nested_attributes_for, this explains why it's not really working.
This got me a big step further, but I think somewhere I will still have some complications with my complex relations ^_^
class Moving < ActiveRecord::Base
has_many :movingworkers, :dependent => :destroy
has_many :workers, :through => :movingworkers
accepts_nested_attributes_for :movingworkers
end
<% form.inputs :for => :movingworkers do |movingworker| %>
<%= movingworker.inputs :worker, :quantity %>
<% end %>
Formtastic's :label_method option might help. E.g.
<%= form.input :movingworkers, :label_method => :worker %>
or
<%= form.input :movingworkers, :label_method => Proc.new { |x| "#{x.worker} #{x.quantity}" } %>
If the fields don't exist in the new view, you can just test if it is new (new_record?) and present a different set of fields (if you wrap into a partial in can be quite a clean approach).

Rails nested form with has_many :through, how to edit attributes of join model?

How do you edit the attributes of a join model when using accepts_nested_attributes_for?
I have 3 models: Topics and Articles joined by Linkers
class Topic < ActiveRecord::Base
has_many :linkers
has_many :articles, :through => :linkers, :foreign_key => :article_id
accepts_nested_attributes_for :articles
end
class Article < ActiveRecord::Base
has_many :linkers
has_many :topics, :through => :linkers, :foreign_key => :topic_id
end
class Linker < ActiveRecord::Base
#this is the join model, has extra attributes like "relevance"
belongs_to :topic
belongs_to :article
end
So when I build the article in the "new" action of the topics controller...
#topic.articles.build
...and make the nested form in topics/new.html.erb...
<% form_for(#topic) do |topic_form| %>
...fields...
<% topic_form.fields_for :articles do |article_form| %>
...fields...
...Rails automatically creates the linker, which is great.
Now for my question: My Linker model also has attributes that I want to be able to change via the "new topic" form. But the linker that Rails automatically creates has nil values for all its attributes except topic_id and article_id. How can I put fields for those other linker attributes into the "new topic" form so they don't come out nil?
Figured out the answer. The trick was:
#topic.linkers.build.build_article
That builds the linkers, then builds the article for each linker. So, in the models:
topic.rb needs accepts_nested_attributes_for :linkers
linker.rb needs accepts_nested_attributes_for :article
Then in the form:
<%= form_for(#topic) do |topic_form| %>
...fields...
<%= topic_form.fields_for :linkers do |linker_form| %>
...linker fields...
<%= linker_form.fields_for :article do |article_form| %>
...article fields...
When the form generated by Rails is submitted to the Rails controller#action, the params will have a structure similar to this (some made up attributes added):
params = {
"topic" => {
"name" => "Ruby on Rails' Nested Attributes",
"linkers_attributes" => {
"0" => {
"is_active" => false,
"article_attributes" => {
"title" => "Deeply Nested Attributes",
"description" => "How Ruby on Rails implements nested attributes."
}
}
}
}
}
Notice how linkers_attributes is actually a zero-indexed Hash with String keys, and not an Array? Well, this is because the form field keys that are sent to the server look like this:
topic[name]
topic[linkers_attributes][0][is_active]
topic[linkers_attributes][0][article_attributes][title]
Creating the record is now as simple as:
TopicController < ApplicationController
def create
#topic = Topic.create!(params[:topic])
end
end
A quick GOTCHA for when using has_one in your solution.
I will just copy paste the answer given by user KandadaBoggu in this thread.
The build method signature is different for has_one and has_many associations.
class User < ActiveRecord::Base
has_one :profile
has_many :messages
end
The build syntax for has_many association:
user.messages.build
The build syntax for has_one association:
user.build_profile # this will work
user.profile.build # this will throw error
Read the has_one association documentation for more details.

Resources