Make array of values from table attribute that matched another tables attribute - ruby-on-rails

In my Ruby on Rails 3 app controller, I am trying to make an instance variable array to use in my edit view.
The User table has a user_id and reseller_id.
The Certificate table has a user_id.
I need to get the reseller_id(s) from the User table that have the user_id(s) in both User table and Certificate table.
Here is my User model:
class User < ActiveRecord::Base
attr_accessible :email, :name, :password, :password_confirmation, :remember_token, :reseller_id, :validate_code, :validate_url, :validated, :admin, :avatar
belongs_to :reseller
has_one :certificate
end
Here is my Certificate model:
class Certificate < ActiveRecord::Base
attr_accessible :attend, :pass, :user_id
validates :user_id, presence: true
end
Here is my controller, this seems to only store the last user_id in the Certificate table.
##train should be reseller.id(s) of all users in Certification table.
#certs = Certificate.all
#certs.each do |user|
#id = []
#id << user.user_id
#id.each do |id|
if User.find(id)
#train = []
#train << User.find(id).reseller_id
end
end
end
Thank you

1) Correct version of your code
#certs = Certificate.all
#reseller_id = [] # 1
#certs.each do |user|
id = user.user_id # 2
if u = User.find(id)
#reseller_id << u.reseller_id # 3
end
end
2) Rails way
Something like this
#reseller_id = User.joins(:certificates).select('users.reseller_id').map { |u| u['reseller_id']}
PS
Don't keep this code in controller please :-)

Well, first of all, you should not nest your ids' each block inside of the each block for certificates. You should build your ids array, then loop over it later. The reason you are only getting the last user_id, is because "#id" will only ever have a single element in it as your code is currently written. You will also run into the same problem with your "#train" array. Because you are declaring the array inside the iterator, it is getting re-created (with nothing in it) on every iteration. Using your existing code, this should work:
#certs = Certificate.all
#ids = []
#certs.each do |user|
#ids << user.user_id
end
#train = []
#ids.each do |id|
if User.find(id)
#train << User.find(id).reseller_id
end
end
A more Rubyish and concise way would be the following:
cert_user_ids = Certificate.all.map(&:user_id)
reseller_ids = cert_user_ids.map { |id| User.find(id).reseller_id rescue nil }.compact
Map is an enumerable method that returns an array of equal size to the first array. On each iteration, whatever the code inside the block returns "replaces" that element in the new array that is returned. In other words, it maps the values of one array to a new array of equal size. The first map function gets the user_ids of all certificates (using &:user_id is a shortcut for Certificate.all.map { |cert| cert.user_id } ) The second map function returns the "reseller_id" of the user, if a user is found. It returns nil if no user is found with that id. Finally, compact removes all nil values from the newly mapped array of reseller_ids, leaving just the reseller ids.
If you want to do this in the most efficient and railsy way possible, minimizing database calls and allowing the database to do most of the heavy lifting, you will likely want to use a join:
reseller_ids = User.joins(:certificates).all.map(&:reseller_id)
This grabs all users for which a certificate with that user's id exists. Then it utilizes map again to map the returned users to a new array that just contains user.reseller_id.
Ruby tends to be slower at this type of filtering than RDBM systems (like mysql), so it is best to delegate as much work as possible to the database.
(Note that this join will compare user.id to certificate.user_id by default. So, if your 'primary key' in your users table is named 'user_id', then this will not work. In order to get it to work, you should either use the standard "id" as the primary key, or you will need to specify that 'user_id' is your primary key in the User model)

Related

Joining two ActiveRecord associations on common attribute

Let's say I have a User model. User has 2 has_many associations, that is User has many pencils and has many cars. Cars and Pencils table has same attribute, :date, and separate such as :speed(car) and :length(pencil). I want to join a user's pencils and cars on their common attribute, :date, so that I have an array/relation [:date, :speed, :length]. How do I achieve that, I tried joins and merge but they were no use.
I'd definitely recommend getting this into a query rather than a loop, for efficiency's sake. I think this will work:
Car.joins(:user => :pencils).where("pencils.date = cars.date")
And if you want to reduce it to the array immediately:
Car.joins(:user => :pencils).where("pencils.date = cars.date").pluck("cars.date", "cars.speed", "pencils.length")
If you need to include matches where date is nil, you might need to add:
Car.joins(:user => :pencils).where("(pencils.date = cars.date) OR (pencils.date IS NULL AND cars.date IS NULL)")
Many more efficient options exist, but here is one possible approach:
class User < ActiveRecord::Base
def get_merged_array
dates = (cars.map(&:date) & pencils.map(&:date))
results = []
dates.each do |date|
cars.where(date: date).each do |car|
pencils.where(date: date).each do |pencil|
results << [date, car.speed, pencil.length]
end
end
end
results
end
end

how to get .sample, if sample exists then get another

I'm getting 5 samples (randoms) from a List, using:
list = List.all.sample(5)
list.each do |list|
list.statuses.create(:user_id => #user.id)
end
in my Status model I have:
validates :list_id, uniqueness: { scope: :user_id }
that validates that the list_id is unique for the user.
However what happens is that it it gives 5 samples, and if one of those are present in statuses it doesn't save.
What I'd like to accomplish though is that it goes and finds a new list, so I always end up with 5 samples, instead of say 3-4 because it got 2 random duplicates.
What I am trying to do is record which list id that gets sent for the specific user, so that when they do it again the same list id doesn't get sent. But a new random 5.
This should do it:
specific_user = User.find(params[:id])
lists = List.order('random()') # This random() is a PostgreSQL function!
lists = lists.where('id NOT IN (?)', specific_user.lists.pluck(:id).presence || -1) # exclude the list of the specific_user
lists = lists.limit(5) # limit to only 5 records
lists.each do |list|
list.statuses.create(:user_id => specific_user.id)
end
First filter out the duplicates, then sample from the remainder. So if User have a has_many :lists you could do something like:
(List.all - #user.lists).sample(5)
The has_many could be through :statuses.

RoR Method to search for data in a table and then update another column on another table based on that data

I have a program i am making changes to for my work. I know what i must do but i am having problems getting the code to work. I come from a Java and C background.
i have two tables one table called customprojecschedule_lines has a project_id,workorder_base_id, and other various columns column.
The other table called customschedule has an id, workorder column and various other columns.
I have a method and variable called work order.
I am trying to get an SQL statement that will do that like this:
class Customschedule < ActiveRecord::Base
set_table_name "customschedules"
after_create :build_customprojectschedule_lines
has_many :customprojectschedule_lines, :dependent => :destroy
has_one :projectschedule_cost
delegate :est_cost, :act_cost, :to => :projectschedule_cost, :allow_nil => true
attr_accessor :workorder_base, :lots
def workorder
customschedule.where(:id => customprojectschedule_lines.product_id)
end
def workorder=(wo)
#workorder_base = wo
customprojectschedule_lines.each do |pl|
pl.update_attributes({:workorder_base_id => wo})
end
end
def build_customprojectschedule_lines
lines = #lots.split(',').inject([]) do |lines, lot_id|
line = customprojectschedule_lines.find_or_initialize_by_workorder_lot_id(lot_id)
if line.new_record?
p workorder_base
line.workorder_base_id = #workorder_base
line.line_no = lot_id
line.workorder_split_id = 0
end
lines << line
end
customprojectschedule_lines.replace(lines)
end
Basically what i would like is that whenever a user enters a workorder on the form number goes into the database gets the stored values with that record and then retrieves the ID(from that record) and puts that same id in my other table.
However, i keep getting this error:
undefined local variable or method `customschedule' for #
<Customschedule:0x00000005542040>
Before when i was trying things i kept getting a weird looking select statement saying that Customschedule ID was null.
We use oracle here.
This is my first time posting on here so please let me know if i need anything else.
Any help is greatly appreciated.
I think all you need is to upcase the first letter in this line
customschedule.where(:id => customprojectschedule_lines.product_id)
change it to
Customschedule.where(:id => customprojectschedule_lines.product_id)

How to enforce uniqueness of an entire row?

I've seen other SO questions like - How do you validate uniqueness of a pair of ids in Ruby on Rails? - which describes adding a scoped parameter to enforce uniqueness of a key pair, i.e. (from the answer)
validates_uniqueness_of :user_id, :scope => [:question_id]
My question is how do you do this kind of validation for an entire row of data?
In my case, I have five columns and the data should only be rejected if all five are the same. This data is not user entered and the table is essentially a join table (no id or timestamps).
My current thought is to search for a record with all of the column values and only create if the query returns nil but this seems like a bad work around. Is there an easier 'rails way' to do this?
You'll need to create a custom validator (http://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations):
class TotallyUniqueValidator < ActiveModel::Validator
def validate(record)
if record.attributes_for_uniqueness.values.uniq.size == 1
record.errors[:base] << 'All fields must be unique!'
end
end
end
class User
validates_with TotallyUniqueValidator
def attributes_for_uniqueness
attributes.except :created_at, :updated_at, :id
end
end
The important line here is:
if record.attributes_for_uniqueness.values.uniq.size == 1
This will grab a hash of all the attributes you want to check for uniqueness (in this case everything except id and timestamps) and converts it to an array of just the values, then calls uniq on it which returns only uniq values and if the size is 1 then they were all the same value.
Update based on your comment that your table doesn't have an id or timestamps:
You can then simply do:
if record.attributes.except(:id).values.uniq.size == 1
...because I'm pretty sure it still has an id unless you're sure it doesn't then just remove the except part.
You can add a unique index to the table in a migration:
add_index :widgets, [:column1, :column2, :column3, :column4, :column5], unique: true
The resulting index will require that each combination of the 5 columns must be unique.

Rails Active Record, how to have an additional field parameter in the result of a query?

I actually have this model:
class Role < ActiveRecord::Base
acts_as_authorization_role
def self.all_join_wisp
self.connection.select_all("SELECT roles.*, wisps.name AS wisp_name FROM roles LEFT JOIN wisps ON wisps.id = roles.authorizable_id")
end
end
the method all_join_wisp does almost what I want, it adds the field wisp_name, except it return a hash and not an active record object.
Is there a way to retrieve an active record object instead?
The Role model does not have belongs_to and the wisp model does not have has_many :roles , I guess it was done for flexibility or something (I did not develop the application I'm working on).
Edit: Solution implemented
Solution implemented here: https://github.com/nemesisdesign/OpenWISP-Geographic-Monitoring/blob/287861d95fffde35d78b76ca1e529c21b0f3a54b/app/models/role.rb#L25
Thanks to #house9
you can use find_by_sql - you get back what looks like an activerecord object but isn't really, it will have attributes from your query but they will all be string data types instead of the real types, often this is ok
def self.all_join_wisp
self.find_by_sql("SELECT roles.*, wisps.name AS wisp_name FROM roles LEFT JOIN wisps ON wisps.id = roles.authorizable_id")
end
then
list = Role.all_join_wisp
list.each do |x|
puts x
puts x.inspect
puts x.id # the role id
puts x.id.is_a?(Integer) # false
puts x.id.is_a?(String) # true
puts x.name # the role name
puts x.wisp_name # wisp name, NOTE: Role does not really have this attribute, find_by_sql adds it
end
model
class Role < ActiveRecord::Base
def self.do
joins("LEFT JOIN wisps w ON
w.id = roles.id").select("roles.*,
wisps.name AS wisp_name")
end
end
controller
#roles = Role.do
#wisps = #roles.map {|i| i.wisp_name}
view
<%= #wisps %>

Resources