How can I know which columns in my table are considered unique? - ruby-on-rails

I have two columns in my table which are unique: username and email. I'm trying to create a method which will automatically check if whatever the user inputs is unique or not. It's something like
def check_if_taken(arg = {})
params.each do |key, value|
if (key is a unique column???)
return true if User.where(key => value).present?
end
end
false
end
How do I figure out the unique columns in the table?
Edit: refactored
def self.is_taken?(params = {})
params.detect do |key, value|
User.where(key => value).present?
end
end

You can try sth like:
class ActiveRecord::Base
def self.unique_attribute?(attribute_name)
!!_validators[attribute_name] && !!_validators[attribute_name].find {|v| v.is_a? ActiveRecord::Validations::UniquenessValidator)
end
end
And then:
YourModel.unique_attribute?(attribute_to_check)

Related

Set a default value if no value is found in the API

I've created a little app based on an assignment that shows a random set of countries and facts about them, including the countries they share borders with. If they don't share any borders the field is currently empty which I would like to change to "I'm an island", or something.
https://countries-display.herokuapp.com/
This should be easy, but I'm very new, and am not sure how to approach it. Any help or points in the right direction would be greatly appreciated.
I tried initialising a default value:
class Country
include HTTParty
default_options.update(verify: false) # Turn off SSL verification
base_uri 'https://restcountries.eu/rest/v2/'
format :json
def initialize(countries = 'water')
#countries = countries
end
def self.all
#countries = get('/all')
#countries.each do |country|
country['borders'].map! do |country_code|
#countries.find { |country| country['alpha3Code'] == country_code } ['name']
end
country['languages'].map! { |language| language['name'] }
country['currencies'].map! { |currency| currency['name'] }
end
#countries
end
end
Setting a default in active record:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
class Country < ActiveRecord::Base
before_create :set_defaults
def set_defaults
self.countries_to_display = 'water' if #countries_to_display.nil?
end
end
I also tried implementing some if statements that were equally unsuccessful.
Solution:
#countries.each do |country|
if country['borders'].empty?
country['borders'] << "I'm an island"
else country['borders'].map! do |country_code|
#countries.find { |country| country['alpha3Code'] == country_code } ['name']
end
end
...
I'm assuming that country['borders'] maps to the field that should contain I'm an island. I'm also assuming that you directly render the output of Country.all.
What you'll need to do is to add a check whether there are any borders and if there aren't write that string to the field instead of the list of bordering countries.
#countries.each do |country|
if country['borders'].empty?
borders = "I'm an island"
else
borders = country['borders'].map do |country_code|
#countries.find { |country| country['alpha3Code'] == country_code }['name']
end
end
country['borders'] = borders
...
end
Note that we use map instead of map! here to not modify the existing collection. You may need to adjust your rendering logic, as country['borders'] previously contained a list of countries and now contains a string.

Can I use the _destroy attribute in a non-nested form?

Say I have something like this in my controller:
FacultyMembership.update(params[:faculty_memberships].keys,
params[:faculty_memberships].values)
and whenever the _destroy key in params[:faculty_memberships].values is true, the record is destroyed.
Is there something like this in rails? I realize there are other ways of doing this, I was just curious if something like this existed.
Short answer
no!
Long answer
Still no! It is true that it works on nested attributes:
If you want to destroy the associated model through the attributes
hash, you have to enable it first using the :allow_destroy option.
Now, when you add the _destroy key to the attributes hash, with a
value that evaluates to true, you will destroy the associated model.
But why not trying it out in the console:
?> bundle exec rails c
?> m = MyModel.create attr_1: "some_value", attr_2: "some_value"
?> m.update(_destroy: '1') # or _destroy: true
?> ActiveRecord::UnknownAttributeError: unknown attribute '_destroy' for MyModel
This is because the update implementation is the following:
# File activerecord/lib/active_record/persistence.rb, line 245
def update(attributes)
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
assign_attributes(attributes)
save
end
end
and the source for assign_attributes is:
# File activerecord/lib/active_record/attribute_assignment.rb, line 23
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
end
return if new_attributes.blank?
attributes = new_attributes.stringify_keys
multi_parameter_attributes = []
nested_parameter_attributes = []
attributes = sanitize_for_mass_assignment(attributes)
attributes.each do |k, v|
if k.include?("(")
multi_parameter_attributes << [ k, v ]
elsif v.is_a?(Hash)
nested_parameter_attributes << [ k, v ]
else
_assign_attribute(k, v)
end
end
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
This code worked for me:
class FacultyMembership < ApplicationRecord
attr_accessor :_destroy
def _destroy= value
self.destroy if value.present?
end
end
Possibly this can break nested forms with destroy - didn't check.

Rails checking model to see if all fields are empty

As far as I've tested it, this helper method works exactly as it's meant to, however I want to know if there is any easier, built-in, or smarter way to run this check! I also am aware that having this in the ApplicationHelper probably isn't ideal. Not sure if I should just put it in the parent object (the Inspection), some other model, or leave as is.
With is_model_empty? I need to run through every field of any one of eleven different (but similar) models to check to see if all of them are Empty. All of them except the :id, :inspection_id, :created_at, and :updated_at fields which will never be blank. Empty can be nil, can be [], or can be ['']. An empty string would actually imply that the user entered something so that won't be included. The value can be either a string or an array so .empty? won't work.
def is_model_empty?(model)
model.attributes.each do |k, v|
unless ['id', 'inspection_id', 'created_at', 'updated_at'].include?(k)
return false unless v.nil? || v == [] || v == [""]
end
end
true
end
The eleven models all belong to the Inspection and each has a has_one relationship:
has_one :first_info_section
has_one :second_info_section
has_one :third_info_section
Any advice/feedback would be much appreciated. Thanks for reading!
-Dave
Your method can be simplifed as an instance method on each of the models. If the attribute exceptions are the same for all the models you can create a shared library and include it each of the models.
app/models/empty_detection.rb:
module EmptyDetection
def empty?
attributes.all? do |k, v|
['id', 'inspection_id', 'created_at', 'updated_at'].include?(k) || v.nil? || v == [] || v == [""]
end
end
end
Include that module in each model you want to be able to check for the empty conditions. For example, the Widget model:
class Widget < ActiveRecord::Base
include EmptyDetection
end
Now you can use it on any instance of a Widget:
widget = Widget.find(45)
widget.empty?
Here's a really basic refactor:
def is_empty?(model)
whitelist = %w[ id inspection_id created_at updated_at ]
model.attributes.all? do |attr, val|
whitelist.exclude?(attr) || val.nil? || val == [] || val == [""]
end
end
What you really want, though, is a validator, which is described in the Active Record Validations Rails Guide:
In this case it would look like this:
class EmptyValidator < ActiveModel::Validator
WHITELIST = %w[ id inspection_id created_at updated_at ].freeze
def validate(record)
return unless model.attributes.all? do |attr, val|
WHITELIST.exclude?(attr) || empty?(val)
end
record.errors[:base] << "You missed one!"
end
private
def empty?(val)
val.nil? || val == [] || val == [""]
end
end
Then, in each of your models...
class MyModel < ActiveRecord::Base
validates_with EmptyValidator
end
I hope that's helpful!
user.attributes.values.all?(&:blank?)

Can accepts_nested_attributes_for update based on a compound key?

My models look like the following:
class Template < ActiveRecord::Base
has_many :template_strings
accepts_nested_attributes_for :template_strings
end
class TemplateString < ActiveRecord::Base
belongs_to :template
end
The TemplateString model is identified by a compound key, on language_id and template_id (it currently has an id primary key as well, but that can be removed if necessary).
Because I am using accepts_nested_attributes_for, I can create new strings at the same time I am creating a new template, which works as it should. However, when I try to update a string in an existing template, accepts_nested_attributes_for tries to create new TemplateString objects, and then the database complains that the unique constraint has been violated (as it should).
Is there any way to get accepts_nested_attributes_for to use a compound key when determining if it should create a new record or load an existing one?
The way I solved this problem was to monkey patch accepts_nested_attributes_for to take in a :key option, and then assign_nested_attributes_for_collection_association and assign_nested_attributes_for_one_to_one_association to check for an existing record based on those key attributes, before continuing on as normal if not found.
module ActiveRecord
module NestedAttributes
class << self
def included_with_key_option(base)
included_without_key_option(base)
base.class_inheritable_accessor :nested_attributes_keys, :instance_writer => false
base.nested_attributes_keys = {}
end
alias_method_chain :included, :key_option
end
module ClassMethods
# Override accepts_nested_attributes_for to allow for :key to be specified
def accepts_nested_attributes_for_with_key_option(*attr_names)
options = attr_names.extract_options!
options.assert_valid_keys(:allow_destroy, :reject_if, :key)
attr_names.each do |association_name|
if reflection = reflect_on_association(association_name)
self.nested_attributes_keys[association_name.to_sym] = [options[:key]].flatten.reject(&:nil?)
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end
end
# Now that we've set up a class variable based on key, remove it from the options and call
# the overriden method to continue setup
options.delete(:key)
attr_names << options
accepts_nested_attributes_for_without_key_option(*attr_names)
end
alias_method_chain :accepts_nested_attributes_for, :key_option
end
private
# Override to check keys if given
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
attributes = attributes.stringify_keys
if !(keys = self.class.nested_attributes_keys[association_name]).empty?
if existing_record = find_record_by_keys(association_name, attributes, keys)
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
return
end
end
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
send("build_#{association_name}", attributes.except(*UNASSIGNABLE_KEYS))
end
elsif (existing_record = send(association_name)) && existing_record.id.to_s == attributes['id'].to_s
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
end
end
# Override to check keys if given
def assign_nested_attributes_for_collection_association(association_name, attributes_collection, allow_destroy)
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end
if attributes_collection.is_a? Hash
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
end
attributes_collection.each do |attributes|
attributes = attributes.stringify_keys
if !(keys = self.class.nested_attributes_keys[association_name]).empty?
if existing_record = find_record_by_keys(association_name, attributes, keys)
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
return
end
end
if attributes['id'].blank?
unless reject_new_record?(association_name, attributes)
send(association_name).build(attributes.except(*UNASSIGNABLE_KEYS))
end
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['id'].to_s }
assign_to_or_mark_for_destruction(existing_record, attributes, allow_destroy)
end
end
end
# Find a record that matches the keys
def find_record_by_keys(association_name, attributes, keys)
[send(association_name)].flatten.detect do |record|
keys.inject(true) do |result, key|
# Guess at the foreign key name and fill it if it's not given
attributes[key.to_s] = self.id if attributes[key.to_s].blank? and key = self.class.name.underscore + "_id"
break unless (record.send(key).to_s == attributes[key.to_s].to_s)
true
end
end
end
end
end
Perhaps not the cleanest solution possible, but it works (note that the overrides are based on Rails 2.3).

ruby object array... or hash

I have an object now:
class Items
attr_accessor :item_id, :name, :description, :rating
def initialize(options = {})
options.each {
|k,v|
self.send( "#{k.to_s}=".intern, v)
}
end
end
I have it being assigned as individual objects into an array...
#result = []
some loop>>
#result << Items.new(options[:name] => 'name', options[:description] => 'blah')
end loop>>
But instead of assigning my singular object to an array... how could I make the object itself a collection?
Basically want to have the object in such a way so that I can define methods such as
def self.names
#items.each do |item|
item.name
end
end
I hope that makes sense, possibly I am overlooking some grand scheme that would make my life infinitely easier in 2 lines.
A few observations before I post an example of how to rework that.
Giving a class a plural name can lead to a lot of semantic issues when declaring new objects, as in this case you'd call Items.new, implying you're creating several items when in fact actually making one. Use the singular form for individual entities.
Be careful when calling arbitrary methods, as you'll throw an exception on any misses. Either check you can call them first, or rescue from the inevitable disaster where applicable.
One way to approach your problem is to make a custom collection class specifically for Item objects where it can give you the information you need on names and such. For example:
class Item
attr_accessor :item_id, :name, :description, :rating
def initialize(options = { })
options.each do |k,v|
method = :"#{k}="
# Check that the method call is valid before making it
if (respond_to?(method))
self.send(method, v)
else
# If not, produce a meaningful error
raise "Unknown attribute #{k}"
end
end
end
end
class ItemsCollection < Array
# This collection does everything an Array does, plus
# you can add utility methods like names.
def names
collect do |i|
i.name
end
end
end
# Example
# Create a custom collection
items = ItemsCollection.new
# Build a few basic examples
[
{
:item_id => 1,
:name => 'Fastball',
:description => 'Faster than a slowball',
:rating => 2
},
{
:item_id => 2,
:name => 'Jack of Nines',
:description => 'Hypothetical playing card',
:rating => 3
},
{
:item_id => 3,
:name => 'Ruby Book',
:description => 'A book made entirely of precious gems',
:rating => 1
}
].each do |example|
items << Item.new(example)
end
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
Do you know the Ruby key word yield?
I'm not quite sure what exactly you want to do. I have two interpretations of your intentions, so I give an example that makes two completely different things, one of them hopefully answering your question:
class Items
#items = []
class << self
attr_accessor :items
end
attr_accessor :name, :description
def self.each(&args)
#items.each(&args)
end
def initialize(name, description)
#name, #description = name, description
Items.items << self
end
def each(&block)
yield name
yield description
end
end
a = Items.new('mug', 'a big cup')
b = Items.new('cup', 'a small mug')
Items.each {|x| puts x.name}
puts
a.each {|x| puts x}
This outputs
mug
cup
mug
a big cup
Did you ask for something like Items.each or a.each or for something completely different?
Answering just the additional question you asked in your comment to tadman's solution: If you replace in tadman's code the definition of the method names in the class ItemsCollection by
def method_missing(symbol_s, *arguments)
symbol, s = symbol_s.to_s[0..-2], symbol_s.to_s[-1..-1]
if s == 's' and arguments.empty?
select do |i|
i.respond_to?(symbol) && i.instance_variables.include?("##{symbol}")
end.map {|i| i.send(symbol)}
else
super
end
end
For his example data you will get following outputs:
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
puts items.descriptions.join(', ')
# => Faster than a slowball, Hypothetical playing card, A book made entirely of precious gems
As I don't know about any way to check if a method name comes from an attribute or from another method (except you redefine attr_accessor, attr, etc in the class Module) I added some sanity checks: I test if the corresponding method and an instance variable of this name exist. As the class ItemsCollection does not enforce that only objects of class Item are added, I select only the elements fulfilling both checks. You can also remove the select and put the test into the map and return nil if the checks fail.
The key is the return value. If not 'return' statement is given, the result of the last statement is returned. You last statement returns a Hash.
Add 'return self' as the last line of initialize and you're golden.
Class Item
def initialize(options = {})
## Do all kinds of stuff.
return self
end
end

Resources