(I replicated an isolated example of my issue on github: https://github.com/diingo/jobs_emps. It contains just the problematic portion - no views, no controllers, just the models I describe below and the breaking test.)
My code is working on development and production but currently a portion is breaking only in the test environment leaving me unable to test it properly.
Here's what's happening:
I have two models, jobs and employees. Each job has employees that oversee and participate in it as a specific role (employee_type) - Manager or Exective. Employees can be promoted - a manager can be promoted to an executive. But their roles for previous jobs they participated in must remain the same.
A join between employees and jobs (JobsEmployee) keeps track of the employee's role through the employee_type attribute. The join will not update a user's position if it was previously set - this is done with a before_save, as seen here:
class JobsEmployee < ActiveRecord::Base
before_save :set_user_type
def set_user_type
self.user_type ||= self.user.type
end
end
This works fine in actual use on development and production. If a job is created, with a manager and exective, job.jobs_employees will show one manager and one executive. If a manager is promoted to executive and that job is then updated for whatever reason, job.jobs_employees will still show one manager and one executive.
However, during testing this changes. Upon updating a job, if an employee was promoted, job.jobs_employees shows two executives.
My test is shown below. You can see I abstracted controller create and update methods into models for convenience. You can reference them on the github link: https://github.com/diingo/jobs_emps/blob/master/app/models/job.rb
RSpec.describe JobsEmployee, :type => :model do
before do
#job_permitted_params = {
city: "Munich",
status: "in_progress"
}
#manager = Employee.create!(name: "Bob Bobber", type: 'Manager')
#executive = Employee.create!(name: "Alice Smith", type: 'Executive')
#job_raw_params = {
job: {
manager_id: #manager.id,
executive_id: #executive.id
}
}
end
it "creates and updates" do
job = Job.create_with_params(#job_permitted_params, #job_raw_params)
# This passes:
expect(job.jobs_employees.map &:employee_type).to include("Manager", "Executive")
#manager.type = 'Executive'
#manager.save!
Job.update_with_params(job, #job_permitted_params, #job_raw_params)
# This breaks in testing but works in production:
expect(job.jobs_employees.map &:employee_type).to include("Manager", "Executive")
end
end
I put break points (pry debugger) in JobsEmployee#set_user_type to see what might be happening. It appears like the record in JobsEmployee are deleted before or during a Job update. So instead of seeing that self.user_type is already set in self.user_type ||= self.user.type, it just runs self.user.type again.
Here is the schema. You can also see it in the github link.
ActiveRecord::Schema.define(version: 20150301232938) do
create_table "employees", force: true do |t|
t.string "name"
t.string "type"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "jobs", force: true do |t|
t.string "city"
t.string "status"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "jobs_employees", force: true do |t|
t.string "employee_type"
t.integer "employee_id"
t.integer "job_id"
t.datetime "created_at"
t.datetime "updated_at"
end
end
I'm not sure why this is happening. Thanks so much for checking out the problem.
The "actual use" you are describing may not correlate with your test. Updating a job and changing an employee's role would not necessarily cause the wholesale reassignment of jobs_employees association that you are doing in your test. I suspect that when you are doing that, Rails is comparing the set of JobsEmployee records represented by the ids you are assigning to the currently associated records. Since the currently associated records have the user_type field set, they aren't equivalent to the records that would be created upon a fresh assignment, so they are deleted and new ones regenerated.
Related
I have part of a rails application where a user will create a recipe that will be saved in their "cookbook". Other users will be able to take recipes from other users. So there will be an aspect in the application that shows who created the recipe.
Schema for a Recipe
create_table "recipes", force: :cascade do |t|
t.string "recipe_name"
t.string "description"
t.integer "calories"
t.integer "carbs"
t.integer "fats"
t.integer "protein"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Where I am having trouble is displaying the recipe's creator.
def show
#user = current_user
#recipe = Recipe.find_by(params[:id])
creator = User.find_by(params[#recipe.user_id])
#creator = creator.first_name
end
So for right now I have two user's John (Id: 1) and Alex (Id:2). When I have Alex make a recipe and I put a pry under #recipe I get a user_id of 2 when I call #recipe.user_id.
However, when I put the pry under creator and call creator I get the user_id of 1 and I get John. I believe something is wrong with how I am trying to find the user using the user_id in #recipe. I was wondering if anyone know what I am doing wrong or if I need to add more information. Thanks.
This:
User.find_by(params[#recipe.user_id])
Doesn't make sense for a couple of reasons:
find_by expects a hash-like structure. Something like: User.find_by(id: xxx)
params[#recipe.user_id] doesn't make sense because that's going to be something like: params[1] which is not what you want.
This:
#recipe = Recipe.find_by(params[:id])
Also suffers from the malformed find_by.
So, try something like:
def show
#user = current_user
#recipe = Recipe.find(params[:id])
creator = #recipe.user
#creator = creator.first_name
end
This, naturally, assumes you have your association between Receipt and User set up correctly (i.e., using belongs_to).
I would like to seed my Rails app database with the permutation of an existing array of objects, and am unsure about the best way to go about this.
I currently have a Country model, with the following attributes:
create_table :countries do |t|
t.string :name
t.float :latitude_dec
t.float :longitude_dec
t.timestamps null: false
end
I have seeded this model from a .yaml file (as these attributes are static), and now would like to use these records to seed a CountryPair model (where the attributes are also static). This model will have the following attributes:
create_table :country_pairs do |t|
t.string :country_a
t.string :country_b
t.string :pair_name
t.float :country_a_latitude_dec
t.float :country_b_latitude_dec
t.float :country_a_longitude_dec
t.float :country_b_longitude_dec
t.float :distance
t.timestamps null: false
end
The aim is to permutate the array of Country objects, and create a CountryPair object from each permutation (and seed the database with the output). I understand the Ruby array#permutation method, but am unsure about how to pull out the appropriate values into the new array of CountryPair objects. The order of countries in the pair is important here, so I'd like to use permutations rather than combinations.
Ultimately, I'd also like to calculate the distance between the country pairs, but I'm hoping to start figuring that out once I have the CountryPair model filled!!
This is my first foray back into Rails after a five year absence, so apologies if I've got some of the terminology/methodology wrong - please do ask for clarification if any further information is required! Thanks in advance!
You can add this snippet to your seeds.rb after the Countries are seeded.
Country.all.permutation(2) do |p|
CountryPair.create(
country_a: p[0].name,
country_b: p[1].name,
pair_name: p[0]name + p[1].name,
country_a_latitude_dec: p[0].latitude.dec,
country_b_latitude_dec: p[1].latitude.dec,
country_a_longitude_dec: p[0].longitude.dec,
country_b_longitude_dec: p[1].longitude.dec,
distance: # An algorithm to calculate distance
)
end
Then run it with: rake db:setup
So, I'm using Rails 4, and I have an enum column on my "Sales_Opportunity" object called pipeline_status - this enables me to move it through a sales pipeline (e.g. New Lead, Qualified Lead, Closed deal etc). This all works fine. I'm able to find the number of sales_opportunities that a company has by status through using the following:
<%= #company.sales_opportunities.where(pipeline_status: 3).count %>
This all works fine. What I want to do is to find all sales_opportunities that have the pipeline_status of "closed_won" (enum value of 4 in my app) and sum the value of each won deal (so I can represent the total value of the customer based on the deals that are won in the system). A Sales_Opportunity in my model has a sale_value field, so I tried:
<%= #company.sales_opportunities.where(pipeline_status: 4).each.sale_value.sum %>
which returns the following error:
undefined method `sale_value' for #<Enumerator:0x007f9b87a9d128>
This is probably a trivial error but I can't for the life of me figure out what's going on. Is there where statement returning the enumerator or the sales_opportunity objects with that enumerator? Any help would be gratefully appreciated.
If it helps here are the fields in my sales_opportunities table:
create_table "sales_opportunities", force: true do |t|
t.datetime "close_date"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "pipeline_status", default: 0
t.string "opportunity_name"
t.integer "company_id"
t.decimal "sale_value", precision: 15, scale: 2, default: 0.0
end
A Sales_opportunity belongs_to a Company Object and a User Object, if that makes any difference.
use aggregate function sum
<%= #company.sales_opportunities.where(pipeline_status: 4).sum(:sale_value) %>
Other possibility is to use
<%= #company.sales_opportunities.where(pipeline_status: 4).pluck(:sale_value).reduce(0, :+) %>
I have a form through which I am uploading images. The workflow is, that I will upload and save an image and then I run a delayed job, that will take this image and will create from it 3 thumbs.
When I refresh the page, I usually see that the thumbs are not created yet, but after a while (10-15 seconds) are the thumbs ready.
But this is not very friendly - I would like to show to a user that his thumb is in progress of creating, but how to do that?
The structure of the delayed_jobs table is like this:
create_table "delayed_jobs", :force => true do |t|
t.integer "priority", :default => 0, :null => false
t.integer "attempts", :default => 0, :null => false
t.text "handler", :null => false
t.text "last_error"
t.datetime "run_at"
t.datetime "locked_at"
t.datetime "failed_at"
t.string "locked_by"
t.string "queue"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
I think the way to go could be to add to this table a column, like photo_id and simply if the respective row would be missing in this table, then I would know that the thumb is ready. Otherwise is in a queue/in progress of creating.
But how to save into this table this extra column? When I call the delay method, it's just like this:
#user.delay.activate!(#device)
How to pass there an ID of a photo?
Thanks
Rather than storing the photo_id in the delayed_jobs table, you can store the job id in the photos (or users) table. For this you'll need to write a Job class like so:
class PhotoThumbnailJob < Struct.new(:user_id)
def perform
# generate the thumbnail
end
end
Then get a reference to the job instance like this:
job = Delayed::Job.enqueue(PhotoThumbnailJob.new(#user.id))
#user.thumbnail_job_id = job.id
Now as long as you have the #user you can get the job. That way you can tell whether it failed or simply hasn't finished yet, and if it failed you can report on the reason, etc.
Note that if you do this, you might want to create a foreign key constraint on thumbnail_job_id and tell it to null that column when the job is deleted, because by default DJ will delete successful jobs from the delayed_jobs table.
I had a need for a similar capability in an application I built. The approach I took was to use WebSockets to send status updates to the user as my DelayedJob jobs progress. I used Pusher because the API is straightforward and it was free for us, but any implementation, including your own, would do.
Otherwise, I would stay very far way from altering the table and instead utilize the various hooks DelayedJob provides to enable a callback capability for you to take whatever action you prefer at various stages of the job.
This might seem like a duplicate question, but I can't find any information on this. I want to show the results from a remotely acquired json array excluding certain results by comparing them to a local table. I have a gallery model with:
t.integer :smugmug_id
t.string :smugmug_key
t.integer :category_id
t.string :category_name
t.string :description
t.integer :highlight_id
t.string :highlight_key
t.string :highlight_type
t.string :keywords
t.string :nicename
t.integer :subcategory_id
t.string :subcategory_name
t.string :title
t.string :url
The data for this model gets populated by a rake task that connects to the smugmug api (json) and stores the data locally. I'm trying to create a view that shows all the smugmug galleries that are not stored locally.
Here's what I've tried so far, but it's not excluding the locally stored galleries like I thought it would.
def self.not_stored
smugmug_list = Smug::Client.new.albums(heavy = true)
gallery_list = Gallery.select(:smugmug_id)
smugmug_list.each do |smugmug|
smugmug unless gallery_list.include? smugmug.id
end
end
Hopefully this makes sense. I'm getting a json array of galleries, and I want to display that array excluding results where the album id matches the smugmug_id of any of my locally stored records.
Quick edit: I'm using an adaptation of this gem to connect to the smugmug api.
Just use the difference operator.
General Example:
ruby-1.9.2-p136 :001 > [3,2,1] - [2,1]
=> [3]
So you would have:
smugmug_list.collect{|e| e.id} - gallery_list
Enumerable#collect will turn the smugmug_list into a list of id's. From there, you can do the difference operator, which will return all the id's of all the smugmug galleries that are not stored locally.
Another option to maintain the list of galleries:
smugmug_list.select{|e|!gallery_list.include?(e.id)}