I have two domains that are connected with one-to-many relation - One User may have many Associations. I want to view all associations that belong to this user. I'm using scaffolding plugin. So code that should return list of Associations in AssociationController looks lile this:
def index(Integer max) {
respond Association.list(params), model:[associationInstanceCount: Association.count()]
}
And on the User view page I have the following code:
<g:form controller="Association" >
<fieldset class="buttons">
<g:hiddenField name="user.id" id="user.id" value="${userInstance.id}"/>
<g:actionSubmit class="list" action="index" value="${message(code: 'default.list.label', default: 'Show Associations')}"/>
</fieldset>
</g:form>
When I press this actionSubmit user.id is sent with the request and it is within params map (I checked it with debug) but for some reason Association.list(params) returns all Associations (for all users) despite the fact that I'm using this user.id within params. A also tried rename user.id to just user and it didn't work either. Seems like this .list() should be used only for sorting or I'm doing something wrong. Can you help me with this please? Thank you!
To clarify the given answer: list( params ) returns ALL results for the domain object, and accepts only sorting (order by) and view-port (limit/offset) parameters. It does not provide any where clause (well, apart from discriminator-column)
UPDATE:
you can use findAllBy*:
Association.findAllByUserId( params.long( 'user.id' ), params )
or criteria if you need to make your query more complex:
Association.withCriteria{
eq 'id', params.long( 'user.id' )
maxResults params.int( 'max' ) ?: 10
firstResult params.int( 'offset' ) ?: 0
if( params.sort && params.order ) order params.sort, params.order
}
According to Grails documentation, list does not take an id as parameter.
Seeing your max, it would be Association.list(max: max) and for what you want, it is something like:
Association.findAllByUserId(params.id, [max: max])
Where params.id contains the id of the wanted user (or params['user.id']).
More on findAllBy...
Not sure about the previous response but :
Association.where {
user {
eq 'id', params['user.id']
}
} .list()
You can add params to list call if you use pagination.
Another solution :
def index(User user) {
respond user.associations, , model:[associationInstanceCount: user.associations.size()]
}
But you'll have to name your param 'id'
It mostly depends on how you have designed your domain objects and what's your goal here.
Related
I have a model CoffeeShop with a column "wifi_restrictions" - an integer value which represents the number of hours you can use the wifi.
I am using the scopes gem, and would like it so that if I get a value in params of "0", then it will return all instances of CoffeeShop where wifi_restrictions = 0.
I have tried two methods to implement this.
Method 1:
In my form I have the following:
<input type="checkbox" name="no_wifi_restrictions" value="0">
In my model I have:
scope :wifi_restrictions, -> hours { where(wifi_restrictions: hours) }
And in my controller:
has_scope :wifi_restrictions, type: :integer
Result: undefined method `any?' for nil:NilClass
(I get this even when there are no params given for the scopes to filter, which I cannot wrap my head around).
Method 2:
In this method I have tried defining a new scope, "no_wifi_restrictions".
In my form I have the following:
<input type="checkbox" name="no_wifi_restrictions" value="true">
Model:
scope :no_wifi_restrictions, -> { where(wifi_restrictions: 0) }
Controller:
has_scope :no_wifi_restrictions, type: :boolean
Result:
This correctly filters when the value is "true" (no_wifi_restrictions=true), but when the value is "false" (no_wifi_restrictions=false), all instances are returned.
In the console, if I search CoffeeShop.no_wifi_restrictions, this filters correctly. But if I search CoffeeShop.where(no_wifi_restrictions: true) or CoffeeShop.where(no_wifi_restrictions: false), then I get no instances returned for either. So I'm unsure as to how it's working even 50% of the time, (and I'm not sure how to investigate what's going on under the hood).
The following lines will not work:
CoffeeShop.where(no_wifi_restrictions: true)
CoffeeShop.where(no_wifi_restrictions: false)
These lines are trying to get all CoffeeShops where the column no_wifi_restrictions is true or false and I guess the CoffeeShops don't have that column.
If the param value is true, you want the scope to return all CoffeeShops where wifi_restrictions = 0 and else it should return all CoffeeShops where wifi_restrictions > 0.
Maybe it's also easier to add an boolean column for wifi restrictions. Looks like you're now trying to compare booleans with integers.
OK, so I was thinking that the following would essentially transform an integer value into a boolean when calling the scoped method.
:no_wifi_restrictions, ->{ where(wifi_restrictions: 0) }
So I thought ok, if I send in my params no_wifi_restrictions=true, that it would return all instances of CoffeeShop which now return if I search in the console:
CoffeeShop.no_wifi_restrictions.
This is not how it works, however. Because in my params, it passes a value. So when I try that, with the param of "true" I get:
wrong number of arguments (given 1, expected 0)
The solution for this, I found, is the following:
scope :no_wifi_restrictions, -> hours { where(wifi_restrictions: hours) }
which can also be written like this:
scope :no_wifi_restrictions, -> hours { where("wifi_restrictions = ?", hours) }
What this does is allow for an argument to be passed to the scope (hours). So in this case I pass it the value of "0" in params and it will run the following:
CoffeeShop.where(wifi_restrictions: 0)
Essentially, the "hours" value is unnecessary for my needs because I don't really want or need a an argument, it's just that I have to provide a value in params (unless I'm mistaken, I have also tried removing the value attribute, but this results in a default value of "on").
Hence, the following will also work even though the argument passed is essentially redundant:
scope :no_wifi_restrictions, -> hours { where("wifi_restrictions = 0", hours) }
In this case, I could really give any value in params, such as:
value="this_is_a_useless_param"
...and it will still work, because all it needs is for an argument to be passed to the scope.
Perhaps someone knows a neater way of achieving this, but for now this worked for me.
I finally got my filterrific get working and its a great gem, if not a little complex for a noob like me.
My original index page was filtering the active records based on those nearby to the user like this:
def index
location_ids = Location.near([session[:latitude], session[:longitude]], 50, order: '').pluck(:id)
#vendor_locations = VendorLocation.includes(:location).where(location_id: location_ids)
#appointments = Appointment.includes(:vendor).
where(vendor_id: #vendor_locations.select(:vendor_id))
end
So this pulls in all of the Appointments with Vendors in the area, but how do I pass this over to the Filterrific search:
#filterrific = initialize_filterrific(
params[:filterrific],
select_options:{ sorted_by: Appointment.options_for_sorted_by, with_service_id: Service.options_for_select },
) or return
#appointments = #filterrific.find.page(params[:page])
respond_to do |format|
format.html
format.js
end
It seems like the Filterrerrific is loading ALL of the appointments by default, but I want to limit to the ones nearby. What am I missing?
What you appear to be missing is a param default_filter_params to filterrific macro in the model. (Your question didn't mention that you made any adjustments to the VendorLocation model, since that is the object that you want to filter, that's where the macro should be called. Maybe you just omitted it from your question...)
From the model docs:
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' },
available_filters: [
:sorted_by,
:search_query,
:with_country_id,
:with_created_at_gte
]
)
You probably found this already, it was on the first page of the documentation, but there's more important stuff in the example application that you need (I ran into this too, when I was just recently using Filterrific for the first time.)
The information on the start page is not enough to really get you started at all.
You have to read a bit further to see the other ways you may need to change your models, model accesses, and views in order to support Filterrific.
The part that makes the default filter setting effective is this default_filter_params hash (NOT select_options, which provides the options for "select" aka dropdown boxes. That's not what you want at all, unless you're doing a dropdown filter.) This hash holds a list of the scopes that need to be applied by default (the hash keys) and the scope parameter is used as the hash value.
That default_filter_params hash may not be the only thing you are missing... You also must define those ActiveRecord scopes for each filter that you want to use in the model, and name these in available_filters as above to make them available to filterrific:
scope :with_created_at_gte, lambda { |ref_date|
where('created_at >= ?', ref_date)
end
It's important that these scopes all take an argument (the value comes from the value of the filter field on the view page, you must add these to your view even if you want to keep them hidden from the user). It's also important that they always return ActiveRecord associations.
This is more like what you want:
scope :location_near, lambda { |location_string|
l = Location.near(location_string).pluck(:id)
where(location_id: l)
end
The problem with this approach is that in your case, there is no location_string or any single location variable, you have multiple coordinates for your location parameters. But you are not the first person to have this problem at all!
This issue describes almost exactly the problem you set out to solve. The author of Filterrific recommended embedding the location fields into hidden form fields in a nested fields_for, so that the form can still pass a single argument into the scope (as in with_distance_fields):
<%= f.fields_for :with_distance do |with_distance_fields| %>
<%= with_distance_fields.hidden_field :lat, value: current_user.lat %>
<%= with_distance_fields.hidden_field :lng, value: current_user.lng %>
<%= with_distance_fields.select :distance_in_meters,
#filterrific.select_options[:with_distance] %>
<% end %>
... make that change in your view, and add a matching scope that looks something like (copied from the linked GitHub issue):
scope :with_distance, -> (with_distance_attrs) {
['lng' => '-123', 'lat' => '49', 'distance_in_meters' => '2000']
where(%{
ST_DWithin(
ST_GeographyFromText(
'SRID=4326;POINT(' || courses.lng || ' ' || courses.lat || ')'
),
ST_GeographyFromText('SRID=4326;POINT(%f %f)'),
%d
)
} % [with_distance_attrs['lng'], with_distance_attrs['lat'], with_distance_attrs['distance_in_meters']])
}
So, your :with_distance scope should go onto the VendorLocation model and it should probably look like this:
scope :with_distance, -> (with_distance_attrs) {
lat = with_distance_attrs['lat']
lng = with_distance_attrs['lng']
dist = with_distance_attrs['distance']
location_ids = Location.near([lat, lng], dist, order: '').pluck(:id)
where(location_id: location_ids)
end
Last but not least, you probably noticed that I removed your call to includes(:location) — I know you put it there on purpose, and I didn't find it very clear in the documentation, but you can still get eager loading and have ActiveRecord optimize into a single query before passing off the filter work to Filterrific by defining your controller's index method in this way:
def index
#appointments = Appointment.includes(:vendor).
filterrific_find(#filterrific).page(params[:page])
end
Hope this helps!
I have a database of different food menus that I am trying to search through. In general everything works fine, but I think that there must be a cleverer way in writing the code compared to what I am doing now.
Every menu has a set of boolean attributes describing the kind of kitchen (e.g. cuisine_thai, cuisine_italian, etc.). In my view I have a dropdown allowing the user to select the type of food he wants and then I am passing the param on and save it in my search-object.
#search.cuisine_type = params[:cuisine_type]
I then continue to check for the different kitchen types and see if there is a match.
#Filter for Thai cuisine
if(#search.cuisine_type == "cuisine_thai")
#menus = #menus.select{ |menu| menu.cuisine_thai == true}
end
#Filter for French cuisine
if(#search.cuisine_type == "cuisine_italian")
#menus = #menus.select{ |menu| menu.cuisine_italian == true}
end
#Filter for Peruvian cuisine
if(#search.cuisine_type == "cuisine_peruvian")
#menus = #menus.select{ |menu| menu.cuisine_peruvian == true}
end
Eventhough the way I do it works, there must be a better way to do this. I feel that the value stored in #search.cuisine_type could just determine the attribute I check on #menus.
Any help on this is greatly appreciated. Thanks!
Yes, your intuition is correct!
I'll assume #menus is an array are ActiveRecord Menu objects, and that the cuisine_* attributes correspond to database columns. In this case you can use ActiveRecord's attributes method.
Every ActiveRecord object has an attributes property. The docs (Rails 4.2.1) say:
attributes()
Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
To verify this is the case, if you peek at the attributes of a given menu, you should see a hash containing:
{
"cuisine_italian" => true,
"cuisine_thai" => false,
# etc.
}
Also, as a minor point of code readability, these two statements are effectively the same:
#menus = #menus.select { ... }
#menus.select! { ... }
Therefore, your search can be rewritten as:
if #search.cuisine_type.present?
#menus.select! { |m| m.attributes[#search.cuisine_type] }
end
Wouldn't it be better if you had column "cuisine" in database and have it set to thai, italian and so on?
Then you'd only check if certain food matches array of kitchens selected by the user.
From the Grails controller function if i want to retrieve a value from another object, i retrieve it as follows:
def person = Person.get(10)
println person.name
The above code will return a person object where the ID is 10, and also it will return the name of that particular user.
Likewise, how can i do such a computation in the view.
View
<body>
<table>
<g:each in="${personInstanceList}" status="i" var="personInstance">
<tr class="${(i % 2) == 0 ? 'even' : 'odd'}">
<td><g:link action="classesoffered"
url="${fieldValue(bean: personInstance, field: "id")}"
id="${personInstance.id}" >
${personInstance.id}
</g:link></td>
.....
... </body>
The above code will display the ID of the person Object. Is it possible for me to use this ID to retrieve a value of another object. For example.
def school = School.get(${personInstance.id})
Can i use the ID (${personInstance.id}) in order to retrieve the school from the View ?
Note: Hope i have explained the question properly. In a nutshell I want to do a computation at the view. To retrieve schoolinstance from ${personInstance.id} from the view.
UPDATE
Person MODEL
String name
int school
School MODEL
String nameOfSchool
You can import a domain in your view with: (first line of the gsp)
<%# page import="com.yourPackage.School" %>
And then, you can use the tag set to create a new variable inside you view.
For example:
<g:set var="school" value="${ School.get(personInstance.id) }" />
If you want to print the value in your GSP (for example the name of the school), you can use:
${ school.nameOfSchool }
(if school is not null of course)
Hope that helps
Rather than trying to do this kind of thing within the view, you should redesign your domain model to fit the task. If you want each Person to be linked to their School then you should do it with a proper association rather than storing an ID (for which, incidentally, you're using the wrong type - by default the ID of a Grails domain class is a Long, not an int):
class Person {
String name
School school
}
class School {
String name
}
and create instances like this:
// create a new school
def school = new School(name:'Example school')
// or fetch an existing one from the DB
// def school = School.get(1)
def person = new Person(name:'Illep', school:school)
With this model, the GSP can access the school name simply as
${personInstance.school?.name}
I have a form being submitted that is saving multiple records, and the parameters look something like this:
{
"utf8"=>"✓",
"_method"=>"put",
"products"=> {
"321" => {
"sale_price"=>"10"
},
"104" => {
"sale_price"=>"10"
}
}
}
Then in my controller, I have this:
#updated_products = Product.update(params[:products].keys, params[:products].values)
This expects the keys (321, 104) to be IDs.
However, I'm using the to_param in my model to change my urls from IDs to another column value.
Is there a way to take the params[:products].keys and swap them for the appropriate IDs so I can use IDs in the .update() statement.
I can use Product.find_by_column_name(321).id to get the id although I don't know how to do this. Still new to rails.
Any help would be appreciated. Thanks.
Looking at the source code here #update iterates through each key and runs update_attributes so it goes through all the validations. You can change your method to
#updated_products = params[:products].inject([]) do |array, (column_id, attributes)|
product = Product.find_by_column_id column_id
if product.update_attributes(attributes)
array << product
else
array
end
end
This may seem a little complex but it is equal to this one below which is easier to understand and code read
#updated_products = []
params[:products].each do |column_id, attributes|
product = Product.find_by_column_id column_id
if product.update_attributes(attributes)
#updated_products << product
end
end