Rails 3, Model Methods / Calculated Attributes - ruby-on-rails-3

I am dealing with numerous calculations to bring various values within a model to a simple TRUE or FALSE. Problem is, these calculations are pretty intense and not something I want to create a long, hard to follow SQL statement for. I'd rather just have the entire calculation within a method that the model could check for when returning records.
I've tried numerous ways to accomplish this, and when looking up other similar feats, others push newbs like me to SQL which might serve most purposes but will not serve mine as the calculations being done are somewhat external to the model.
Model:
class Quality < ActiveRecord::Base
...
def passed_inspection
[code that calculates based on values in model]
end
Controller:
#records = Quality.where('passed_inspection = true')
View:
Did pass inspection?: <%= record.passed_inspection %>

It sounds like the solution to your problem would be to use a Scope with a Class Method to help clean up your model. Essentially you would set up your model like this:
class Quality < ActiveRecord::Base
def self.passed_inspection
# Code that does your calculations
end
scope :passed, passed_inspection() # This needs to be below the function above
end
Then you could get this data by calling it like this
#records = Quality.passed
There is a rails cast about this problem if you need any more information: RailsCast #215 Advanced Queries
Edit: Fixed some terrible grammar

Related

How do I retrieve a random GET request in Ruby on Rails 5?

I've created my REST API based on Michael Scott from the Office, so far if you go onto the main part of the API it displays all the quotes /api/v1/quotes/ and you can get individual ones if you add an id number such as /api/v1/quotes/5,but I want users to be able to get a random quote when they put in a GET request.
This would preferably be done with an extra part of the URL such as /api/v1/quotes/random.
I've looked at a lot online but I can't figure this out. Do I put my code into the quotes controller and create another function or do I have to put this somewhere else such as routes.db? Also do I put in the SQL function of RANDOM or is there a better and more efficient way to randomise it. My database only has 50 records in it and it's done in mysql2.
Thanks in advance.
It's my first time posting here as usually I hate asking for help as I think I can always figure it out myself but I'm extremely new to Ruby so I'm fairly clueless on how to solve this. If you need me to clarify my question then just let me know.
You can do that in Model as well as controller.
Model:
class Model < ApplicationRecord
def self.random
Model.limit(1).order("RANDOM()").first
end
end
Controller: In method show
def show
render json: Model.find_by(id: params[:id]) || Model.random
end
I hope that helpful.
I would configure a quotes/random collection route:
# in config/routes.rb
resources :quote do
get 'random', on: :collection
end
and use a controller method like this:
# in controllers/quotes_controller.rb
def random
#quote = Quote.order('RAND()').first
render :show
end
Please note: Returning a single random row from a table is database specific – for MySQL
ORDER BY RAND() LIMIT 1
seems to be the way to go.

Find_by_sql and calculated field

I am using find my sql, and I want to calculate something in the database and add to my model.
I wil try to simplify the code
class User < ActiveRecord::Base
attr_accessor :comments_count
end
And somewhere else:
#users = User.find_by_sql("select * (select count(*) from comments where user_id=u.id) as comments_count from Users u")
#user.map{|u| puts u.comments_count}
Any help?
ActiveRecord will, unfortunately, not just match any selected columns to attributes on the model. However, something like that would probably be fairly easy to put together. First you'd need to overload find_by_sql to something like this -
def self.find_by_sql(sql)
values = connection.select_all(sql)
objects = []
values.each do |row|
objects << self.new(row)
end
objects
end
Then the initialize function for your model can mass assign the schema-based attributes, and then handle assigning your custom attributes. Its not as clean or simple a solution as you were probably hoping for, but it should accomplish the task. And you could probably write the initialize function so that it is fairly generic, in that it can mass assign all the schema-based attributes, and then match leftover keys that were passed in with any attributes that may be present on self.
Ok, i got it.
It has nothing to do with attr_accessor. I had to remove it.
#users = User.find_by_sql("select * (select count(*) from comments where user_id=u.id) as comments_count from Users u")
#user.map do |u|
puts u.comments_count if #user.has_attribute?(:comments_count)
end

How can I query a rails 3 app efficiently?

I have a search form that queries one table in the database but there are many parameters (language, level, creator etc). The code below works provided the fields in question are filled in but I want to change it to:
a) add more parameters (there are several);
b) allow for a field to be empty
Here's the code in the controller:
#materials = Material.find(:all, :conditions => {:targ_lang => params["targ_lang"],
:inst_lang => params["inst_lang"],
:level => params["level"]})
Totally new to this I'm afraid but a lot of the documentation suggests I should be using "where".
Since Rails 3 you can use the where() function:
#materials = Material.where(targ_lang: params["targ_lang"], inst_lang: params["inst_lang"], level: params["level"])
Also, you could take a look at scopes
These allow you to set what you want to do in the model and call it in the controller for example:
class Material < ActiveRecord::Base
scope :active, where(active_state: true)
end
Then in the controller you do something like:
#active_materials = Material.active
This can be useful if you are joining several models and want to keep your controllers less messy.
To conclude, like #RVG said, seachlogic is quite useful as well as, there are others like Sphinx and Elastic Search. You should take a quick look at these and use the one you feel most confortable with.
If you are using search functionality in your app I suggest using SearchLogic gem
It is easy to use and effective..
SearchLogic
RailsCasts for searchlogic

ARel mimic includes with find_by_sql

I've got a fairly complex sql query that I'm pretty sure I can't accomplish with ARel (Rails 3.0.10)
Check out the link, but it has a few joins and a where exists clause, and that I'm pretty sure is too complex for ARel.
My problem however is that, before this query was so complex, with ARel I could use includes to add other models that I needed to avoid n+1 issues. Now that I'm using find_by_sql, includes don't work. I still want to be able to fetch these records and attach them to my model instances, the way includes does, but I'm not quite sure how to achieve this.
Can someone point me in the right direction?
I haven't tried joining them in the same query yet. I'm just not sure how they would be mapped to objects (ie. if ActiveRecord would properly map them to the proper class)
I know that when using includes ActiveRecord actually makes a second query, then somehow attaches those rows to the corresponding instances from the original query. Can someone instruct me on how I might do this? Or do I need to join in the same query?
Let's pretend that the SQL really can't be reduced to Arel. Not everything can, and we happen to really really want to keep our custom find_by_sql but we also want to use includes.
Then preload_associations is your friend:
(Updated for Rails 3.1)
class Person
def self.custom_query
friends_and_family = find_by_sql("SELECT * FROM people")
# Rails 3.0 and lower use this:
# preload_associations(friends_and_family, [:car, :kids])
# Rails 3.1 and higher use this:
ActiveRecord::Associations::Preloader.new(friends_and_family, [:car, :kids]).run
friends_and_family
end
end
Note that the 3.1 method is much better, b/c you can apply the eager-loading at any time. Thus you can fetch the objects in your controller, and then just before rendering, you can check the format and eager-load more associations. That's what happens for me - html doens't need the eager loading, but the .json does.
That help?
I am pretty sure that you can do even the most complex queries with Arel. Maybe you are being over-skeptical about it.
Check these:
Rails 3: Arel for NOT EXISTS?
How to do "where exists" in Arel
#pedrorolo thanks for the heads up on that not exists arel query, helped me achieve what I needed. Here's the final solution (they key is the final .exists on the GroupChallenge query:
class GroupChallenge < ActiveRecord::Base
belongs_to :group
belongs_to :challenge
def self.challenges_for_contact(contact_id, group_id=nil)
group_challenges = GroupChallenge.arel_table
group_contacts = GroupContact.arel_table
challenges = Challenge.arel_table
groups = Group.arel_table
query = group_challenges.project(1).
join(group_contacts).on(group_contacts[:group_id].eq(group_challenges[:group_id])).
where(group_challenges[:challenge_id].eq(challenges[:id])).
where(group_challenges[:restrict_participants].eq(true)).
where(group_contacts[:contact_id].eq(contact_id))
query = query.join(groups).on(groups[:id].eq(group_challenges[:group_id])).where(groups[:id].eq(group_id)) if group_id
query
end
end
class Challenge < ActiveRecord::Base
def self.open_for_participant(contact_id, group_id = nil)
open.
joins("LEFT OUTER JOIN challenge_participants as cp ON challenges.id = cp.challenge_id AND cp.contact_id = #{contact_id.to_i}").
where(['cp.accepted != ? or cp.accepted IS NULL', false]).
where(GroupChallenge.challenges_for_contact(contact_id, group_id).exists.or(table[:open_to_all].eq(true)))
end
end

rails 3: difference between write_attribute and update_attribute

I did not know about write_attribute until today...
it seems like update_attribute, although not calling validation is still calling the :before_save callbacks, whereas write_attribute doesn't.
Is that the difference between these two methods?
update_attribute actually makes a physical call to the DB. You get a full execution of an UPDATE statement. It's like update_attributes but just for a single column.
While write_attribute writes the attribute for assignment to the model for AR based columns. If you were to overwrite a DB based attribute.
def first_name=(val)
write_attribute :first_name, val
end
# some_model.first_name => 'whatever val is'
def first_name=(val)
#first_name = val
end
# some_model.first_name => nil
I have not looked into write_attribute extensively, but I gather Activerecord based models handle assignments to db based columns slightly differently than your run of the mill accessor.
write_attribute is used when you want to overwrite the default accessors for a method. It is essentially syntactic sugar for self[:attribute]=(value).
Have a look at the ActiveRecord::Base documentationunder the heading "Overwriting default accessors".
If you tried to rewrite the example in the documentation using update_attribute, I'd imagine it would end up in a loop.