Accessing column stored in a join table - Rails - ruby-on-rails

I have a join table called ProductFeatures which joins Product and Feature instances via has_many: ..., through: product_features, and has an additional column called rating.
I want to add .rating method on Feature which will return a rating float based on specific product instance that is calling it. Something like:
Product.find(...).features.first.rating #=> should specific product_feature rating
I've tried:
passing caller_id as an argument to .rating. This works, but makes me use product.id each time I want to get a specific product rating feature.
Obtaining a caller instance id from inside the method using .caller (with binding_of_caller, or vanilla Ruby), but .caller does not seem to let me get a calling instance id, and would also fail in tests as the caller would be the spec's ExampleGroup

You can get these data with other way.
I want to add #rating method on Feature which will return a rating
float based on specific product instance that is calling it. Something
like:
## Controller add this code snipped
get_feature = Product.find(...).features
rating(get_feature)
protected
def rating(get_all_feature)
all_rating = []
get_all_feature.each do |feature|
all_rating << feature.product_features.each { |u| u.rating }
end
all_rating
end
Hope this help you!

Related

How to use an argument's value to build a `.collection` method for an associated model?

I'm not sure what the correct terminology is for my question, but is it possible to use an argument's value to "build" its appropriate .collection method?
For instance, a Submission has_many images, tags, and documents. And depending on what the user is interacting with, I'd like to create the appropriate association.
Initially, I had it set up for only the tags...
def add_to_submission(task, user, tag)
sub = Submission.find_or_create_by(task: task, user: user)
sub.tags << tag
end
But is there a way I can generalize it further so that the third argument can by more dynamic? So rather than only accept a tag, it could be used like
add_to_submission(#task, #current_user, #new_image)
Something along the lines of...
def add_to_submission(task, user, associated_item)
sub = Submission.find_or_create_by(task: task, user: user)
items = associated_item.pluralize
sub.items << associated_item
end
For dynamic calling of methods .send can be used. You can pass a symbol of the method name. You should be able to do something like this, but I would make sure to have good unit tests for your method.
def add_to_submission(task, user, associated_item)
submission = Submission.find_or_create_by(task: task, user: user)
children = associated_item.pluralize.to_sym
submission.send(children) << associated_item
end

Rails: Issue in adding alias name in rails sql query

i am working on maintenance application.basically i have a method in my code which is called by multiple methods. This method basically forms sql query as shown below.
def filter_data
data = Student.all
data = data.where(:id => params[:id]) if params[:id]
data = data.where(:roll_no => params[:roll_no]) if params[:roll_no]
return data
end
i am using this method in many places in my application as shown below
def get_user_data
data = filter_data
data.pluck("sum(marks)")
end
Now the issue is that that i want to add inner join to "students" table inside filter_data method. Now since "marks" column is in both table i will have to add alias in "get_user_data" method as shown below.
data.pluck("sum(students.marks)")
Adding alias is not an issue but i will have to add alias throughout my application where "filter_data" method is used. This is very time consuming process and i want to avoid it. is there any way to tell active-record to use "xxx" as an alias name when alias is not present in sql query.
Please let me know if anyone has any solutions for this issue.
you can put an alias in models as
# alias_attribute :new_column_name, :column_name_in_db
alias_attribute :model_name_marks, :marks
So, probably you should be able to pluck with new name without sql alias.
Note: Untested but worth a try..
Also, I think, filter_data method can be reduced to one query instead of multiple..
in student.rb
scope :filter_by_id, ->(id) { where(id: id) if id.present? }
scope :filter_by_roll_no, ->(roll_no) { where(roll_no: roll_no) if roll_no.present? }
then your method becomes,
def filter_data
Student.filter_by_id(params[:id]).filter_by_roll_no(params[:roll_no])
end

Rails: Using array from model in a view

I have a static array in my User model declared like this:
class User < ActiveRecord::Base
...
states = ['NYC', 'CAL', ...]
...
end
I know I should create a model for the states but I figured I just need the list for registration purposes. When I try to use it in a view like this:
= f.select(:state, options_for_select(states))
I get a Undefinded Method error. I tried using instance variables through the controller and that didnt work either. Whats the correct way of doing this?
You should be able to access it as
User::STATES
that's assuming you upcase it from states to STATES since that's idiomatic :)
Another option is to create a class method that returns the array
def self.states
['NYC', 'CAL', etc]
end
Capitalizing the constant in the model and using the Model::CONSTANT syntax is probably the most common way to do this.
States collection is not specific to a user, so I wouldn't have it under the User model. As shown in this answer, I would add a us_states helper to your application, and use that in your views:
= f.select(:state, options_for_select(us_states))

Scope vs Class Method in Rails 3

Based on the Rails 3 API, the difference between a scope and a class method is almost non-existent.
class Shipment < ActiveRecord::Base
def self.unshipped
where(:shipped => false)
end
end
is the same as
scope :unshipped, where(:shipped => false)
However, I'm finding that I'm sometimes getting different results using them.
While they both generate the same, correct SQL query, the scope doesn't always seem to return the correct values when called. It looks like this problem only occurs when its called the same way twice, albeit on a different shipment, in the method. The second time it's called, when using scope it returns the same thing it did the first time. Whereas if I use the class method it works correctly.
Is there some sort of query caching that occurs when using scope?
Edit:
order.line_items.unshipped
The line above is how the scope is being called. Orders have many line_items.
The generate_multiple_shipments method is being called twice because the test creates an order and generates the shipments to see how many there are. It then makes a change to the order and regenerates the shipments. However, group_by_ship_date returns the same results it did from the first iteration of the order.
def generate_multiple_shipments(order)
line_items_by_date = group_by_ship_date(order.line_items.unshipped)
line_items_by_date.keys.sort.map do |date|
shipment = clone_from_order(order)
shipment.ship_date = date
line_items_by_date[date].each { |line_item| shipment.line_items << line_item }
shipment
end
end
def group_by_ship_date(line_items)
hash = {}
line_items.each do |line_item|
hash[line_item.ship_date] ||= []
hash[line_item.ship_date] << line_item
end
hash
end
I think your invocation is incorrect. You should add so-called query method to execute the scope, such as all, first, last, i.e.:
order.line_items.unshipped.all
I've observed some inconsistencies, especially in rspec, that are avoided by adding the query method.
You didn't post your test code, so it's hard to say precisely, but my exeprience has been that after you modify associated records, you have to force a reload, as the query cache isn't always smart enough to detect a change. By passing true to the association, you can force the association to reload and the query to re-run:
order.line_items(true).unshipped.all
Assuming that you are referencing Rails 3.1, a scope can be affected by the default scope that may be defined on your model whereas a class method will not be.

I want to map my database lookup tables to a hash, good idea?

I am developing a Rails web application and am confused about how to utilize the lookup table values in my models. Here is an example model from my app:
table name: donations
id
amount
note
user_id
appeal_id
donation_status_id
donation_type_id
is_anonymous
created_at
updated_at
The fields *donation_status_id* and *donation_type_id* refer to lookup tables. So in my code I have several random places where I make calls like this:
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus.find_by_name("completed").id
#do something
end
To my inexperienced eyes, a one-off query to the DonationStatus table seems incredibly wasteful here, but I don't see any other good way to do it. The first idea I thought of was to read all my lookup tables into a hash at application startup and then just query against that when I need to.
But is there a better way to do what I am trying to do? Should I not worry about queries like this?
Thanks!
Since you have two models, you should use ActiveRecord Model Associations when building the models.
class Donation
has_one :donation_status
end
class DonationStatus
belongs_to :donation
end
Then when you do
my_donation = Donation.find(params[:id])
if my_donation.donation_status.status_name == 'complete'
#do something
end
For more information, you may want to read up how rails is doing the model associations http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Don't worry about performance, rails has taken care of that for you if you follow how the way it should be done
How about putting it in a constant? For example, something like this:
class DonationStatus < ActiveRecord::Base
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id
PENDING_DONATION_ID = DonationStatus.find_by_name("pending").id
# ...
end
class DonationsController < ApplicationController
def some_action
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus::COMPLETED_DONATION_ID
#do something
end
end
This way, DonationStatus.find_by_name("pending").id gets executed exactly one. I'm assuming, of course, that this table won't change often.
BTW, I learned this trick in Dan Chak's book, Enterprise Rails.
EDIT: I forgot to mention: in practice, I declare constants like this:
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id rescue "Can't find 'completed' in donation_statuses table"
What you could do is add this method to Donation:
# Donation.rb
def completed?
self.donation_status.name == 'completed' ? true : false
end
And then just do my_donation.completed?. If this is called a second time, Rails will look to cache instead of going to the DB.
You could add memcached if you want, or use Rails' caching further, and do:
def completed?
return Rails.cache.fetch("status_#{self.donation_status_id}_complete") do
self.donation_status.name == 'completed' ? true : false
end
end
What that will do is make a hash key called (for example) "status_1_complete" and if it's not defined the first time, will evaluate the block and set the value. Otherwise, it will just return the value. That way, if you had 1,000,000,000 donations and each of them had donation_status 1, it would go directly to the cache. memcached is quite fast and popular.

Resources