i have been learning to using rspec.
i have to test this one:
Book.where(old:false, id:user.collections.select(:book_id).group(:book_id).map(&:book_id)).map{|b|[b.name, b.id] }
what i've already done is :
#user = Fabricate(:user)
#book1 = Fabricate(:book, old:false)
#book2 = Fabricate(:book, old:true)
#collection = Fabricate(:collection, book_id:#book1.id)
#collection2 = Fabricate(:collection, book_id:#book2.id)
#user.collections << #book1 << #book2
#books = Book.where(old:false, id:#user.collections.select(:book_id).group(:book_id).map(&:book_id)).map{|b|[b.name, b.id]}
.....
it "books" do
subject[:filter].should == #books.map{|d| [d.name, d.id]}
end
it expected a book, but i got 0. did i forget something?
thanks in advance for any idea and help!
Your code is very unreadable and hard to test. You should split the execution into smaller methods.
Book.where(old:false, id:user.collections.select(:book_id).group(:book_id).map(&:book_id)).map{|b|[b.name, b.id] }
could be
class Book
def self.find_books_with_name(ids)
where(old: false, id: ids).map { |b| [b.name, b.id] }
end
end
Then you can call it with:
Book.find_books_and_name(user.collections.select(:book_id).group(:book_id).map(&:book_id))
which appears equally insane to me. Add to your user model and collection model something like
class Collection
def self.book_ids
select(:book_id).group(:book_id).map(&:book_id)
end
end
Now you have
Book.find_books_and_name(user.collections.book_ids)
and now you can (and you should) test each method separately. A failure will be easier to spot and the test itself will be much more simple, without all that coupling to various models and records.
Related
I have a recurrent code with conditions in a new permission recently added code. It was not designed from the start, so it is a little messy:
#usuarios = Usuario.menores_de_edad.con_autorizacion(params[:autorizacion]).con_nombre(params[:nombre])
# master puede ver todos, asà que ignora los permisos
if !usuario_actual.es_master?
if usuario_actual.permiso.blank?
# Si es admin y no tiene permisos establecidos
#usuarios = Usuario.where(id: nil)
else
# Lee de que niveles puedes mostrar los usuarios
#usuarios = #usuarios.del_nivel(usuario_actual.permiso.niveles)
end
end
if usuario_actual.es_admin_occ?
#usuarios = #usuarios.de_occ
end
I want to make it a scope this way:
#usuarios = Usuario.menores_de_edad.con_autorizacion(params[:autorizacion]).con_nombre(params[:nombre])
#usuarios = #usuarios.permitibles(usuario_actual)
How can I make it work? I have currently this:
class Usuario < ActiveRecord::Base
scope :permitibles, lambda{ |usuario_actual|
# master can see everything, so, don't scope anything at all
if !usuario_actual.es_master?
if usuario_actual.permiso.blank?
# return nothing if you don't have permissions
where(id: nil)
else
# this is a scope
del_nivel(usuario_actual.permiso.niveles)
end
if usuario_actual.es_admin_occ?
# this is a scope
de_occ
end
end
}
end
The problem is, inside the scope, that I don't know how to chain the other scopes, I mean, "de_occ" must be chained depending on the other condition, but right now this will will not work as is, because it will return only one scope instead of chaining "de_occ" to the first condition. How can I achieve that?
Thanks!
Ok, it's little problematic for me to go through your code. The best practice is not to use you mother langauge as a source of model and method names.
The basic approach for your problem would be to move the logic into a method and chains scopes / sqls. Here is a proof of concept:
scope :menores_de_edad, ->() {where(something: true)}
scope :del_nivel, ->(params) {where(field: param)}
scope :de_occ, ->() { ... }
def self.we_are_chaining_scopes
result = self.scoped
if !usuario_actual.es_master?
if usuario_actual.permiso.blank?
# return nothing if you don't have permissions
result = result.menores_de_edad
else
# this is a scope
result = result.del_nivel(usuario_actual.permiso.niveles)
end
if usuario_actual.es_admin_occ?
# this is a scope
result = result.de_occ
end
end
result
end
You can make it even nicer by creating a service object for this query. Rough example:
class SpecificQuery
def initialize(relation = Model.scoped)
#relation = relation
end
private
def scoped_to(&block)
#relation = #relation.instance_eval(&block)
end
def scoped_behavior_method_one(param)
scoped_to { where(:something: param) }
end
def scoped_behavior_method_two(param)
scoped_to { ... }
end
end
And then you can chain scopes nicely as well + you're encapsulating the logic which should not necessarily belongs to your model. Also, it's easier to write specs :) I hope that helps.
For every request, I get this in the logs:
Completed 200 OK in 854ms (Views: 1.0ms | ActiveRecord: 17.0ms)
Is it possible to get it to also include the number of queries?
Something like:
Completed 200 OK in 854ms (Views: 1.0ms | ActiveRecord: 17.0ms | Queries: 10)
Ideally, I'd like all the "cached" ones to show up in that count too. Ie, even if the "cache" is saving me from "N+1" queries from hitting the DB, I still want to know I have a problem.
I'm fine with monkeypatching / manually editing something, since I really want this just for my dev box.
(If this can be made civilizedly so I can have it in production, that's even better, but if not, I'm fine with just having a manually modified Rails in my own machine)
Thanks!
Daniel
I know the ThinkingSphinx gem does something quite like this, adding the time spent running Sphinx queries to the summary in the log. You can probably do something similar ( maybe by making your own gem, since I bet other people would appreciate this functionality) to make the number of queries appear.
I haven't really looked hard at how it works, but it looks like modifications to ActionController and LogSubscriber are responsible:
lib/thinking_sphinx/action_controller.rb:
module ThinkingSphinx
module ActionController
extend ActiveSupport::Concern
protected
attr_internal :query_runtime
def cleanup_view_runtime
log_subscriber = ThinkingSphinx::ActiveRecord::LogSubscriber
query_runtime_pre_render = log_subscriber.reset_runtime
runtime = super
query_runtime_post_render = log_subscriber.reset_runtime
self.query_runtime = query_runtime_pre_render + query_runtime_post_render
runtime - query_runtime_post_render
end
def append_info_to_payload(payload)
super
payload[:query_runtime] = query_runtime
end
module ClassMethods
def log_process_action(payload)
messages, query_runtime = super, payload[:query_runtime]
messages << ("Sphinx: %.1fms" % query_runtime.to_f) if query_runtime
messages
end
end
end
end
lib/thinking_sphinx/active_record/log_subscriber.rb:
require 'active_support/log_subscriber'
module ThinkingSphinx
module ActiveRecord
class LogSubscriber < ActiveSupport::LogSubscriber
def self.runtime=(value)
Thread.current['thinking_sphinx_query_runtime'] = value
end
def self.runtime
Thread.current['thinking_sphinx_query_runtime'] ||= 0
end
def self.reset_runtime
rt, self.runtime = runtime, 0
rt
end
def initialize
super
#odd_or_even = false
end
def query(event)
self.class.runtime += event.duration
return unless logger.debug?
identifier = color('Sphinx Query (%.1fms)' % event.duration, GREEN, true)
query = event.payload[:query]
query = color query, nil, true if odd?
debug " #{identifier} #{query}"
end
def message(event)
return unless logger.debug?
identifier = color 'Sphinx', GREEN, true
message = event.payload[:message]
message = color message, nil, true if odd?
debug " #{identifier} #{message}"
end
def odd?
#odd_or_even = !#odd_or_even
end
def logger
return #logger if defined? #logger
self.logger = ::ActiveRecord::Base.logger
end
def logger=(logger)
#logger = logger
end
attach_to :thinking_sphinx
end
end
end
I hope this helps.
I'm trying to step through a list of records that are retrieved with find_each.
I patterned my controller code on the answer in this stack overflow post, but I'm still getting a "No Block Given (Yield)" error.
I'm just getting started in Ruby and Rails and I haven't yet found a full fledged explanation (lots of basic examples though) of blocks and yield that gives me what I need.
My code looks like this:
def select_save
#class = params[:class]
#student_id = params[:id]
#class.each do |id|
old_subject = Subject.find(id)
new_subject = old_subject.dup
new_subject.student_id = #student_id
new_subject.save
Assignment.find_each.where(:subject_id => id) do |assignments|
assignments.each do |a|
new_assignment = a.dup
new_assignment.subject_id = new_subject.id
new_assignment.save
end
end
end
respond_to do |format|
format.html { redirect_to #student, :notice => 'Subject and assignments created.' }
end
end
and the error points to the line with find_each.
I know I need a block to yield to, but how exactly that would look in this particular case escapes me.
Thanks for any suggestions.
You're passing a block to where and no block to find_each. You can't do that. You need to reverse find_each and where on this line, the order is important as the block is passed to the last method invoked:
Assignment.find_each.where(:subject_id => id) do |assignments|
It should read:
Assignment.where(:subject_id => id).find_each do |assignments|
Your next problem is you're trying to iterate over assignments, which is a single Assignment. find_each is already doing the iteration for you, passing one assignment into the block at a time. The block should read:
Assignment.where(:subject_id => id).find_each do |assignment|
new_assignment = assignment.dup
new_assignment.subject_id = new_subject.id
new_assignment.save
end
I'm going to make the assumption that your Subject has many Assignments, since you have a subject_id inside your Assignment class. If this is the case, the final and most correct way to write your loop would be:
old_subject.assignments.each do |assignment|
new_assignment = assignment.dup
new_assignment.subject_id = new_subject.id
new_assignment.save
end
I've been working on a rails project where I am needed to serialize permissions for user roles and store in the database. As far as that goes I'm all good. Now my problem comes when I want to modify the serialized data from a rails generated form.
I acted on instinct and tried with the expected behavior.
That would be to use something like this:
f.check_box :permissions_customer_club_events_read
But as no getters or setters exist for the serialized data, this doesn't work (obviously :p). Now I wonder how I would go about tackling this problem and the only thing that comes to mind is dynamically generating getter and setter methods from my serialized hash.
Example:
def permissions_customer_club_events_read=(val)
permissions[:customer][:club][:events][:read] = val
end
def permissions_customer_club_events_read
permissions[:customer][:club][:events][:read]
end
Anyone understand what I'm getting at?
Here is my Model:
class User::Affiliation::Role < ActiveRecord::Base
require 'yajl'
class YajlCoder
def dump data
Yajl.dump data
end
def load data
return unless data
Yajl.load data
end
end
serialize :permissions, YajlCoder.new
after_initialize :init
def init
## Sets base permission structure ##
self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")
end
end
I suggest you have a look at something like attr_bucket. Ostensibly, this can be used to solve some inheritance annoyances, but it will also solve your problem for you. Here is the essence.
It looks like you know what all your permissions are, but you want to serialize all of them into the same database field. But within your actual rails app, you want to treat all your permissions as if they were totally separate fields. This is exactly what a solution like attr_bucket will let you do. Let's take your example, you would do something like this:
class User::Affiliation::Role < ActiveRecord::Base
attr_bucket :permissions => [:permissions_customer_club_events_read, :permissions_customer_club_events_write, :permission_do_crazy_things]
after_initialize :init
def init
## Sets base permission structure ##
self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")
end
end
Now you will be able to use permissions_customer_club_events_read, permissions_customer_club_events_write, permission_do_crazy_things as if they were separate database fields (this includes using them in forms etc.), but when you actually save your objects all those fields would get 'bucketed' together and serialized into the :permissions field.
The only caveat is the serialization mechanism, I believe attr_bucket will serialize everything using YAML, whereas you were using JSON. If this doesn't matter then you're golden, otherwise you might need to patch attr_bucket to use json instead of YAML which should be pretty straight forward.
Sorry if I did not understand the question ;)
You could have a customdata module, included in your model, and use method_missing:
module CustomData
def self.included(base)
base.instance_eval do
after_save :save_data
end
def method_missing(method, *args, &block)
if method.to_s =~ /^data_/
data[method] ? data[method] : nil
else
super
end
end
def data
#data ||= begin
#get and return your data
end
end
private
def save_data
end
end
With this method, you would have to use f.check_box :data_permissions_customer_club_events_read
It's not really complete, but I hope you get the idea ;)
attr_bucket seems like a good solution too.
This worked out for me in the end, this is how I solved it.
serialize :permissions, YajlCoder.new
after_initialize :init
def init
self.permissions ||= YAML.load_file("#{Rails.root}/config/permissions.yml")['customer']
build_attributes_from self.permissions, :permissions
end
private
def build_attributes_from store, prefix, path=[]
store.each do |k,v|
if v.class == Hash
build_attributes_from v, prefix, ( path + [k] )
else
create_attr_accessors_from prefix, ( path + [k] )
end
end
end
def create_attr_accessors_from prefix, path=[]
method_name = prefix.to_s + "_" + path.join('_')
class << self
self
end.send :define_method, method_name do
self.permissions.dig(:path => path)
end
class << self
self
end.send :define_method, "#{method_name}=" do |value|
self.permissions.dig(:path => path, :value => value)
end
end
And some monkey patching for hashes...
class Hash
def dig(args={})
path = args[:path].to_enum || []
value = args[:value] || nil
if value == nil
path.inject(self) do |location, key|
location.respond_to?(:keys) ? location[key] : nil
end
else
path.inject(self) do |location, key|
location[key] = ( location[key].class == Hash ) ? location[key] : value
end
end
end
end
Now getter and setter methods are generated for all of the serialized fields.
How can you tell if a generate or destroy command has been used to invoke a custom generator?
In Rails 2 you could do this:
if options[:command] == :destroy
...
end
I want to print out some helpful information, but only when rails generate has been called, not when rails destroy is called:
if is_generating
puts "You're generated something!"
end
Thanks.
check the generator class's behavior. It seems you should get either :invoke for generate or :revoke for destroy. For example, I added this:
class PatternGenerator < Rails::Generators::NamedBase
def echo_behavior
p "generate? #{generating?}"
p "destroying? #{destroying?}"
end
protected
def generating?
:invoke == behavior
end
def destroying?
:revoke == behavior
end
end
Running this, I get:
younker % rails g pattern foo
"generate? true"
"destroying? false"
younker % rails destroy pattern foo
"generate? false"
"destroying? true"
Seems to work and makes sense, so that's my final answer.