I have the following models, which both currently have the same scope:
programme.rb
has_many: :campaigns
scope :visible, -> { where(status: 1) }
camapaign.rb
belongs_to: programme
scope :visible, -> { where(status: 1) }
I would like to edit the campaign scope to only search through the programmes which are visible. Ideally I would like to change that scope to be:
(most likely in a function)
def self.visible
Programme.visible.campaigns.where.(status: 1)
end
But this is returning:
undefined method `campaigns' for #<Programme::ActiveRecord_Relation:0x007f8c9677cd68>
Can someone explain why I can't do this? Also, if any solution to this would be appreciated? preferably without using .joins (But I'll take what I can get at this point)
Thanks
It's hard to tell from your question whether status is an attribute of Programme, Campaign, or both.
Assuming both, you could do this:
# class Programme
has_many :campaigns
scope :visible, -> { where(status: 1) }
# class Campaign
belongs_to :programme
scope :visible, do
joins(:programme).
merge(Programme.visible).
where(status: 1)
end
With this setup, Campaign.visible will only return Campaigns that belong to visible Programmes (and where status == 1, not sure if that means "visible" in your app).
As for why you can't call
Programme.visible.campaigns.where(status: 1)
campaigns is a collection that belongs to a single Programme. It doesn't make sense to ask a collection of Programmes for its campaigns.
Related
i am trying to put 2 queries together in a scope - i am unsure where i am going wrong. your advise would be much appreciated
I want to displays only users whose status is stated as "Accepted" or "nil"
i wrote the below scope and tried others but no success
scope :active_recruiters, -> {where(['status = ? OR status = ?', 'Accepted', 'IS NULL'])}
If you want to chain with OR instead of AND user AREL table constraint
scope = where( where(status: 'Accepted').arel.constraints.reduce( :and ).or( where(status: nil).arel.constraints.reduce( :and ) ) )
Try scope :active_recruiters, -> { where(status: ['Accepted', nil]) }
the query that you're trying to curate would look like this:
WHERE status = 'Accepted' OR status = 'IS NULL';
You're probably looking for something like this instead:
WHERE status = 'Accepted' OR status is null;
try this in your code
scope :active_recruiters, -> {where(['status = ? OR status ?', 'Accepted', 'IS NULL'])}
Even better though, how about this?
scope :active_recruiters, -> { where :status, [nil, 'Accepted'] }
I have created rspec tests for my scopes (scope1, scope2 and scope3) and they pass as expected but I would also like to add some tests for a class method that I have which is what is actually called from my controller (the controller calls the scopes indirectly via this class method):
def self.my_class_method(arg1, arg2)
scoped = self.all
if arg1.present?
scoped = scoped.scope1(arg1)
end
if arg2.present?
scoped = scoped.scope2(arg2)
elsif arg1.present?
scoped = scoped.scope3(arg1)
end
scoped
end
It seems a bit redundant to run the same scope tests for each scenario in this class method when I know they already pass so I assume I really only need to ensure that different scopes are called/applied dependant on the args being passed into this class method.
Can someone advise on what this rspec test would look like.
I thought it might be something along the lines of
expect_any_instance_of(MyModel.my_class_method(arg1, nil)).to receive(:scope1).with(arg1, nil)
but that doesn't work.
I would also appreciate confirmation that this is all that's necessary to test in this situation when I've already tested the scopes anyway would be reassurring.
The Rspec code you wrote is really testing the internal implementation of your method. You should test that the method returns what you want it to return given the arguments, not that it does it in a certain way. That way, your tests will be less brittle. For example if you change what scope1 is called, you won't have to rewrite your my_class_method tests.
I would do that by creating a number of instances of the class and then call the method with various arguments and check that the results are what you expect.
I don't know what scope1 and scope2 do, so I made an example where the arguments are a name attribute for you model and the scope methods simply retrieve all models except those with that name. Obviously, whatever your real arguments and scope methods do you should put that in your tests, and you should modify the expected results accordingly.
I used the to_ary method for the expected results since the self.all call actually returns an ActiveRecord association and therefore wouldn't otherwise match the expected array. You could probably use includes and does_not_includes instead of eq, but perhaps you care about the order or something.
describe MyModel do
describe ".my_class_method" do
# Could be helpful to use FactoryGirl here
# Also note the bang (!) version of let
let!(:my_model_1) { MyModel.create(name: "alex") }
let!(:my_model_2) { MyModel.create(name: "bob") }
let!(:my_model_3) { MyModel.create(name: "chris") }
context "with nil arguments" do
let(:arg1) { nil }
let(:arg2) { nil }
it "returns all" do
expected = [my_model_1, my_model_2, my_model_3]
expect_my_class_method_to_return expected
end
end
context "with a first argument equal to a model's name" do
let(:arg1) { my_model_1.name }
let(:arg2) { nil }
it "returns all except models with name matching the argument" do
expected = [my_model_2, my_model_3]
expect_my_class_method_to_return expected
end
context "with a second argument equal to another model's name" do
let(:arg1) { my_model_1.name }
let(:arg2) { my_model_2.name }
it "returns all except models with name matching either argument" do
expected = [my_model_3]
expect_my_class_method_to_return expected
end
end
end
end
private
def expect_my_class_method_to_return(expected)
actual = described_class.my_class_method(arg1, arg2).to_ary
expect(actual).to eq expected
end
end
I invoke ReleaseSchedule.next_release in other controller
and got the following error
NoMethodError (undefined method `to_criteria' for #<ReleaseSchedule:0x007f9cfafbfe70>):
app/controllers/weekly_query_controller.rb:15:in `next_release'
releae_schedule.rb
class ReleaseSchedule
scope :next_release, ->(){ ReleaseSchedule.where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at).first }
end
That's not really a scope at all, that's just a class method wrapped up to look like a scope. There are two problems:
You're saying ReleaseSchedule.where(...) so you can't chain the "scope" (i.e. ReleaseSchedule.where(...).next_release won't do what it is supposed to do).
Your "scope" ends in first so it won't return a query, it just returns a single instance.
2 is probably where your NoMethodError comes from.
If you really want it to be a scope for some reason then you'd say:
# No `first` or explicit class reference in here.
scope :next_release, -> { where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at) }
and use it as:
# The `first` goes here instead.
r = ReleaseSchedule.next_release.first
But really, you just want a class method:
def self.next_release
where(:release_date.gte => Time.now).without(:_id, :created_at, :updated_at).first
end
The scope macro is, after all, just a fancy way to build class methods. The only reason we have scope is to express an intent (i.e. to build queries piece by piece) and what you're doing doesn't match that intent.
To allow me to quickly filter records in ActiveAdmin i've defined scopes on my model. I have "shipped" and "unshipped" scopes as below. For some reason the "Shipped" scope is working as expected and shows the number of shipped items but the "Unshipped" scope doesn't do anything, it doesn't seem to know what is unshipped. It seems that i have to check and then uncheck "Shipped" checkbox in order for it to know that it's unshipped??
ORDER MODEL
class Order < ActiveRecord::Base
scope :shipped, where(:shipped => true)
scope :unshipped, where(:shipped => false)
end
ADMIN ORDER MODEL
ActiveAdmin.register Order do
scope :all, :default => true
scope :shipped
scope :unshipped
index do
selectable_column
column "Status", :sortable => :shipped do |s|
status_tag((s.shipped? ? "Shipped" : "Unshipped"), (s.shipped? ? :ok : :warning))
end
end
end
Can anyone see what the problem is?
Many Thanks
Is that the actual code from your model?
It should be:
scope :shipped, -> { where(shipped: true) }
scope :unshipped, -> { where(shipped: false) }
Realised that shipped was not by default set to false so fixed the issue by doing so in the Orders table.
I'm refactoring scaffolding templates and I hit a wall with this issue:
I was trying to call service (some security logic) from template _FORM.GSP - but in the code part, not in the output part
I've read and tried what was suggested in here: How do I call a Grails service from a gsp?
I've tried to use taglib, but my knowledge of grails may not be extensive enough for that
I've tried add import and def to the beginning of _FORM.GSP file (both grailsApplication and application instantiation of service were crashing on missing property application resp. missing property grailsApplication)
I've even tried to call the taglib from the code both directly as method isAllowedToEdit and also as g.isAllowedToEdit both crashing on unknown method resp. "no such property g"
it seems that template _form.gsp has different rules than standard gsp view
I want to do something like this:
private renderFieldForProperty(p, owningClass, prefix = "") {
boolean hasHibernate = pluginManager?.hasGrailsPlugin('hibernate')
boolean display = true
boolean required = false
if (hasHibernate) {
cp = owningClass.constrainedProperties[p.name]
display = (cp ? cp.display : true)
required = (cp ? !(cp.propertyType in [boolean, Boolean]) && !cp.nullable && (cp.propertyType != String || !cp.blank) : false)
}
/* trying to do this part */
// I want to assign value to cp.editable - so later I can render read-only fields in renderEdit
if (!mySecurityService.canEdit(springSecurityService.currentUser, owningClass.getClass(), actionName, p.name)) {
cp.editable = false
}
/* trying to do this part */
if (display) { %>
<div class="fieldcontain \${hasErrors(bean: ${propertyName}, field: '${prefix}${p.name}', 'error')} ${required ? 'required' : ''}">
<label for="${prefix}${p.name}">
<g:message code="${domainClass.propertyName}.${prefix}${p.name}.label" default="${p.naturalName}" />
<% if (required) { %><span class="required-indicator">*</span><% } %>
</label>
${renderEditor(p)}
</div>
<% } } %>
if there is any way to assign cp.editable - I'll try your suggestions
it seems that template _form.gsp has different rules than standard gsp view
The generated _form.gsp works same as other gsps but the template inside the scr/templates/scaffolding/ is different. Customizing the templates like you are doing is a bit more tricky. Keep in mind that the logic you are writing is for Grails on how to generate views(gsp). Meaning you are telling Grails to check some logic before generating the views in memory or in the file. You might be able to accomplish that to some extend for dynamic (in memory) scaffolding at run-time but for sure not for static scaffolding. That's because Grails
is not aware of currentUser when generating the templates.
Your problem will be much simpler if you generate your views and then customize them instead of modifying their templates. Then you can inject your services and do other checks. However, as you also mentioned those logics are better off in a tag library here.
Also since you mentioned security, rendering a field non-editable does not guaranty inability to edit your fields. I would suggest to put the check logic inside your controller for example in SAVE or UPDATE action to prevent any unauthorized user editing fields.
Did you try this?
<%# page import="com.myproject.MyService" %>
<%
def myService = grailsApplication.classLoader.loadClass('com.myproject.MyService').newInstance()
%>
this will work for sure.
go through this link : click here