Integration testing rails-api model using rspec and fixtures - ruby-on-rails

I'm pretty new to Rails and I've finished (I think) my rails API, and now I'm up to test it.
Say I have this model:
class Child < ActiveRecord::Base
belongs_to :parents
validates :id, presence: true
validates :type, presence: true
validates :parent_id, presence: true
before_create :update_child_if_exists
private
def update_child_if_exists
conditions = {type: self.type, parent_id: self.parent_id}
if existing_measure = Child.find_by(conditions) # or Child.where(conditions).last
new_values = (existing_child.values || []) + [self.values]
existing_measure.update_attribute(:values, new_values)
end
end
end
This is meant to update a field in the child table if the record by type and parent_id already exists, else it would be created. How can I write a test for this using RSpec?
Any help would be much appreciated, I'm pretty lost here.
If any additional information is needed, please ask me and I will provide it.

Related

Using Concerns to Extend User Model With Roles

I'm experimenting in building a roles-based data structure in Rails using Concerns with role-specific methods. I've explored other solutions, like STI, polymorphic associations, but opted to try this method.
All attributes depicted are stored in one table, users.
module Client
extend ActiveSupport::Concern
def self.extended(target)
target.class_eval do
validates :emergency_contact, presence: true
end
end
end
module Doctor
def self.extended(target)
target.class_eval do
validates :credentials, presence: true
end
end
end
class User < ApplicationRecord
validates :role, allow_blank: true, inclusion: { in: roles }
validates :first_name, presence: true
validates :last_name, presence: true
validates :email, presence: true
after_initialize { extend_role }
# Extend self with role-appropriate Concern
def extend_role
extend role.camelize.constantize if role
end
def role=(role)
super(role)
extend_role
end
end
My challenge is during the changing of a role, specifically, what can be done (if anything) to negate the previous role's Concern having extended the User?
Each user will have 1 role, so having the Client and Doctor concerns both mixed into the User would not be appropriate (at this time, at least).
In an ideal solution, the User instance would morph with the role change and retain all changed attributes.
So, calling this:
user.update_attributes({ first_name: 'Wily', last_name: 'McDoc', role: 'doctor' })
...would first handle the role change and then update the attributes.
guess could, in part, remove all methods:
Module.instance_methods.each{|m|
undef_method(m)
}
also, How can I reverse ruby's include function, in case that may be of interest

Validation of nested resource fails on build

I'm building instances of a model in another model controller. All seems to work fine, child instances are well created with the parent id but as soon as I add validations for parent_id in this resource, the instance is no longer valid. Any idea what I'm missing ?
Mission model:
class Mission < ActiveRecord::Base
has_many :planned_times
validates :code, presence: true, uniqueness: { case_sensitive: false }
validates :days_sold, presence: true
end
PlannedTime model:
class PlannedTime < ActiveRecord::Base
belongs_to :mission
validates :date, presence: true
validates :mission_id, presence: true # this is the validation which causes problem
end
Mission controller:
class MissionsController < ApplicationController
def create
#mission = Mission.new(mission_params)
week_nums = params[:weeks].split(/[\s]*[,;\-:\/+][\s]*/).uniq
year = params[:year].to_i
week_nums.each do |week_num|
date = Date.commercial(params[:year].to_i,week_num.to_i)
#mission.planned_times.build(date: date)
end
if #mission.save
flash.now[:success] = "Mission added"
end
end
private
def mission_params
params.require(:mission).permit(:code, :days_sold)
end
end
So validating the presence of associations is a little tricky. In your case you're putting the mission_id validator on the child association but rails runs the validation on planned_time before it saves the mission so it will fail because mission_id is still nil. Also, by putting the validation on planned_time it'll mean that that validation won't run if you never mission.planned_items.build because the associated planned_time won't exist and therefore not run its validations.
With minimal changes to your code or validation logic you can get it to work like this:
class PlannedTime < ActiveRecord::Base
belongs_to :mission
validates :mission_id, presence: { if: ->(p) { p.mission.nil? } }
end
This part presence: { if: ->(p) { p.mission.nil? } } will check if there is a mission object present (albeit without an id yet) and if there is no mission object the validation will fail. So good, now we know we can't create a planned_time without its parent mission object present. But this says nothing about the mission requiring the planned_time to be created. If this is what you want then that's the solution. Although I'm left wondering if you really wanted it the other way around where you want to make sure a mission is always created along with its planned_time?

How can you check if all of a model's attributes have a value set?

I currently have the following method:
def complete?
attributes.delete_if{|k,v| k == 'id' or k == 'user_id'}.values.each do |value|
return false if value.blank?
end
true
end
Is there a better way of doing this? I just want to know if, with my instance, all the attributes have been set apart from id and user_id. There must be a better way of doing this.
I'm on rails 3 and ruby 1.9.3 (just so people don't give answers that will work with newer versions)
There is the validates method to do that:
class User < ActiveRecord::Base
validates :full_name, :username, :email, :address, presence: true
If you want to validate the presence of every attributes except few ones:
class User < ActiveRecord::Base
validates *(self.column_names - ['id', 'created_at', 'updated_at']), presence: true
Above exemple extended:
class User < ActiveRecord::Base
validates *self.validable_columns, presence: true
def self.validable_columns
excluded_columns = ['id', 'created_at', 'updated_at'] # columns to be excluded in the `validates`
self.column_names - excluded_columns
end

Rails: Looking up foreign key id using virtual attribute failing

My app allows users to add words from a master words table into their own custom list. So, a word list contains multiple custom words each of which link to a master word.
In my view, I have a field called word_text (virtual attribute) where I let users enter a word, and in my model I am trying to look up the master_word_id and set it on the custom word table. I am unable to access the #word_text value in the model. I always seem to get an error that the master word is a required field (because the look up is failing).
class CustomWord < ActiveRecord::Base
attr_accessible :master_word_id, :word_list_id, :word_text
attr_accessor :word_text
belongs_to :word_list
belongs_to :master_word
validates :word_list, presence: true
validates :master_word, presence: true
before_validation :set_master_word
private
def set_master_word
logger.debug "Received word text #{#word_text}"
_mw_id = nil
if !#word_text.nil?
master_word = MasterWord.find_word(#word_text)
if master_word.nil?
errors.add("#{#word_text} is not a valid word")
else
_mw_id = master_word.id
end
end
self.master_word_id = _mw_id
end
end
I sincerely appreciate any suggestions as to how I can set the value of the master_word_id correctly.
There are several things to fix:
class CustomWord < ActiveRecord::Base
attr_accessible :master_word_id, :word_list_id, :word_text
attr_accessor :word_text
belongs_to :word_list
belongs_to :master_word
validates :word_list, presence: true
#validates :master_word, presence: true <= This will always throw error
validates :word_text, presence: true
validates :master_word_id, presence: true
before_validation :set_master_word
private
def set_master_word
logger.debug "Received word text #{self.word_text}"
self.master_word_id = MasterWord.find_by_word_text(self.word_text).id
end
end
Not sure if it will work because I don't know the rest of your app but I hope it points you in the right direction.

Model has_many OtherModel but localized, so different OtherModel for same Model based on locale: how?

I'm having hard time with this, it's not a direct problem of implementation but I don't understand which is the right way to do it, I have two options, but first, these are my models:
class Boat < ActiveRecord::Base
validates :name, presence: true, uniqueness: { case_sensitive: false }
has_many :tech_specs, order: 'position'
def visible?
self.visible
end
end
class TechSpec < ActiveRecord::Base
validates :boat_id, presence: true
validates :tech_spec_name_id, presence: true, uniqueness: { scope: :boat_id }
belongs_to :boat
belongs_to :tech_spec_name
before_destroy :destroy_name_if_required
acts_as_list scope: :boat
def as_json(options = {})
super(options.except!(:tech_spec_name_id).merge!(methods: [self.name]))
end
def name
self.tech_spec_name.try(:name) || ''
end
def name=(value)
self.tech_spec_name = TechSpecName.find_or_create_by_name(value)
end
def destroy_name_if_required
self.tech_spec_name.destroy if self.tech_spec_name.tech_specs.size <= 1
end
end
class TechSpecName < ActiveRecord::Base
validates :name, presence: true, uniqueness: { case_sensitive: false }
has_many :tech_specs
def self.with_name_like(str)
where('lower(name) LIKE lower(?)', "%#{ str }%")
end
end
The problem is that I want a page for a boat showing some tech specs when with a locale and when on a different locale, showing other tech specs.
Idea #1
My basic idea is to add to TechSpec globalize3 on tech_spec.value and on TechSpecName for field tech_spec_name.name
Idea #2
The other idea is to remove TechSpecName and, instead, use a field (tech_spec.name) that will "replace" completely TechSpecName. Notice that in this case, I'll still need to fetch names for autocomplete, but I will filter them in TechSpec instead of fetching all from TechSpecName. This field will use globalize3 again obviusly.
I don't know the downside of both approaches, so I need a suggestion.
Seems like idea #1 is ok, it works correctly and reduce the amount of repeated text inside Db.
I18n.with_locale helps a lot too, also Globalize.with_locale is helpful

Resources