How to properly allow nil in rails - ruby-on-rails

In my rails app my bands have many events. In the view of the bands it can show what events they have. My problem is if a new band is created it will throw an error in the view because it does not have an event.
I was reading about allow_nil and I wanted to use it in my bands model but Im not sure how to implement it. The documentation on it is pretty dry and not really helpful
class Band < ApplicationRecord
has_many :events :allow_nil true
end
Im not sure if the above way to do it is correct.

Rails 5.x
I think you are looking for optional: true, but it has to be added in the belongs_to side of the association, like this:
class Event < ApplicationRecord
belongs_to :band, optional: true
end
Rails 4.x
Rails 4 allows nil by default on any association, you just need to remove any validation that needs band_id presence.

I suspect what you're asking for (ActiveRecord-level data validations on associated models) isn't actually your problem. Your original question is about allow_nil but you state that (with emphasis mine):
In the view of the bands it can show what events they have. My problem is if a new band is created it will throw an error in the view because it does not have an event.
What you're probably doing is attempting to show a list of events by iterating over them using .each, but since the collection of events does not exist .each errors out. Instead, before you do:
<% events.each do |event| %> # or similar code that you're using
... # to iterate over the collection
<% end %>
You should check that events.present?:
<% if band.events.present? %>
<% band.events.each do |event| %>
...
<% end %>
<% else %>
# show something else
<% end %>
For the sake of completeness though, allow_nil is used on specific fields of a model to skip other validations on that field if that field is nil. ([See the documentation here.])(http://edgeguides.rubyonrails.org/active_record_validations.html#allow-nil)
It doesn't work in the code you posted in your question since there isn't an event_id field on your Band model—since it's a one-to-many association from Band to Events (assuming you setup your database correctly).

Related

Rails polymorphic association in a form

This is a slightly unique version of a polymorphic association. It's one of those "real world" problems that I'm struggling to solve and haven't come across many good answers so I made my own.
A Transaction record has many Tasks and each Task has an Assignee, which can be from multiple tables.
# Models
class Transaction < ApplicationRecord
has_many :tasks
has_many :borrowers
has_many :partners
# Combine into a single array to display on the collection_select
def assignees
borrowers + partners
end
end
class Task < ApplicationRecord
# has attribute :assignee_type_and_id (string)
belongs_to :transaction
# Reverse engineer single attribute into type/id parts
def assignee
if assignee_type_and_id
parts = assignee_type_and_id.split(".")
type = parts.first
id = parts.last
if type.to_s.downcase == "borrower"
Borrower.find(id)
elsif type.to_s.downcase == "partner"
Partner.find(id)
end
end
end
end
class Borrower < ApplicationRecord
# has attribute :name
belongs_to :transaction
def type_and_id
"borrower.#{id}"
end
end
class Partner < ApplicationRecord
# has attribute :name
belongs_to :transaction
def type_and_id
"partner.#{id}"
end
end
On the Task form pages, I want a single HTML select that combines BOTH the Borrowers and Partners.
Classic polymorphism says to add an assignee_type column, but now I'm working with 2 fields instead of one.
My solution is to combine these 2 into a single select such that the final value is of the format assignee_type.assignee_id.
# form.html.erb
<%= form_for #task do |f| %>
<%= f.label :assignee_type_and_id, "Assignee" %>
<%= f.collection_select :assignee_type_and_id, #transaction.assignees, :name, :type_and_id %>
<% end %>
When the form is submitted, it POSTs values in the format borrower.123, partner.57, etc, and that value gets stored in the DB column.
When I want to retrieve the actual task's Assignee, I have to do a little reverse engineering as noted above in the Task#assignee method.
Question
Is there a more appropriate way to do this? I came up with this myself, which scares me because I know problems like this must have been solved by people much smarter than me...
Is there a way to make this work with "normal" polymorphism instead of forcing my own hybrid version?
Update
I happened upon Rails 4.2+ GlobalID, which seems to do this very thing. Unless there's a reason not to use that, I may use that implementation instead of my own "bastardized" version. Is there any better solution to a situation like this?
For these type of problems where a form spans multiple models/complex associations I use a form backing object. It keeps everything clean and modular. Here is a good write up: https://content.pivotal.io/blog/form-backing-objects-for-fun-and-profit

Ruby on Rails 4 Nested Forms with Active Record

I am new to Rails and just building my first app (coming from a PHP and .NET background and loving it btw) but I have run into a problem that I am struggling to find an answer to, even though I am sure there is an easy one!!
My project has 3 main Models; Locations, Services and Location Services
There are multiple services available and a Location can have any number of them. Basically I am using a record in Locations Services to store the ID of the selected service and the ID of the location.
A simplified version of my models are as below:
class Location < ActiveRecord::Base
has_many :location_services
end
class Service < ActiveRecord::Base
has_many :location_services
end
class LocationService < ActiveRecord::Base
belongs_to :location
belongs_to :service
end
I have read up about nested forms and using 'accepts_nested_attributes_for' to allow sub forms to edit data taken from another model which sounds very similar to what I want, except I don't want to just be able to edit the Location Services that I have, I want to be able to choose from every single available Service as checkboxes, then when checked and my Location is saved, I want it to create a record for each selected service in the Location Services table using the ID of the Location and the ID of the service
Im sure I could easily generate all the tickboxes with Services.all and then loop through that and then in my controller grab all of the ticked checkboxes from the POST, loop through them and build an array of all of them and then pass that array to Location.location_services.create([]) but this is rails and I feel like there is probably a better way to do it?
So firstly, am i going about this in a stupid way? Rather than having 3 tables, is there a better way of doing it? And is there a nice way of generating and saving all of the services?
Many thanks in advance
David
Many thanks Yan for your help on this one, I have finally managed to resolve my issue and it actually turned out to be really simple. I am posting here in the hope it helps someone else.
What I needed to do was add a has_many relation to services through location services so my model now looks like below:
class Location < ActiveRecord::Base
has_many :services, :through => :location_services
has_many :location_services
accepts_nested_attributes_for :location_services
end
I updated my view to include:
<%= f.collection_check_boxes(:service_ids, Service.all, :id, :name) do |b| %>
<%= b.label(class: "check_box") do %>
<%= b.check_box %>
<%= b.object.name %>
<% end %>
<% end %>
Then in my controller I have:
def location_params
params.require(:location).permit(:service_ids => [])
end
I have stripped out all of my other fields for simplicity. Then finally in the Update method, it is as simple as:
def update
#location.update(location_params)
redirect_to #location, notice: 'Location was successfully updated.'
end
Hope this helps someone out!!
Many thanks
David
A has_many relationship adds a number of methods to your model. From which you only need the collection_singular_ids method, which does the following:
Replace the collection with the objects identified by the primary keys
in ids. This method loads the models and calls collection=.
The above method can be combined with collection_check_boxes as explained in this tutorial. So in your case you'll have something like:
f.collection_check_boxes :location_service_ids, LocationService.all, :id, :name
Note that the last parameter (:name here) is the text_method_option which generates the labels for your check boxes.
Last but not least: don't forget to use accepts_nested_attributes properly.

Testing for lack of associated objects in Ruby on Rails

This follows on from an earlier question as I am bending my brain around Ruby on Rails.
I have items which are displayed on a webpage, depending on whether their status allows the display or not, using a named scope - if the document status ("For Sale", "Sold", "Deleted" etc) has the show_latest_items flag set to 1, it will allow associated items to be displayed on the page :
class Item < ActiveRecord::Base
belongs_to :status
scope :show_latest_items, joins(:status).where(:statuses => {:show_latest_items => ["1"]})
end
class Status < ActiveRecord::Base
has_many :items
end
This is how it is displayed currently
<% latest_items = Items.show_latest_items.last(30) %>
<% latest_items.each do |i| %>
:
<% end %>
So this is all well and good, but I now want to only display the item if it has an associated photo.
class Item < ActiveRecord::Base
has_many :item_photos
end
class ItemPhoto < ActiveRecord::Base
belongs_to :item
end
So in my mind, I should, using the named scope, be able to pull back a list of Items for display, and then filter them using .present? or .any? methods. Curious thing is this:
<% latest_items = Items.show_latest_items.where(:item_photos.any?).last(30) %>
returns an error:
undefined method `any?' for :item_photos:Symbol
Whereas:
<% latest_items = Items.show_latest_items.where(:item_photos.present?).last(30) %>
doesn't error, but it doesn't filter out items with no photos, either.
I've tried various other methods, as well as trying to do custom finders, writing names scopes for photos, but nothing is making a lot of sense. Should I be approaching this from a different angle?
:item_photos.any?
This doesn't work because Ruby's Symbol has no any? method.
.where(:item_photos.present?)
This doesn't do the filtering you're after because you're calling .present? on the Symbol :item_photos which evaluates to true, making the condition really
.where(true)
Try simply
<% latest_items = Items.show_latest_items.joins(:item_photos).last(30) %>
The SQL for this .joins(:item_photos) is going to be an INNER JOIN, causing Item instances with no associated ItemPhoto instances to be omitted from the result.

Ruby : Access array object by index vs iterator

I'm pretty new to Ruby and the Rails framework. My background is primarily Java. Anyhow, I'm facing a weird situation. I have a method in one of my models that returns associated models. The association is as follows. A has_many Bs, and B belongs to A (i.e. one-to-many)
class ModelA < ActiveRecord::Base
has_many :model_bs
def get_bs
ModelB.where(:a_id => id)
end
end
class ModelB < ActiveRecord::Base
belongs_to :model_a
end
In my view, if I try to access the records (models) in the result set, I'm able to call its properties without any issue (Figure A). Life is good.
Figure A:
<% bs = a.get_bs %>
<% bs.each do |b| %>
<%= b.some_prop %>
<% end %>
But if I try to access the models by index, I get an error saying that I can't call a method on a nil object (Figure B & C).
Figure B:
<% bs = a.get_bs %>
<%= bs[0].some_prop) %>
Or even..
Figure C:
<% bs = a.get_bs %>
<%= bs[0].first %>
Does not work. I know it's user error (me). I've looked at the documentation for accessing objects from a collection (in this case, I believe it's a Ruby array). I've also searched here on StackOverflow. I'm still left scratching my head. I haven't quite found a similar thread.
You are wrong, it is not Array, it is an ActiveRecord::Relation class. You can transform it to an array with .to_a, if you really need it. I've checked, you can use [] operator to access item by index: ModelA.where("created_at = created_at")[0].name, so I think the problem is somewhere else, maybe in your condition.
Check the documentation.
But anyway, you shouldn't use the relationship like this. Use has_many and belongs_to to indicate relationship between models. Like this:
class ModelA < ActiveRecord::Base
has_many :ModelB
end
class ModelB < ActiveRecord::Base
belongs_to :ModelA
end
I found a solution. It's not pretty but it'll due for now. With a little help from someone on LinkedIn, I discovered that using the .try method on my model while attempting to access an attribute, I'm able to retrieve the value without the null pointer exception.
Example:
<% bs = a.get_bs.to_a%>
<%= bs[0].try(:some_attr) %>
It's not clear to me as why I need to use the .try method. I mean, I know what the method is for. It's a convenience method for checking nil values and allowing the page to render without throwing an exception. But it's obvious that my model is not null and it has data. So why am I only able to access its attributes with the .try method? Honestly, I think this could be a bug in Rails.
I think what I'll end up doing is create a helper method that utilizes the .try method. That way, I'm not calling .try all over my views.
Why not using this?
def get_bs
ModelB.find_all_by_a_id(id)
end

Activerecord relationship joins

I'm working in Rails and Activerecord and trying to merge some data from related tables together in my view, here are my models:
class Report < ActiveRecord::Base
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :reports
end
class User < ActiveRecord::Base
has_many :votes
end
Each vote has a user and a report.
In my view I need the following, hopefully as easily as possible:
a total number of votes for each report from all users
a true/false if the user has voted on the particular report
Right now, my basic understanding of ActiveRecord queries only takes me as far as creating a helper with the report and the current user and than querying for the existence of report
Same goes for counting the total number of votes for all users for a report as follows:
Controller
def index
#this is where I need some help to get the related information into a single
#object
#reports = Report.where('...')
end
View
<% #reports.each do |report| %>
<% if(hasVoted(#current_user.id, report.id)) %>
<!-- display the 'has voted html' -->
<% end %>
<% end %>
Helper
def hasVoted(current_user_id, report_id)
if(Vote.exists?(:user_id => current_user_id, :report_id => report_id))
true
else
false
end
end
Hope that gives you some insight into helping...thanks!
Sure.
Firstly, please consider naming your method has_voted? instead of hasVoted. Secondly, consider moving that method in the user model.
#user.rb
def voted_on?(report_id)
votes.where(:report_id => report_id).exists?
end
Your view will then read
<% if current_user.voted_on?(report) %>
...
<% end %>
The other question you had was to find the number of votes a report has received. This is simple too. You could do this in your view inside the loop where you iterate over #reports
<% vote_count = report.votes.size %>
Please keep in mind that his would result in N queries (where N = number of reports). Since you are new to Rails i'm not going to complicate your Reports query in the controller where you fetch you reports to include the vote count (unless you ask me to). But once you are comfortable with what happening in here, thats where you would optimize.

Resources