Rails create multiple interrelated objects in one form - ruby-on-rails-4

I have a Vehicles model where each vehicle has a make and model. When a user creates a vehicle, they can either select from currently available makes and models, or they can create a new make and model. Both make and model contain no extra data, so they are stored in the CommonLookup model I use for dynamic enumerations.
I want to limit model choices through ajax based on the currently selected make. To do this, I've created a blongs_to relationship on the CommonLookup model to itself; in other words, any record of that type can optionally reference a parent record of the same type in a many-to-one relationship.
The problem I'm running into is actually saving the relationship. My model code, which works for creating non-related make and model records, is as follows:
class Vehicle < ActiveRecord::Base
belongs_to :make, :class_name => "CommonLookup", :foreign_key => "make_id"
belongs_to :model, :class_name => "CommonLookup", :foreign_key => "model_id"
attr_accessor :new_make_name
attr_accessor :new_model_name
before_save :create_make_from_name, :create_model_from_name
def create_make_from_name
create_make(
:value => new_make_name
)
end
def create_model_from_name
create_model(
:value => new_model_name
)
end
end
This code successfully creates a vehicle with the associated new make and model, but the new make and model are not associated with each other as I need them to be. I need a many-models to one-make relationship that I can use to easily limit choices. To be clear, this question has nothing to do with the ajax part that is necessary for limiting choices; I'm focusing on the creation of the model instances themselves so that they are related, all from a single form.
I've attempted to set up code in the create_model_from_name callback but to no avail; there is no accessible reference to the object created in the first callback that could be used to set up the relationship. What I tried:
def create_model_from_name
create_model(
:value => new_model_name,
:parent => :make
)
end
But this didn't work. Any help would be greatly appreciated.

I solved this by combining the before_save callbacks and including a little more logic:
class Vehicle < ActiveRecord::Base
belongs_to :make, :class_name => "CommonLookup", :foreign_key => "make_id"
belongs_to :model, :class_name => "CommonLookup", :foreign_key => "model_id"
attr_accessor :new_make_name
attr_accessor :new_model_name
before_save :create_make_and_model_from_names
def create_make_and_model_from_names
if not new_model_name.blank?
if not new_make_name.blank?
create_model(
:value => new_model_name,
:parent => create_parent(
:value => new_make_name
)
)
else
create_model(
:value => new_model_name,
:parent => model
)
end
end
end
end
This met my requirements by setting up a way of creating new makes and creating and associating new models with both new makes and existing makes. The end result is that my ajax request is much easier to set up because I can easily locate the models associated with each make and place these in the options list.

Related

Rails 4: stack level too deep when trying to destroy picture

Hi I have a normal setup of Paperclip and S3 for image uploads in my application, this is the model I use for attachments:
class Picture < ActiveRecord::Base
belongs_to :ofert, dependent: :destroy
has_attached_file :image, :styles => { :medium => "300x300#", :thumb => "100x100>", :large => "600x400#", :morethumb => "50x50#", :ultrathumb => "25x25#" },
:default_url => "https://s3-sa-east-1.amazonaws.com/:s3_bucket/ofert_defaults/:style/brown_hat.jpg"
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
validates_attachment_presence :image, :if => :isProduction?
validates_attachment_size :image, :less_than => 5.megabytes
#process_in_background :image, processing_image_url: 'https://s3-sa-east-1.amazonaws.com/:s3_bucket/ofert_defaults/:style/brown_hat.jpg'
end
The above works very well, however, when I try to destoy a picture:
picture.destroy
I get the following error: stack level too deep
but if instead I do the following:
picture.delete
It works, however the above only deletes the record but not the file uploaded to my S3 bucket, any idea?
It is a bug in rails. Read here
Using
belongs_to :ofert, dependent: :destroy
will cause a circular loop (assuming you have a similar line in the associated model 'Ofert' as well)
You can try replacing it with dependent :delete in one of these models or write after_destroy methods in both to manually destroy the associated model.
Read this discussion here on stackoverflow

Rails 4 - Thinking Sphinx filter using field on related model

In my Rails 4 app I have models like:
'AgencyGroup'
has_many :group_agencies
has_many :agencies, :through => :group_agencies
Where I keep 'token' field
'Agency' model
has_many :group_agencies
has_many :agency_groups, :through => :group_agencies
has_many :advertisements
'Advertisement' model
belongs_to :agency
I use Thinking Sphinx and it works really greate but now I got new requirement to filter 'Advertisements' by AgencyGroup token fields.
Basically I need to find advertisment with some parameters but only for agencies that are in agency group with posted token.
if params[:search]
#results = Advertisement.search Riddle::Query.escape(params[:search]), :star => true, :page => params[:page], :per_page => 6
end
To get results I run http query like this:
http://api.localhost.local:3000/v1/advertisements?token=JHW_tdXn5g-vQY1f_ZzLuw&search=Nissim
What I'm missing? How to use relation between models in TS?
I think the best approach here involves a few steps:
Step 1: Add AgencyGroup IDs as an attribute in your Advertisement index. If you're using SQL-backed indices (:with => :active_record), it's a one-liner:
has agency.group_agencies.agency_group.id, :as => :agency_group_ids
If you're using real-time indices, then you'll want a method in Advertisement that returns all of those IDs:
def agency_group_ids
agency.agency_group_ids
end
And your attribute definition will look like this:
has agency_group_ids, :type => :integer, :multi => true
Step 2: Because you've changed your index structure, don't forget to rebuild your indices:
# for SQL-backed indices:
rake ts:rebuild
# or, for real-time indices
rake ts:regenerate
Step 3: In your controller, find the agency group for the given token:
agency_group = AgencyGroup.find_by :token => params[:token]
Step 4: Finally, use that agency group's id in your search call:
#results = Advertisement.search Riddle::Query.escape(params[:search]),
:star => true,
:page => params[:page],
:per_page => 6,
:with => {:agency_group_ids => agency_group.id}

Nested form with nested has_many, and has_one inside

I am essentially running into the same issue as this post, though I have a slightly different situation: has_many nested form with a has_one nested form within it
But as someone else mentioned in that post, the provided answer does not solve the issue.
The relationship is set up so that Invoice has_many items and each Item has_one Modifier. I am attempting to make a single form_for Invoice that allows a use to create many items, each with a Modifier.
MODELS
class Invoice < ActiveRecord::Base
has_many :items
has_many :modifiers, through: :items
accepts_nested_attributes_for :items
end
class Item < ActiveRecord::Base
belongs_to :invoice
belongs_to :modifier
accepts_nested_attributes_for :modifier
end
class Modifier < ActiveRecord::Base
has_one :item
end
CONTROLLER
class Invoice
def new
#invoice = Invoice.new
end
def edit
end
...
end
VIEWS(haml)
invoice.html.haml:
= form_for #invoice do |f|
= f.text_field :status
= f.fields_for :items do |builder|
= render partial: "items/fields", locals: { :f => builder }
= link_to_add_association 'New Item', f, :items, partial: "items/fields", id: "add-item-button"
items/_fields.html.haml:
.nested-fields
- #item = #invoice.items.build
= f.fields_for :modifier, #item.build_modifier do |modifier|
= modifier.text_field :name
Let's review what's happening. In order to build the nested has_one relationship, I build an Item in the nested-fields partial so that I can build the has_one Modifier. This is because rails requires that you explicitly call 'build_something' in a has_one relationship (usually this is called in the controller's new, but I only want to build once someone has clicked the New Item button). For creating new Invoices, this code works perfectly. Checking console, I see that the relationship is created and I can verify that the Modifier was created successfully.
However, when I go back to edit the invoice, cocoon knows that I already have a modifier, so it calls the partial once to create the necessary fields_for my single Modifier. These fields are empty. This makes sense though, because as cocoon is rendering that partial, it is building a new Code with a new modifier and setting the fields blank. I can confirm that is what is occuring because once I have my modifier saved properly, I can go into my partial, remove the two build calls, and view the edit page which properly displays the saved Modifier info just fine.
Of course, now that I've removed my build calls, the form no longer saves any Modifiers that I create. So essentially, I need the build calls in there to build new Modifiers, but I can't have them in there if I want to view them.
Does anyone have a solution to this situation? I have found multiple stack overflow questions and none of them resolves this issue.
You say has_one but in your model I see has_many ?
You nested partial items/_fields is wrong: you build an extra item and there is not need for that. Coccon, in the link_to_add_association builds a new item to insert.
There are two ways to do what you want to do.
1) In the partial
To handle it correctly in your partial you can do the following (items/_fields.html.haml) :
.nested-fields
- f.object.build_modifier if f.object.new_record?
= f.fields_for :modifier do |modifier|
= modifier.text_field :name
In rails, to refer to the object of the form you can use f.object. Note this will work, but we have to check if it is a newly created object. Alternatively we could just check if the modifier is present.
2) Using the :wrap_object option ( documentation )
Cocoon allows to execute some extra code with the newly created object. So in your case that would become:
= link_to_add_association('New item', f, :items,
:wrap_object => Proc.new { |item| item.build_modifier; item })

undefined method `attr_accessible' with Mongoid

I am using Rails 4.1.1, ruby 2.1, mongodb, mongoid as a wrapper, rails_admin for creating admin interfaces
I know that 'attr_accessible' no longer works for Rails4. So i have installed 'protected_attributes' gem. But still no success i am still getting warning in my console
[RailsAdmin] Could not load model Company, assuming model is non existing. (undefined method `attr_accessible' for Company:Class)
So, rails admin do not load the class Company because i have defined attr_accessible in the model. Here is my company model.
class Company
include Mongoid::Document
##employees_strength = {0 => '0-10', 1 => '11-50', 2 => '51-100', 3 => '101-500', 4 => '501-1000', 5 => '1000+', 6 => '5000+'}
field :name, type: String
field :website, type: String
field :domain_name, type: String
field :strength, type: Integer
has_many :employees
has_one :admin, :class_name => 'Employee', :dependent => :destroy, :inverse_of => :organization
#attr_accessible :name, :website, :domain_name, :strength#, :admin_attributes, :allow_destroy => true
attr_accessible :admin_attributes
accepts_nested_attributes_for :admin, :allow_destroy => true
end
Please any can body can help?
Thanks
Mongoid 4 (<= 4.0.2 at the time of writing) does not know about the ActiveModel::MassAssignmentSecurity module provided by protected_attributes gem.
As such you must include the behaviour in your models manually e.g.
class SomeDocument
include Mongoid::Document
include ActiveModel::MassAssignmentSecurity
field :some_field
attr_accessible :some_field
end
However, this gets tedious pretty quickly so a reasonable alternative is to include the module into the Mongoid::Document module before any of your models are defined.
module Mongoid
module Document
include ActiveModel::MassAssignmentSecurity
end
end

Rails 4 routes with single table inheritance and self references

I've been jumping between design patterns, firstly trying polymorphic, now landing on STI. The main goal is to implement a Server > Host > Guest model where a Server has Hosts, Hosts have Guests and each able to have Posts. Although not the main purpose of the question any ideas in the design matter would be helpful as this is my first rails or ruby project.
What I have now is:
class Device
has_may :children, :class_name => "Device", :foreign_key => "parent_id"
belongs_to :parent, :class_name => "Device"
has_many :posts
end
class Server,Host,Guest < Device
end
STI is used because Server,Host,Guest basically have the same attributes.
I'm having trouble setting up the routes and controllers so I could view a Server's children which would be of type Host or to create a new Server's Host.
First, a good thing would be to add the following things, making everything easier to use for you :
class Device < ActiveRecord::Base
has_many :children, :class_name => "Device", :foreign_key => "parent_id"
has_many :servers, -> { where type: "Server" }, :class_name => "Device", :foreign_key => "parent_id"
has_many :hosts, -> { where type: "Host" }, :class_name => "Device", :foreign_key => "parent_id"
has_many :guests, -> { where type: "Guest" }, :class_name => "Device", :foreign_key => "parent_id"
belongs_to :parent, :class_name => "Device"
has_many :posts
end
With that, you will be able to do server.hosts, etc, which is quite convenient.
Then, you should move each subclass (Server, Host, Guest) to its own file due to Rails loading system. You can try to access the model Server in the console, you will get an undefined error. To fix it, you need to load the model Device, or simply move each subclass in a different file.
Finally, for the routing/controller part, I will advise you to read this post I wrote about common controller for STI resources : http://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-2/.
Note that this is the second part, for more details check out the other articles.