Rails link_to polymorphic attribute that is potentially nested - ruby-on-rails

I have a Region > Store > Device hierarchy, where each of these things can have a config_set.
class Region < ActiveRecord::Base
has_many :stores, dependent: :destroy
has_one :config_set, as: :configurable
end
class Store < ActiveRecord::Base
has_many :devices
has_one :config_set, as: :configurable
belongs_to :region
end
class Device < ActiveRecord::Base
has_one :config_set, as: :configurable
belongs_to :store
end
class ConfigSet < ActiveRecord::Base
belongs_to :configurable, polymorphic: true
end
From the config_set show page, I want to generate a link to whatever configurable that config_set belongs to. link_to is using polymorphic_url internally, which wants either a #device, a [#store, #device], or a [#region, #store, #device]. Is there a simple way to deal with this?

Found out a not very nice solution abusing the fact that to polymorphic_url #device and [nil, #device] are equivalent, combined with ruby's try :
<%= link_to 'Back', [#config_set.configurable.try(:store).try(:region), #config_set.configurable.try(:store) || #config_set.configurable.try(:region), #config_set.configurable] %>

Related

Solution for nested form

I have been stuck on this problem for a while.
Need to make a form for competitions category with custom inputs. It should take all values from Information table and build the inputs, but the tricky part is that it should be saved to Category_informations table.
class Competition < ApplicationRecord
has_many :categories
has_many :informations
end
class Category < ApplicationRecord
belongs_to :competetion
has_many :category_informations
has_many :information, through: competition
end
class CategoryInformation
belongs_to :catagory
belongs_to :information
end
class Information < ApplicationRecord
belongs_to :competetion
has_many :category_informations
end
Competition -> name
Category -> name, competition_id
Information -> name, competition_id
Category_informations -> value, category_id, information_id
Take a look at this gem: https://github.com/plataformatec/simple_form
Simple Form aims to be as flexible as possible while helping you with powerful components to create your forms.
Let's take a simple example:
class Machine < ActiveRecord::Base
has_many :parts , inverse_of: :machine
accepts_nested_attributes_for :parts
end
class Part < ActiveRecord::Base
# name:string
belongs_to :machine
end
With these models, we can use simple_form to update the machine and its associated parts in a single form:
<%= simple_form_for #machine do |m| %>
<%= m.simple_fields_for :parts do |p| %>
<%= p.input :name %>
<% end %>
<% end %>
For 'new' action, build the nested model from the controller:
class MachinesController < ApplicationController
def new
#machine = Machine.new
#machine.parts.build
end
end
Source: https://github.com/plataformatec/simple_form/wiki/Nested-Models
Sounds to me like you're looking for accepts_nested_attributes_for
See:
https://apidock.com/rails/v3.2.3/ActiveRecord/NestedAttributes/ClassMethods/accepts_nested_attributes_for
https://rubyplus.com/articles/3681-Complex-Forms-in-Rails-5
Also, check out the cocoon gem.

Polymorphic, STI or other

I am unsure of the best way to approach this scenario and would appreciate some direction. Essentially I have a scenario whereby I need to record assets for an organisation.
There are various types of assets so the attributes differ from type to type however there are a number of fields common to all assets such as:
location
make
model
colour
purchase_date
warranty_period
Similar to:
How to design a product table for many kinds of product where each product has many parameters
I had through of creating this as
one-to_many between Organisation and Asset
polymorhpic between Asset and Details
class Organisation < ApplicationRecord
has_many :assets
end
class Asset < ApplicationRecord
belongs_to :organisation
belongs to :detail, polymorphic: true
end
class CarAsset < ApplicationRecord
has_one :asset, as: :detail
end
class ComputerAsset < ApplicationRecord
has_one :asset, as: :detail
end
My question is:
I wish to create the asset & detail in a single form action so the user after selecting the asset type makes a single form entry for both models.
The user would click a link on the organisation show page:
<%= link_to "New car asset", new_organisation_asset_path(#organisation, query: :new_car_asset) %>
The in my controller I could do something similar to:
class AssetsController < ApplicationController
def new
#organisation = Organisation.find(params["organisation_id"])
#asset = #organisation.assets.new
case params[:query]
when "new_car_asset"
#details = #asset.car_assets.new
when "new_computer_asset"
#details = #asset.computer_assets.new
end
end
end
In my view I could also check the value of params[:query] and render the corresponding form partial that relates to the asset type.
Is this going down the correct path or is there a better way to achieve this? It does feel quite clunky.
I think it would be maybe better to use has_many :trough, should get more out of it in a long run. Like this:
class Organisation < ApplicationRecord
has_many :cars, through: assets
has_many :cumputers, through: assets
has_many :locations, through: assets
has_many :purchase_date, through: assets
end
class Asset < ApplicationRecord
belongs_to :organisation
belongs_to :cars
belongs_to :cumputers
belongs_to any :locations
belongs_to :purchase_date
end
class Car < ApplicationRecord
has_one :organisation, through: assets
end
class Cumputer < ApplicationRecord
has_one :organisation, through: assets
end
class Location < ApplicationRecord
has_one :organisation, through: assets
end
class Purchase_date < ApplicationRecord
has_one :organisation, through: assets
end
Then you can create Assets within Organisations_controller and can nest everything in Organisation form with fields_for. Assets model would contain references between Organisation and every single detail model, but everything would be sepperate if you wonna do more with it in views or special fields.

Rails: alternatives to using nested loops to find match in controller

There has to be a better way to do this. My Favorite model belongs to User while Applicant belongs to both Gig and User. I am trying to efficiently determine whether a user has applied for Gig that was favorited (<% if #application.present? %>).
I tried chaining the collection by using something like #favorites.each.gig to no avail. While the below index action for Favorites seems to work, it's really verbose and inefficient. What is a more succinct way of doing this?
def index
#favorites = Favorite.where(:candidate_id => current_candidate)
#applications = Applicant.where(:candidate_id => current_candidate)
#favorites.each do |favorite|
#applications.each do |application|
if favorite.gig.id == application.id
#application = application
end
end
end
end
class User
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :candidate
belongs_to :gig
end
class Applicant < ActiveRecord::Base
belongs_to :gig
belongs_to :candidate
end
class Candidate < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants
has_many :favorites
end
class Gig < ActiveRecord::Base
belongs_to :employer
has_many :applicants
has_many :favorites
has_many :users, :through => :applicants
end
For lack of a better answer, here's my idea:
--
User
Your user model should be structured as such (I just highlighted foreign keys, which I imagine you'd have anyway):
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :applicants
has_many :gigs, :through => :applicants, foreign_key: "candidate_id"
has_many :favorites, foreign_key: "candidate_id"
end
This means you'll be able to call:
current_candidate.favorites
current_candidate.applicants
This will remove the need for your #applications and #favorites queries
--
Favorite
You basically want to return a boolean of whether applicant is part of the favorite model or not. In essence, for each favorite the candidate has made, you'll be able to check if it's got an application
I would do this by setting an instance method on your favorites method using an ActiveRecord Association Extension, like so:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :favorites do
def applied?
self.applicant.exists? proxy_association.owner.gig.id
end
end
end
This will allow you to call:
<%= for favorite in current_candidate.favorites do %>
<%= if favorite.applied? %>
<% end %>
This is untested & highly speculative. I hope it gives you some ideas, though!

Using Delegate With has_many In Rails?

We've got 2 models & a join model:
#app/models/message.rb
Class Message < ActiveRecord::Base
has_many :image_messages
has_many :images, through: :image_messages
end
#app/models/image.rb
Class Image < ActiveRecord::Base
has_many :image_messages
has_many :messages, through: :image_messages
end
#app/models/image_message.rb
Class ImageMessage < ActiveRecord::Base
belongs_to :image
belongs_to :message
end
Extra Attributes
We're looking to extract the extra attributes from the join model (ImageMessage) and have them accessible in the Message model:
#message.image_messages.first.caption # -> what happens now
#message.images.first.caption #-> we want
We've already achieved this using the select method when declaring the association:
#app/models/message.rb
has_many :images, -> { select("#{Image.table_name}.*", "#{ImageMessage.table_name}.caption AS caption") }, class_name: 'Image', through: :image_messages, dependent: :destroy
Delegate
We've just found the delegate method, which does exactly what this needs. However, it only seems to work for has_one and belongs_to associations
We just got this working with a single association, but it seems it does not work for collections (just takes you to a public method)
Question
Do you know any way we could return the .caption attribute from the ImageMessage join model through the Image model?
We have this currently:
#app/models/image.rb
Class Message < ActiveRecord::Base
has_many :image_messages
has_many :messages, through: :image_messages
delegate :caption, to: :image_messages, allow_nil: true
end
#app/models/image_message.rb
Class ImageMessage < ActiveRecord::Base
belongs_to :image
belongs_to :message
def self.caption # -> only works with class method
#what do we put here?
end
end
Update
Thanks to Billy Chan (for the instance method idea), we have got it working very tentatively:
#app/models/image.rb
Class Image < ActiveRecord::Base
#Caption
def caption
self.image_messages.to_a
end
end
#app/views/messages/show.html.erb
<%= #message.images.each_with_index do |i, index| %>
<%= i.caption[index][:caption] %> #-> works, but super sketchy
<% end %>
Any way to refactor, specifically to get it so that each time .caption is called, it returns the image_message.caption value for that particular record?
delegate is just a shorthand as equivalent instance method. It's not a solution for all, and there are even some debate that it's not so explicit.
You can use an instance method when simple delegate can't fit.
I reviewed and found any association is unnecessary is this case. The ImageMessage's class method caption is more like a constant, you can refer it directly.
def image_message_caption
ImageMessage.caption
end

rails creating model with multiple belongs_to, with attr_accessible

My models look something like this:
class User < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Product < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Review < ActiveRecord::Base
attr_accessible: :comment
belongs_to :user
belongs_to :product
validates :user_id, :presence => true
validates :product_id, :presence => true
end
I am trying to figure out what the best way is to create a new Review, given that :user_id and :product_id are not attr_accessible. Normally, I would just create the review through the association ( #user.reviews.create ) to set the :user_id automatically, but in this case I am unsure how to also set the product_id.
My understanding is that if I do #user.reviews.create(params), all non attr_accessible params will be ignored.
You can do:
#user.reviews.create(params[:new_review])
...or similar. You can also use nested attributes:
class User < ActiveRecord::Base
has_many :reviews
accepts_nested_attributes_for :reviews
...
See "Nested Attributes Examples" on http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html.
It seems you would like to implement a many-to-many relationship between a User and Product model, with a Review model serving as a join table to connect the two with an added comment string. This can be accomplished with a has many through association in Rails. Start by reading the Rails Guides on Associations.
When setting up your Review model, add foreign keys for the User and Product:
rails generate model review user_id:integer product_id:integer
And set up your associations as follows:
class User < ActiveRecord::Base
has_many :reviews
has_many :products, through: :reviews
end
class Product < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class Review < ActiveRecord::Base
# has comment string attribute
belongs_to :user
belongs_to :product
end
This will allow you to make calls such as:
user.products << Product.first
user.reviews.first.comment = 'My first comment!'
Here's how you would create a review:
#user = current_user
product = Product.find(params[:id])
#user.reviews.create(product: product)

Resources